You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程调用多USB摄像头时OpenCV显示黑窗口问题求助

多USB摄像头多线程流传输问题:imshow显示空白黑窗口

我正在开发一个基于Python和OpenCV的多USB摄像头同时流传输项目,尝试用多线程并发处理两路摄像头流,但遇到cv2.imshow仅显示空白黑窗口的问题。

问题细节

  • 两个摄像头单独测试时连接正常、功能正常;
  • 单独在代码中指定单个摄像头编号(如cv2.VideoCapture(2)或cv2.VideoCapture(0))可正常运行;
  • 多线程同时运行时,两个摄像头窗口均显示为黑色;
  • 调整time.sleep的时长后无任何改善。

简化代码

import cv2
import concurrent.futures
import time

def gen(src):
    vid = cv2.VideoCapture(src)  
    if not vid.isOpened():
        print(f"Error reading camera {src}")
        return

    while True:
        time.sleep(0.1)
        flag, frame = vid.read()
        if not flag:
            print(f"Failed to capture frame from camera {src}")
            break
        cv2.imshow(f"Camera {src}", frame)
        cv2.moveWindow(f"Camera {src}", src*300, 0)
        k = cv2.waitKey(1)
        if k == ord('q'):
            break

    vid.release()  
    cv2.destroyAllWindows()    

with concurrent.futures.ThreadPoolExecutor() as executor:
    try:    
        executor.map(gen, (0, 2))
    except Exception as e: 
        print(f"Error executing thread function: {e}")    

问题原因与解决方法

OpenCV的GUI操作(如imshow、waitKey)并非线程安全,多线程环境下同时调用会导致渲染异常,出现黑窗口。

修复方案1:主线程统一处理GUI渲染(推荐)

将摄像头帧捕获与GUI渲染分离,子线程仅负责读取帧,主线程统一处理imshow和waitKey,这是最稳定的实现方式:

import cv2
import concurrent.futures
import time
from queue import Queue

# 帧队列,用于线程和主线程间传递帧
frame_queues = {0: Queue(maxsize=1), 2: Queue(maxsize=1)}

def capture_frames(src):
    vid = cv2.VideoCapture(src)  
    if not vid.isOpened():
        print(f"Error reading camera {src}")
        return

    while True:
        flag, frame = vid.read()
        if not flag:
            print(f"Failed to capture frame from camera {src}")
            break
        
        # 队列满时丢弃旧帧,保留最新帧
        if frame_queues[src].full():
            frame_queues[src].get()
        frame_queues[src].put(frame)
        
        time.sleep(0.01)  # 适当降低捕获频率,避免占用过多资源

    vid.release()
    # 放入None标记线程结束
    frame_queues[src].put(None)

# 启动捕获线程
with concurrent.futures.ThreadPoolExecutor() as executor:
    executor.map(capture_frames, (0, 2))

# 主线程统一处理GUI渲染
while True:
    stop_flag = False
    for src in frame_queues:
        if not frame_queues[src].empty():
            frame = frame_queues[src].get()
            if frame is None:
                stop_flag = True
                break
            cv2.imshow(f"Camera {src}", frame)
            cv2.moveWindow(f"Camera {src}", src*300, 0)
    
    # 仅在主线程调用waitKey处理退出逻辑
    if cv2.waitKey(1) == ord('q') or stop_flag:
        break

cv2.destroyAllWindows()

修复方案2:子线程单独处理GUI(不推荐,存在线程安全风险)

若坚持在子线程处理GUI,需确保waitKey调用逻辑不相互干扰,调整代码如下:

import cv2
import concurrent.futures
import time

def gen(src):
    vid = cv2.VideoCapture(src)  
    if not vid.isOpened():
        print(f"Error reading camera {src}")
        return

    window_name = f"Camera {src}"
    cv2.namedWindow(window_name)
    cv2.moveWindow(window_name, src*300, 0)

    while True:
        flag, frame = vid.read()
        if not flag:
            print(f"Failed to capture frame from camera {src}")
            break
        
        cv2.imshow(window_name, frame)
        # 降低waitKey阻塞时间,避免抢占资源
        if cv2.waitKey(1) == ord('q'):
            cv2.destroyAllWindows()
            break

    vid.release()
    cv2.destroyWindow(window_name)    

with concurrent.futures.ThreadPoolExecutor() as executor:
    futures = [executor.submit(gen, src) for src in (0, 2)]
    for future in concurrent.futures.as_completed(futures):
        try:
            future.result()
        except Exception as e:
            print(f"Error executing thread function: {e}")

关键说明

  • OpenCV的GUI组件依赖主线程消息循环,多线程直接调用imshow和waitKey会导致消息处理混乱;
  • 生产者-消费者模式(子线程捕获帧、主线程渲染)能有效避免线程安全问题,是多摄像头流传输的标准实现方式;
  • 避免在子线程中长时间阻塞,确保帧捕获与GUI渲染的频率匹配。

内容的提问来源于stack exchange,提问作者vukica

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 16:31:14