From 62292edc70322188357fa033a23a4aabc03c0cf3 Mon Sep 17 00:00:00 2001 From: ywb <347742090@qq.com> Date: Sat, 13 Jun 2026 15:56:09 +0800 Subject: [PATCH] - --- agv_app/config.py | 2 +- agv_app/utils/qr_scanner.py | 35 ++++++++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/agv_app/config.py b/agv_app/config.py index 20d6479..55593ad 100644 --- a/agv_app/config.py +++ b/agv_app/config.py @@ -35,7 +35,7 @@ MAP_CONFIG = { # ========== 摄像头 ========== CAMERA_CONFIG = { - "device_index": 3, # AGV 摄像头 video3(Orbbec Gemini 彩色流,V4L2后端) + "device_index": 4, # AGV 摄像头 video4(Orbbec Gemini 彩色流,YUYV 640x480) "backend": "v4l2", # 使用 V4L2 后端获取标准彩色格式(640x480) "qr_detect_interval": 0.5, "capture_delay": 0.5, diff --git a/agv_app/utils/qr_scanner.py b/agv_app/utils/qr_scanner.py index d0c779c..41b17b9 100644 --- a/agv_app/utils/qr_scanner.py +++ b/agv_app/utils/qr_scanner.py @@ -74,7 +74,7 @@ class QRScanner: # 情况 2: 3 通道但实际帧数据显示为 YUYV(绿屏特征:G 通道全满,B/R 近空) if ndim == 3: g_mean = frame[:, :, 1].mean() - if g_mean > 220 and frame[:, :, 0].mean() < 30 and frame[:, :, 2].mean() < 30: + if g_mean > 80 and frame[:, :, 0].mean() < 30 and frame[:, :, 2].mean() < 30: # 典型的"Lime"绿屏 — 当做 YUYV 原始数据解码 logger.debug(f"检测到绿屏 (G={g_mean:.0f}, B={frame[:,:,0].mean():.0f}, R={frame[:,:,2].mean():.0f}),尝试修复") try: @@ -85,7 +85,7 @@ class QRScanner: # 我们只需要取前 w*h*2 字节作为 YUYV 数据 yuyv_len = w * h * 2 if len(raw_bytes) >= yuyv_len: - yuyv_img = np.frombuffer(raw_bytes[:yuyv_len], dtype=np.uint8).reshape(h, w * 2, 1) + yuyv_img = np.frombuffer(raw_bytes[:yuyv_len], dtype=np.uint8).reshape(h, w, 2) frame = cv2.cvtColor(yuyv_img, cv2.COLOR_YUV2BGR_YUYV) logger.debug("绿屏修复完成") return frame @@ -101,14 +101,35 @@ class QRScanner: # 正常 BGR 帧 return frame - def read_frame(self) -> Optional[np.ndarray]: - """读取一帧""" + def read_frame(self, timeout: float = 2.0) -> Optional[np.ndarray]: + """读取一帧(带超时保护,避免 V4L2 select() 永久阻塞)""" if not self._cap or not self._cap.isOpened(): return None - ret, frame = self._cap.read() - if not ret or frame is None: + + from concurrent.futures import ThreadPoolExecutor, TimeoutError as FuturesTimeout + + pool = ThreadPoolExecutor(max_workers=1) + try: + fut = pool.submit(self._cap.read) + ret, frame = fut.result(timeout=timeout) + if not ret or frame is None: + return None + return self._fix_frame(frame) + except FuturesTimeout: + logger.warning(f"摄像头 read_frame 超时 ({timeout}s),尝试重建 _cap") + self.close() + self.open() + # 重建后重试一次 + if self._cap and self._cap.isOpened(): + ret, frame = self._cap.read() + if ret and frame is not None: + return self._fix_frame(frame) return None - return self._fix_frame(frame) + except Exception as e: + logger.error(f"read_frame 异常: {e}") + return None + finally: + pool.shutdown(wait=False) def detect_qr(self, frame: np.ndarray) -> Optional[str]: """从图像帧中检测二维码"""