-
This commit is contained in:
+1
-1
@@ -35,7 +35,7 @@ MAP_CONFIG = {
|
|||||||
|
|
||||||
# ========== 摄像头 ==========
|
# ========== 摄像头 ==========
|
||||||
CAMERA_CONFIG = {
|
CAMERA_CONFIG = {
|
||||||
"device_index": 3, # AGV 摄像头 video3(Orbbec Gemini 彩色流,V4L2后端)
|
"device_index": 4, # AGV 摄像头 video4(Orbbec Gemini 彩色流,YUYV 640x480)
|
||||||
"backend": "v4l2", # 使用 V4L2 后端获取标准彩色格式(640x480)
|
"backend": "v4l2", # 使用 V4L2 后端获取标准彩色格式(640x480)
|
||||||
"qr_detect_interval": 0.5,
|
"qr_detect_interval": 0.5,
|
||||||
"capture_delay": 0.5,
|
"capture_delay": 0.5,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class QRScanner:
|
|||||||
# 情况 2: 3 通道但实际帧数据显示为 YUYV(绿屏特征:G 通道全满,B/R 近空)
|
# 情况 2: 3 通道但实际帧数据显示为 YUYV(绿屏特征:G 通道全满,B/R 近空)
|
||||||
if ndim == 3:
|
if ndim == 3:
|
||||||
g_mean = frame[:, :, 1].mean()
|
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 原始数据解码
|
# 典型的"Lime"绿屏 — 当做 YUYV 原始数据解码
|
||||||
logger.debug(f"检测到绿屏 (G={g_mean:.0f}, B={frame[:,:,0].mean():.0f}, R={frame[:,:,2].mean():.0f}),尝试修复")
|
logger.debug(f"检测到绿屏 (G={g_mean:.0f}, B={frame[:,:,0].mean():.0f}, R={frame[:,:,2].mean():.0f}),尝试修复")
|
||||||
try:
|
try:
|
||||||
@@ -85,7 +85,7 @@ class QRScanner:
|
|||||||
# 我们只需要取前 w*h*2 字节作为 YUYV 数据
|
# 我们只需要取前 w*h*2 字节作为 YUYV 数据
|
||||||
yuyv_len = w * h * 2
|
yuyv_len = w * h * 2
|
||||||
if len(raw_bytes) >= yuyv_len:
|
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)
|
frame = cv2.cvtColor(yuyv_img, cv2.COLOR_YUV2BGR_YUYV)
|
||||||
logger.debug("绿屏修复完成")
|
logger.debug("绿屏修复完成")
|
||||||
return frame
|
return frame
|
||||||
@@ -101,14 +101,35 @@ class QRScanner:
|
|||||||
# 正常 BGR 帧
|
# 正常 BGR 帧
|
||||||
return frame
|
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():
|
if not self._cap or not self._cap.isOpened():
|
||||||
return None
|
return None
|
||||||
ret, frame = self._cap.read()
|
|
||||||
|
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:
|
if not ret or frame is None:
|
||||||
return None
|
return None
|
||||||
return self._fix_frame(frame)
|
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
|
||||||
|
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]:
|
def detect_qr(self, frame: np.ndarray) -> Optional[str]:
|
||||||
"""从图像帧中检测二维码"""
|
"""从图像帧中检测二维码"""
|
||||||
|
|||||||
Reference in New Issue
Block a user