【Python】PyQt5实现多相机(USB)同时打开
硬件与软件支持解析
硬件支持
对于需要同时操作多个USB相机的应用,标准计算机或工控机的USB端口数量和带宽可能成为瓶颈。通常情况下,一个USB控制器(如集成在主板上的)可以管理两个USB相机的实时数据流,但如果要扩展到六个或更多USB相机,则需要额外的硬件支持。
解决方案:
PCIE USB 扩展卡:使用带有多个USB端口的PCIE扩展卡来增加可用的USB通道数。这种扩展卡插入主板的PCIE插槽中,提供独立工作的USB端口,确保每个相机都有足够的带宽进行数据传输。
软件支持
为了有效地管理和操作多个USB相机,不仅需要合适的硬件,还需要相应的软件库来简化开发过程并确保稳定性和效率。
安装依赖库:
pip install pycameralist opencv-python PyQt5
pycameralist
是一个用于列出系统中所有视频设备的Python库,它可以帮助开发者轻松获取当前连接的所有相机信息。opencv-python
提供了强大的图像处理功能,并且包含了一个易于使用的接口来捕获和显示视频流。PyQt5
是构建图形用户界面的库,这里用来创建窗口并在界面上显示视频流。OpenVideos
定义了一个名为open_videos
的函数,用于管理和打开视频设备(如摄像头)。它使用了OpenCV库(cv2
)来处理视频捕捉,并通过日志记录函数log_message
输出操作信息。
初始化
camera_dict = {}
camera_dict
是一个字典,用来存储每个视频设备的名字及其对应的VideoCapture
对象。函数定义
def open_videos():
open_videos
的函数,负责释放当前已打开的视频设备并尝试重新打开所有可用的视频设备。释放现有视频设备
for cap in camera_dict.values():
if cap is not None and cap.isOpened():
cap.release()
camera_dict
中的所有值(即之前打开的VideoCapture
对象)。VideoCapture
对象存在且是打开状态,则调用release()
方法关闭它,以确保在重新打开前没有残留的打开设备。获取视频设备列表
video_devices = dict(list_video_devices())
log_message(f'视频设备列表:{video_devices}')
list_video_devices()
函数获取系统中所有可用的视频设备,并将结果转换为字典格式。log_message
函数记录下这些视频设备的信息,方便调试和监控。打开视频设备
for (index, name) in video_devices.items():
camera_dict[name] = None
# 打开视频设备
cap = cv2.VideoCapture(index, cv2.CAP_DSHOW)
if cap is not None and cap.isOpened():
camera_dict[name] = cap
log_message(f'视频设备[{name}]打开成功!')
list_video_devices()
获得的视频设备列表。camera_dict
条目设置为None
。VideoCapture
类打开该视频设备,指定设备索引index
和捕获API后端标识符cv2.CAP_DSHOW
(DirectShow,Windows平台上的一个视频捕捉API)。VideoCapture
对象创建成功并且设备确实被打开(isOpened()
返回True
),则更新camera_dict
,将设备名字映射到这个新的VideoCapture
对象,并记录一条日志表示设备打开成功。总结
open_videos
函数的主要功能包括:
VideoCapture
对象,以便后续使用。这种方法可以有效地管理多个视频输入源,并保证每次启动或重新配置视频输入时,程序能够正确地识别和初始化所有的视频设备。此外,通过日志记录,开发人员可以更容易地跟踪视频设备的状态变化,便于排查问题和维护代码。
camera_dict = {}
def open_videos():
for cap in camera_dict.values():
if cap is not None and cap.isOpened():
cap.release()
video_devices = dict(list_video_devices())
log_message(f'视频设备列表:{video_devices}')
for (index, name) in video_devices.items():
camera_dict[name] = None
# 打开视频设备
cap = cv2.VideoCapture(index, cv2.CAP_DSHOW)
if cap is not None and cap.isOpened():
camera_dict[name] = cap
log_message(f'视频设备[{name}]打开成功!')
实时视频刷新
为了实现实时视频流的更新,代码中设置了两个定时器 (QTimer
),分别负责刷新不同的相机组。这样做的目的是减轻CPU负载,防止因为频繁调用导致性能下降。
self.video_timer1 = QTimer(self)
self.video_timer1.timeout.connect(self.updateFrames13)
self.video_timer1.start(50)
self.video_timer2 = QTimer(self)
self.video_timer2.timeout.connect(self.updateFrames46)
self.video_timer2.start(50)
每个定时器触发时都会调用相应的更新函数(如 updateFrames13
和 updateFrames46
),这些函数会遍历指定的相机名称列表,并调用通用的 updateFrames
函数来读取最新的帧数据、添加文本标签,并将图像转换为适合GUI显示的格式。
updateFrames
定义了一个名为updateFrames
的方法,用于更新与一系列摄像头名称关联的视频帧,并将这些帧显示在相应的GUI标签(labels
)上。它使用了OpenCV库来处理视频捕捉和图像处理,并且转换图像格式以便在PyQt或PySide GUI框架中显示
方法签名
def updateFrames(self, names):
self
:指向实例对象本身,表明这是一个类方法。names
:一个包含摄像头名称的列表,每个名称对应于self.cameras
字典中的一个VideoCapture
对象。尝试更新每个摄像头的帧
try:
for name in names:
cap = self.cameras[name]
if cap is not None and cap.isOpened():
_, frame = cap.read()
if not _:
self.cameras[name] = None
log_message(f'视频设备[{name}]断开,已关闭!')
cv2.putText(frame, name, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
else:
frame = np.zeros((480, 640, 3), np.uint8)
cv2.putText(frame, name, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
names
列表:对于每一个摄像头名称,尝试从对应的VideoCapture
对象中读取一帧图像。_
为False
),则认为该摄像头已经断开连接,设置其对应的VideoCapture
对象为None
并记录日志。将图像转换为适合GUI显示的格式
h, w, ch = frame.shape
bytesPerLine = ch * w
convertToQtFormat = QImage(frame.data, w, h, bytesPerLine, QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(convertToQtFormat)
self.labels[name].setPixmap(pixmap.scaled(630, 480, Qt.KeepAspectRatio))
QImage
对象时指定图像数据的排列方式。frame.data
直接从NumPy数组创建QImage
对象,注意这里调用了.rgbSwapped()
方法,因为OpenCV默认使用的BGR格式需要转换为RGB以正确显示。QImage
对象转换为QPixmap
对象,这是为了能够在PyQt或PySide的QLabel
控件中显示图像。QLabel
控件的pixmap
属性,同时保持图像的宽高比缩放到合适的尺寸(630×480)。异常处理
except:
pass
总结
updateFrames
方法的主要目的是周期性地从多个视频源获取最新的帧,并将这些帧更新到GUI界面上相应的标签控件中。通过这种方式,用户可以实时查看多个摄像头的视频流。此外,该方法还处理了摄像头断开的情况,当检测到某个摄像头不再可用时,会显示一条提示信息而不是空内容。这种方法适用于开发视频监控系统或其他需要多摄像头输入的应用程序。
def updateFrames(self, names):
try:
for name in names:
cap = self.cameras[name]
if cap is not None and cap.isOpened():
_, frame = cap.read()
if not _:
self.cameras[name] = None
log_message(f'视频设备[{name}]断开,已关闭!')
cv2.putText(frame, name, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
else:
frame = np.zeros((480, 640, 3), np.uint8)
cv2.putText(frame, name, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
h, w, ch = frame.shape
bytesPerLine = ch * w
convertToQtFormat = QImage(frame.data, w, h, bytesPerLine, QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(convertToQtFormat)
self.labels[name].setPixmap(pixmap.scaled(630, 480, Qt.KeepAspectRatio))
except:
pass
刷新视频方法
refreshVideos
方法提供了手动重新扫描和打开视频设备的功能,这对于应对设备热插拔场景非常有用。该方法会释放所有现有的视频捕获对象,重新枚举视频设备,并尝试重新建立连接。
def refreshVideos(self):
for cap in camera_dict.values():
if cap is not None and cap.isOpened():
cap.release()
video_devices = dict(list_video_devices())
log_message(f'视频设备列表:{video_devices}')
for (index, name) in video_devices.items():
camera_dict[name] = None
# 打开视频设备
cap = cv2.VideoCapture(index, cv2.CAP_DSHOW)
if cap is not None and cap.isOpened():
camera_dict[name] = cap
log_message(f'视频设备[{name}]打开成功!')
self.cameras = camera_dict
总结
本文展示了如何结合硬件扩展和适当的软件库来管理多个USB相机,并在一个图形用户界面中实时显示其视频流。通过合理地分配任务给不同的线程以及对视频设备的有效管理,实现了高效稳定的多相机视频采集和展示系统。
作者:道友老李