# 相机参考工具 (Optimized by XSimple) # 作用: 实时预览指定摄像头,并在主界面对比原始画面与去畸变画面,支持按键抓拍 # 特性: 彻底修复 RK3588 退出后的 MIPI_CSI2 内核报错刷屏问题 import cv2 import threading import sys import os import argparse import numpy as np import time # <--- 修复:引入 time 模块以处理底层 timeout 重试 # ==================== 全局控制与参数 ==================== running = True capture_flag = False # Front 相机内参 (K) 与 畸变系数 (D) K = np.array([[ 3.9875655282072148e+02, 0., 6.3788553583130647e+02], [0.,3.7214138046449295e+02, 3.4915903287538981e+02], [ 0., 0., 1. ]]) D = np.array([ 8.6662165805452579e-02, -2.8155159006680735e-02, -3.5111914522638211e-02, 1.4621518110074484e-02 ]) W, H = 1280, 720 # ==================== 终端指令后台监听 ==================== def input_thread(): """ 负责在后台默默倾听 SSH 终端的输入。 由于 input() 是阻塞式的,将其放入独立后台线程可以防止卡死画面。 """ global running, capture_flag print("\n[XSimple Tool] Terminal commands Ready: 's' = screenshot, 'q' = quit") while running: try: cmd = input().strip().lower() if cmd == 's': capture_flag = True elif cmd == 'q': print("[SSH] Quit command received. Shutting down...") running = False break except EOFError: break except Exception: pass # ==================== 主控与渲染流程 ==================== def main(): global running, capture_flag # 1. 解析参数 parser = argparse.ArgumentParser(description="Multi-Camera Hardware Reference Tool") parser.add_argument("--i", type=str, required=True, choices=["front", "back", "left", "right"], help="摄像头方位 (front, back, left, right)") args = parser.parse_args() camera_mapping = {"front": 0, "left": 1, "back": 2, "right": 3} which_camera = camera_mapping[args.i] cam_name = args.i.upper() print(f"[INFO] Initializing Camera: {cam_name} (Node: /dev/video{which_camera})") # 2. 检查输出文件夹 save_dir = os.path.join(os.getcwd(), "images") if not os.path.exists(save_dir): os.makedirs(save_dir) # 3. 开启终端监听线程 cmd_listener = threading.Thread(target=input_thread, daemon=True) cmd_listener.start() # 4. 初始化 V4L2 摄像头 cap = None try: cap = cv2.VideoCapture(which_camera, cv2.CAP_V4L2) cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"YUYV")) cap.set(cv2.CAP_PROP_FRAME_WIDTH, W) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, H) # 【关键防内核报错修复】:保证底层的 V4L2 有充足缓冲池 cap.set(cv2.CAP_PROP_BUFFERSIZE, 3) if not cap.isOpened(): print(f"[ERROR] Cannot open camera /dev/video{which_camera}", file=sys.stderr) running = False return # 预计算鱼眼镜头的抗畸变映射矩阵 map1, map2 = cv2.fisheye.initUndistortRectifyMap(K, D, np.eye(3), K, (W, H), cv2.CV_16SC2) cv2.namedWindow('Video old vs new', cv2.WND_PROP_FULLSCREEN) cv2.setWindowProperty('Video old vs new', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) print("[INFO] Stream started. Press 'q' on windows or terminal to exit.") # 5. 主线程 UI 渲染循环 (OpenCV 原生归属) while running: ret, f = cap.read() if not ret or f is None: # 出现 select() timeout 时,给物理层重试的机会,不直接崩溃 time.sleep(0.01) continue raw_frame = f.copy() # —— 截图逻辑拦截 —— if capture_flag: filename = os.path.join(save_dir, f"{args.i.lower()}.png") cv2.imwrite(filename, raw_frame) print(f"[SUCCESS] Image saved accurately: {filename}") capture_flag = False # —— 图像处理与拼接 —— undistorted = cv2.remap(f, map1, map2, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT) comparison = np.hstack((f, undistorted)) # 等比例缩放以适应屏幕 scale = min(1920 / comparison.shape[1], 1080 / comparison.shape[0]) if scale < 1: comparison = cv2.resize(comparison, (int(comparison.shape[1] * scale), int(comparison.shape[0] * scale))) # 为了绘制字体时流畅,确保内存连续并转换类型 comparison = np.ascontiguousarray(comparison, dtype=np.uint8) # 绘制 OSD 信息层 text_info = f"Camera: {cam_name} | Press 'q'='Quit', 's'='Screenshot'" cv2.putText(comparison, text_info, (20, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 255), 3, cv2.LINE_AA) # 区分原本与去畸变后的区域 w_half = comparison.shape[1] // 2 h_bottom = comparison.shape[0] - 30 cv2.putText(comparison, "RAW (FISHEYE)", (20, h_bottom), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 3) cv2.putText(comparison, "UNDISTORTED", (w_half + 20, h_bottom), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 3) # —— 显示与按键监控 —— cv2.imshow('Video old vs new', comparison) key = cv2.waitKey(1) & 0xFF if key == ord('q'): print("[GUI] Quit command triggered by UI window.") running = False break elif key == ord('s'): capture_flag = True finally: # 6. 安全清理底层资源 (无论遇到什么错误或中断,此代码块必会执行) print("\n[INFO] Exiting and safely releasing hardware resources...") running = False if cap is not None and cap.isOpened(): cap.release() cv2.destroyAllWindows() print("[INFO] V4L2 Hardware closed peacefully. Goodbye.") if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\n[INFO] Ctrl+C Detected. Terminating safely...") running = False