多线程实现双路摄像头CRST目标跟踪异常问题咨询
双路USB摄像头CSRT多线程目标跟踪故障解决方案
问题复现
基于多线程实现两台USB外接摄像头的CSRT(原描述中CRST为笔误)实时目标跟踪,单路运行逻辑完全正常,摄像头ID规则为:0=笔记本内置摄像头,1=1号外接摄像头,2=2号外接摄像头。双路运行时出现两类异常:
- 内置摄像头+单外接摄像头组合:两路均弹出ROI框选窗口,但仅一路可正常进入跟踪流程,另一路选完ROI后无响应
- 双外接摄像头组合(核心场景):显示窗口两路画面快速交替闪烁,抛出如下线程异常:
Exception in thread Thread-8: Traceback (most recent call last): File "C:\ProgramData\Anaconda3\lib\threading.py", line 973, in _bootstrap_inner self.run() File "C:\ProgramData\Anaconda3\lib\threading.py", line 910, in run self._target(*self._args, **self._kwargs) File "C:\Users\user\AppData\Local\Temp/ipykernel_18232/2959108515.py", line 59, in object_tracking_1 cv2.error: OpenCV(3.4.11) C:\Users\appveyor\AppData\Local\Temp\1\pip-req-build-neg5amx3\opencv\modules\core\src\dxt.cpp:3335: error: (-215:Assertion failed) type == CV_32FC1 || type == CV_32FC2 || type == CV_64FC1 || type == CV_64FC2 in function 'cv::dft'
公开资料提及该异常可能和USB总线冲突相关,但单路运行无任何问题。
故障根因
- 代码逻辑错误:两个跟踪函数使用了完全相同的显示窗口名
Tracking,且函数内所有逻辑被包裹在if __name__ == '__main__'判断块中,在ipykernel子线程环境下该判断不成立时,会直接导致逻辑不执行;子线程同时调用cv2.selectROI、cv2.waitKey等非线程安全的UI接口,出现资源抢占 - OpenCV版本缺陷:当前使用的3.4.11版本CSRT跟踪器存在全局资源并发冲突bug,多线程同时调用DFT计算时,会拿到被其他线程污染的帧数据,触发类型断言报错
- 硬件带宽冲突:两台高分辨率摄像头同时接入同一个USB根集线器(比如共用同一个扩展坞、同侧USB接口)时,会出现USB带宽不足,导致帧读取数据损坏,进一步触发跟踪器报错
修复步骤
- 代码逻辑修正
- 给两路摄像头分配独立的ROI窗口、显示窗口名称,避免同窗口资源抢占
- 删除子线程函数内冗余的
if __name__ == '__main__'判断,避免子线程逻辑被意外屏蔽 - 不要在子线程内调用
cv2.waitKey,该接口必须在主线程执行,否则会出现UI事件响应异常
- 版本兼容修复
- 升级OpenCV到4.5及以上版本,官方已修复旧版CSRT跟踪器的多线程并发bug
- 若暂时无法升级,给跟踪器更新逻辑加线程互斥锁,保证同一时间只有一个线程调用跟踪器计算接口
- 硬件带宽优化
- 将两台外接摄像头分别接入不同的USB根集线器(比如笔记本左右侧各插一个,不要共用同一个扩展坞)
- 代码中手动设置摄像头分辨率、帧率为较低值(比如640x480@15fps),降低USB带宽占用
最小修改可运行代码
import cv2 import sys import threading (major_ver, minor_ver, subminor_ver) = (cv2.__version__).split('.') # 线程锁,旧版OpenCV需要加锁避免跟踪器资源冲突 tracker_lock = threading.Lock() exit_flag = threading.Event() def create_tracker(): tracker_type = "CSRT" if int(minor_ver) < 3: tracker = cv2.Tracker_create(tracker_type) else: tracker = cv2.TrackerCSRT_create() return tracker, tracker_type def object_tracking(cam_id, window_name): # 初始化摄像头 video = cv2.VideoCapture(cam_id) # 降低分辨率和帧率减少带宽占用 video.set(cv2.CAP_PROP_FRAME_WIDTH, 640) video.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) video.set(cv2.CAP_PROP_FPS, 15) if not video.isOpened(): print(f"Could not open camera {cam_id}") return ok, frame = video.read() if not ok: print(f"Cannot read camera {cam_id}") return # 独立ROI窗口 bbox = cv2.selectROI(f"Select ROI for {window_name}", frame, False) cv2.destroyWindow(f"Select ROI for {window_name}") tracker, tracker_type = create_tracker() tracker.init(frame, bbox) while True: ok, frame = video.read() if not ok: break timer = cv2.getTickCount() # 加锁保证跟踪器调用线程安全,新版OpenCV可去掉锁 with tracker_lock: ok, bbox = tracker.update(frame) fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer) if ok: p1 = (int(bbox[0]), int(bbox[1])) p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3])) cv2.rectangle(frame, p1, p2, (255,0,0), 2, 1) center_x = round((bbox[0]+bbox[2])/2, 2) center_y = round((bbox[1]+bbox[3])/2, 2) cv2.putText(frame, f"x Coordinate : {center_x}", (100,80), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2) cv2.putText(frame, f"y Coordinate : {center_y}", (100,105), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2) else: cv2.putText(frame, "Tracking failure detected", (100,80), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2) cv2.putText(frame, f"{tracker_type} Tracker Cam{cam_id}", (100,20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50),2) cv2.putText(frame, f"FPS : {int(fps)}", (100,50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50), 2) # 独立显示窗口 cv2.imshow(window_name, frame) # 退出标志由主线程waitKey控制 if exit_flag.is_set(): break video.release() if __name__ == '__main__': t1 = threading.Thread(target=object_tracking, args=(1, "Tracking_Cam1")) t2 = threading.Thread(target=object_tracking, args=(2, "Tracking_Cam2")) t1.start() t2.start() # 主线程统一处理UI按键事件 while True: k = cv2.waitKey(1) & 0xff if k == 27: exit_flag.set() break t1.join() t2.join() cv2.destroyAllWindows()
注:如果要运行内置+外接的组合,只需要把其中一个线程的cam_id改成0即可。
内容的提问来源于stack exchange,提问作者sad park
相关产品推荐
相关产品推荐

