前言
在Python编程环境下通过OpenCV库使用VideoCapture调用摄像头时,只能传入摄像头的索引号来进行调用而不知道对应的摄像头名称。在某些情况下,我们可能不清楚每个摄像头对应的索引号,尤其是当电脑连接了多个摄像头时。此时,仅仅依靠索引号来选择摄像头会变得很不方便。
一、解决方法
为了解决这个问题,我们可以利用Windows系统中每个设备和驱动程序都有唯一的GUID这一特性。通过在设备管理器中找到摄像头的GUID,将其传入Python自带的WMI库,以获取符合该GUID条件的摄像头设备列表,并从中提取出硬件ID。接着,可以使用Windows平台的DirectShow API多媒体平台框架,通过Visual Studio编译C++代码生成一个动态链接库,以供Python环境下使用。最后,通过ctypes库调用C++动态链接库,根据传入的硬件ID返回对应的摄像头索引号,从而得知摄像头名称和索引号之间的关系。
二、使用步骤
1、代码下载
具体源码及已编译动态链接库可以参考这个大佬写的:Myles Yang/CvCameraIndex (gitee.com),我是在这个代码的基础上根据本身的需求进行更改的。
2、查看电脑摄像头GUID
GUID是全局唯一标识符,可以理解成区分每个不同类型的设备的编号,不同类型的设备GUID不相GUID(全局唯一标识符)不仅可以用于唯一标识对象,还可以用来区分不同类型的设备。不同类型的设备有不同的GUID,例如显卡和摄像头的GUID是不同的;但相同类型的设备共享相同的GUID,这样所有显卡的GUID都是一样的,而摄像头的GUID则是另一个值。
我们可以在设备管理器界面查看电脑连接摄像头的GUID,如下图中摄像头的GUID为{ca3e7ab9-b4c3-4ae6-8251-579ef933890f}。
之所以不用硬件ID,因为不同摄像头的硬件ID(即VID&PID)是不一样的,达不到搜索电脑多个连接摄像头的目的。
3、编译代码
如果电脑没有wmi
和OpenCV-Python
库要通过以下命令安装,如果安装速度较慢可以选择国内的镜像源。
pip install wmi
pip install OpenCV-Python
我所使用的库版本分别为wmi == 1.5.1,opencv-python==4.9.0.80,大家如果有问题可以尝试卸载库然后安装我提供的版本。
在下载的代码进行适当的修改以满足搜索多个摄像头并连接的需求,具体的代码如下,其中动态链接库的路径要根据本地路径进行更换。
import wmi
import ctypes
import cv2
def get_device_by_guid(guid: str) -> list:
""" 根据GUID获取设备信息
:param guid: 设备的GUID
:return: 符合GUID条件的设备信息列表
"""
c = wmi.WMI()
devices = []
for device in c.Win32_PnPEntity():
if device.ClassGuid == guid:
devices.append(device)
return devices
def extract_vid_pid(hardware_id: str) -> str:
""" 从硬件ID中提取VID和PID部分
:param hardware_id: 硬件ID字符串
:return: VID和PID部分
"""
vid_pid = ""
for item in hardware_id.split('&'):
if 'VID_' in item or 'PID_' in item:
vid_pid += item + "&"
return vid_pid.rstrip("&")
def format_devices(devices: list) -> str:
""" 格式化设备信息为表格
:param devices: 设备信息列表
:return: 格式化后的表格字符串
"""
table = "设备名称\t\t硬件ID\t\t\t索引号\n"
for idx, device in enumerate(devices):
vid_pid = extract_vid_pid(device.HardwareID[0])
# 去掉"USB\"前缀
vid_pid = vid_pid.replace("USB\\", "")
table += f"{
device.Name}\t{
vid_pid}\t{
idx}\n"
print(table)
return table
def devices_tuple(devices: list) -> tuple:
""" 格式化设备信息为表格
:param devices: 设备信息列表
:return: 包含设备名称、硬件ID和索引号的元组
"""
device_names = [device.Name for device in devices]
hardware_ids = [extract_vid_pid(device.HardwareID[0]) for device in devices]
indices = list(range(len(devices)))
return device_names, hardware_ids, indices
def get_camera_index(hwid: str) -> int:
""" 获取摄像头索引编号
:param hwid: 摄像头硬件标识,如VID_13D3&PID_56FF
:return: 摄像头索引编号,如果获取失败则返回 -1
"""
_CvCameraIndex = ctypes.cdll.LoadLibrary('CvCameraIndex_x64.dll')
_CvCameraIndex.getCameraIndex.argtypes = [ctypes.c_char_p]
_CvCameraIndex.getCameraIndex.restype = ctypes.c_int
b_hwid = bytes(hwid, encoding='utf-8')
return _CvCameraIndex.getCameraIndex(b_hwid)
def get_camera_names() -> tuple:
""" 获取摄像头名称、硬件ID和索引号的元组 """
# 替换为你要搜索的设备的GUID
target_guid = "{ca3e7ab9-b4c3-4ae6-8251-579ef933890f}" # 例如,摄像头设备的GUID
# 获取符合GUID条件的设备信息
devices = get_device_by_guid(target_guid)
format_devices(devices)
# 使用之前定义的函数来格式化设备信息
camera_names, hardware_ids, indices = devices_tuple(devices)
return camera_names, hardware_ids, indices
def find_camera():
# 替换为你要搜索的设备的GUID
target_guid = "{ca3e7ab9-b4c3-4ae6-8251-579ef933890f}" # 例如,摄像头设备的GUID
# 获取符合GUID条件的设备信息
devices = get_device_by_guid(target_guid)
# 格式化设备信息为表格
table = format_devices(devices)
# 用户输入要打开的摄像头索引号
idx = int(input("Enter the index of the camera you want to open: "))
# 获取选定摄像头的硬件ID
selected_device = devices[idx]
vid_pid = extract_vid_pid(selected_device.HardwareID[0])
vid_pid = vid_pid.replace("USB\\", "")
# 获取摄像头索引编号
camera_index = get_camera_index(vid_pid)
# 打开选定摄像头并显示
cap = cv2.VideoCapture(camera_index)
while True:
ret, frame = cap.read()
if not ret:
break
cv2.imshow('Camera', frame)
key = cv2.waitKey(1)
if key == 27: # 按下ESC键退出
break
# 释放摄像头资源
cap.release()
cv2.destroyAllWindows()
return cap
if __name__ == "__main__":
find_camera()
下面来讲述一下主要函数的作用
get_device_by_guid函数使用WMI模块根据传入的GUID获取匹配的即插即用(PnP)设备列表
def get_device_by_guid(guid: str) -> list:
""" 根据GUID获取设备信息
:param guid: 设备的GUID
:return: 符合GUID条件的设备信息列表
"""
c = wmi.WMI()
devices = []
for device in c.Win32_PnPEntity():
if device.ClassGuid == guid:
devices.append(device)
return devices
返回的devices列表如下这些参数就是设备管理器中所带的,大家可以执行去了解,可以直接使用devices.name/devices.caption获取设备的名称,devices.DeviceID获取硬件ID
instance of Win32_PnPEntity
{
Caption = "Integrated Camera";
ClassGuid = "{ca3e7ab9-b4c3-4ae6-8251-579ef933890f}";
CompatibleID = {"USB\\COMPAT_VID_13d3&Class_0e&SubClass_03&Prot_00", "USB\\COMPAT_VID_13d3&Class_0e&SubClass_03", "USB\\COMPAT_VID_13d3&Class_0e", "USB\\Class_0e&SubClass_03&Prot_00", "USB\\Class_0e&SubClass_03", "USB\\Class_0e"};
ConfigManagerErrorCode = 0;
ConfigManagerUserConfig = FALSE;
CreationClassName = "Win32_PnPEntity";
Description = "Integrated Camera";
DeviceID = "USB\\VID_13D3&PID_56FF&MI_00\\7&2663F3B7&0&0000";
HardwareID = {"USB\\VID_13D3&PID_56FF&REV_1919&MI_00", "USB\\VID_13D3&PID_56FF&MI_00"};
Manufacturer = "Sonix";
Name = "Integrated Camera";
PNPClass = "Camera";
PNPDeviceID = "USB\\VID_13D3&PID_56FF&MI_00\\7&2663F3B7&0&0000";
Present = TRUE;
Service = "usbvideo";
Status = "OK";
SystemCreationClassName = "Win32_ComputerSystem";
SystemName = "DESKTOP-VNU4EVA";
};
get_camera_index函数根据ctypes获取c++的动态链接库,动态链接库就是把代码封装成一个函数,然后可以跨环境使用,函数根据传入的摄像头硬件ID返回对应的索引编号从而得到摄像头名称、硬件ID和索引号之间的关系。
def get_camera_index(hwid: str) -> int:
""" 获取摄像头索引编号
:param hwid: 摄像头硬件标识,如VID_13D3&PID_56FF
:return: 摄像头索引编号,如果获取失败则返回 -1
"""
_CvCameraIndex = ctypes.cdll.LoadLibrary('CvCameraIndex_x64.dll')
_CvCameraIndex.getCameraIndex.argtypes = [ctypes.c_char_p]
_CvCameraIndex.getCameraIndex.restype = ctypes.c_int
b_hwid = bytes(hwid, encoding='utf-8')
return _CvCameraIndex.getCameraIndex(b_hwid)
编译代码后可以在终端看到输出当前电脑连接的摄像头信息,可以输入索引号进行连接,这里就不再展示了。
注意:如果想要通过VideoCapture同时连接两个以上的摄像头,要确保摄像头不全都连接在一个HUB上,否则会报错
[ WARN:[email protected]] global cap_msmf.cpp:1768 CvCapture_MSMF::grabFrame videoio(MSMF): can’t grab frame. Error: -1072875772。
当前电脑连接的是两个摄像头,可以在设备管理器中看到摄像头的相关属性和代码运行打印出来的是一样的。
参考
根据摄像头硬件标识VID&PID获取OpenCV打开照相机所需参数index索引下标
总结
好了,查看电脑连接的摄像头信息功能已经实现了,也有其他的方法可以实现这个功能。大家可以把遇到的问题以及自己的想法发在评论区中进行讨论。感谢大家观看!