OpenCV VideoCapture无法访问部分摄像头设备索引的解决方法
问题根因
- 硬件带宽瓶颈:PC的USB总线为共享带宽架构,14个摄像头如果全部接在同一组USB根集线器(比如同一个扩展坞、同一排前置USB口)上,OpenCV默认以最高分辨率、最高帧率拉流时,单路摄像头就会占用大量带宽,后续端口的摄像头抢不到带宽资源,就会出现端口可识别但读帧失败的情况,偶数端口全部失效基本都是这个原因导致的。
- 代码存在多处bug:
- 端口枚举逻辑错误:原
list_ports()遇到第一个打不开的端口就直接终止扫描,且扫描时打开摄像头后没有释放句柄,持续占用设备资源。 - 摄像头资源泄漏:采集流程中打开摄像头后,从未调用
release()释放句柄,后续摄像头访问会被占用阻塞。 - 堆叠逻辑错误:原代码在每帧采集的循环内重复初始化像素和数组为0,最终平均计算实际只用到最后1帧,完全没有实现多帧平均的效果。
- 基础语法错误:
list_ports()函数内代码没有正确缩进,循环使用了未定义的变量stunden(实际定义的变量是hours)。 - 采集参数未优化:打开摄像头时没有指定分辨率、帧率,默认参数占用带宽极高,自动对焦参数在帧循环内重复设置,生效逻辑不稳定。
- 端口枚举逻辑错误:原
修复方法
- 硬件调整:把14个摄像头分散接到不同的USB控制器上,台式机优先接后置面板分属不同USB总线的接口,不要全部串在同一个USB Hub/扩展坞上,必要时加装PCIe转USB扩展卡分摊带宽。
- 代码修正:
- 优化端口扫描逻辑,固定扫描0-20端口,遇到打不开的端口直接跳过不终止,扫描完成立刻释放摄像头句柄。
- 采用串行采集逻辑:每次只打开1个摄像头,采完指定帧数、计算完平均图、保存文件后立刻释放摄像头资源,避免同时占用多个设备带宽。
- 打开摄像头后主动设置较低的分辨率、帧率,大幅降低带宽占用,摄像头初始化完成后就关闭自动对焦,避免参数反复调整。
- 修正图像堆叠逻辑:把像素和数组的初始化移到帧采集循环外,所有帧采集完成后统一计算平均值。
- 修复所有语法、变量名错误,提前创建1-14号摄像头对应的存储文件夹,避免文件保存失败。
修正后可运行代码
import os import numpy as np import time from time import localtime import subprocess import sys def install(package): subprocess.check_call([sys.executable, "-m", "pip", "install", package]) # 避免每次运行都重复安装OpenCV try: import cv2 except ImportError: install("opencv-python") import cv2 def list_ports(max_scan_port=20): working_ports = [] available_ports = [] for dev_port in range(max_scan_port): camera = cv2.VideoCapture(dev_port, cv2.CAP_DSHOW) if not camera.isOpened(): print(f"Port {dev_port} is not working.") camera.release() continue is_reading, img = camera.read() w = camera.get(cv2.CAP_PROP_FRAME_WIDTH) h = camera.get(cv2.CAP_PROP_FRAME_HEIGHT) if is_reading: print(f"Port {dev_port} is working and reads images ({h} x {w})") working_ports.append(dev_port) else: print(f"Port {dev_port} for camera ({h} x {w}) is present but does not read.") available_ports.append(dev_port) # 扫描完成立刻释放资源 camera.release() return available_ports, working_ports if __name__ == "__main__": # 提前创建1-14号摄像头的存储目录 for cam_id in range(1, 15): os.makedirs(f"./cam_{cam_id}", exist_ok=True) available, camports = list_ports() print(f"Detected working ports: {camports}") # 采集配置参数 picture_count = 5 # 单次采集用于堆叠平均的帧数 collect_hours = int(input("amount of hours: ")) target_minute = localtime().tm_min # 采集分辨率、帧率,可根据需求调整,数值越低带宽占用越小 FRAME_WIDTH = 640 FRAME_HEIGHT = 480 FRAME_FPS = 15 for collect_round in range(collect_hours): current_hour = localtime().tm_hour print(f"starting hour round: {collect_round + 1}") for port_idx, cam_port in enumerate(camports): cam_id = port_idx + 1 if cam_id > 14: break print(f"currently taking images at camera id: {cam_id}") cam = cv2.VideoCapture(cam_port, cv2.CAP_DSHOW) # 提前设置采集参数降低带宽占用 cam.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_WIDTH) cam.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT) cam.set(cv2.CAP_PROP_FPS, FRAME_FPS) cam.set(cv2.CAP_PROP_AUTOFOCUS, 0) # 等待摄像头参数生效 time.sleep(0.8) img_sum = None capture_success = True timestr = time.strftime("%d.%m.%Y-%H_%M_%S") for frame_idx in range(picture_count): result, image = cam.read() if not result: print(f"capture failure at camera: {cam_id}, frame: {frame_idx + 1}") capture_success = False break if img_sum is None: img_sum = np.zeros_like(image, dtype=np.float64) img_sum += image.astype(np.float64) time.sleep(0.1) # 采完立刻释放摄像头 cam.release() if capture_success: avg_img = (img_sum / picture_count).round().astype(np.uint8) save_path = f"./cam_{cam_id}/pic_{collect_round + 1}_{timestr}.tiff" cv2.imwrite(save_path, avg_img) print(f"save average image to {save_path}") print("waiting for the next hour") # 等待到下一小时的目标分钟点 while True: current_time = localtime() if current_time.tm_min == target_minute and current_time.tm_hour != current_hour: break time.sleep(1)
内容的提问来源于stack exchange,提问作者dacon06
相关产品推荐
相关产品推荐

