项目需求控制和打开两个USB摄像头,并且根据相机的PID和VID来打开指定的相机,来区分主副相机,在Windows下可以通过AForge.Video.DirectShow库来实现,但是Ubuntu下没这个库,找了很多资料也没找到Ubuntu下怎么实现,最后只能借助Libusb库来实现,虽然不是很理想,但是应该可以勉强实现。
第一步: 先来了解 什么是USB Device Path/ 什么是PID、VID
Windows操作系统通过一个“设备路径”来唯一“标识”接入系统中的USB设备/接口,这个"设备路径"就是USB Device Path。USB Device Path 常常被传入 Win32 的API函数 CreatFile() 来与USB设备建立通信。更多解释参见Windows USB Device Path,当然Ubuntu下也有。
VID和PID唯一标识一个设备,HardwareID是为了给系统识别的 ,他是根据PID/VID而生成的。这个与序列号没什么关系,序列号一般都是厂家固化到芯片中的信息而已。GUID只是为了标志你安装的设备是属于一个什么类当中,这个类可以显示再设备管理器中。比如:你可以定义一个类,当然这个类有与系统中任何类都不同的GUID,然后选择一个图标和类名,就可以同网卡等其他设备一起显示在设备管理器下的根目录中了
根据USB规范的规定,所有的USB设备都有供应商ID(VID)和产品识别码(PID),主机通过不同的VID和PID来区别不同的设备,VID和PID都是两个字节长,其中,供应商ID(VID)由供应商向USB执行论坛申请,每个供应商的VID是唯一的,PID由供应商自行决定,理论上来说,不同的产品、相同产品的不同型号、相同型号的不同设计的产品最好采用不同的PID,以便区别相同厂家的不同设备。
VID和PID通常情况下有两种存储方式,第一种是主控生产商的VID和PID,存储在主控的bootcode中;第二种是设备生产商的VID和PID,该VID和PID存储在主控外部的非易失性存储设备中(EEPROM或Flash)的设备固件中,当USB设备连接主机时,如果固件中有设备生产商的VID和PID,会将该VID和PID报告给主机,而忽略主控生产商的VID和PID。所以理论上一个USB存储设备的VID应该是设备生产商的VID,而不是主控生产商的VID,这两个VID应该是不同的(主控生产商自己生产的设备除外)。
由于VID和PID重复并不会对产品的使用带来严重影响,很多USB设备生产商(山寨厂居多)为了方便,并不会向USB执行论坛申请自己的VID,而是依然沿用主控生产商的VID或随便向产品写入VID和PID;同时,正规厂家只需要申请VID,PID由厂家自行确定,所以存在相同型号的产品,可能采用了不同的主控(商业需要,很正常),而他们的PID是一样的,基于上述原因通过VID和PID就不能准确识别USB设备的主控型号,这个问题大家在使用USB设备的过程中需要注意。
第二步:查看USB Device Path的方法
Windows系统下
1、鼠标右键“我的电脑”,选择“管理”,进入“计算机管理”。
2、在“计算机管理”界面,依次选择“设备管理器”、“图像设备”、摄像头的型号,选中点击鼠标右键,选择“属性”
选择硬件id,就可以看到usb相机的vid,pid是0x18EC,0x3399(这个是我电脑这款usb相机的pid、vid,不同的设备pid、vid是不一样的)的设备,右击属性里找到Device instance path,别急,这还不是全部的USB Device Path。
最终 USB Device Path 组成格式是:\\?\(第一部分,固定的), hid#vid_1fc9&pid_0130#a&2eb8245&0&0000(第二部分,即Device instance path,\用#替换), #{4d1e55b2-f16f-11cf-88cb-001111000030}"(第三部分,HID类型标识值前加个#)。另外注意在脚本里写入此参数时需要用双引号括起来,类似如下:
"\\?\hid#vid_1fc9&pid_0130#6&20AC856D&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"
第三步:程序获取USB Device Path方法:
windows下,C#可以通过AForge.Video.DirectShow库来获取,并且非常方便,获取方法如下
public static FilterInfoCollection videoDevices;// 枚举所有视频输入设备videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);videoDevices[i].MonikerString //返回值就是Device instance path
Ubuntu下获取方法1:
因为python可以跨平台,Windows下可以Ubuntu下都可以用:但是Windows下需要把dll放到系统目录。Ubuntu下不需要
pip3 install PyQt5pip3 install pyqt5-toolspip install pyusbDownload libusb from libusb releases解压 libusb-xxx.7z并且把\MS64\dll\libusb-1.0.dll复制到C:\Windows\System32
代码如下:
# FileName : usbdemo.py# Author : Adil# DateTime : /9/1 10:26# SoftWare : PyCharmimport usball_devs = usb.core.find(find_all=True)print(all_devs)for d in all_devs:if (d.idVendor == 'VID_04F2') & (d.idProduct == 'PID_B541'):print(d)
用libusb库的时候需要用知道usb相机的个数,因为libusb获取到的usb设备很多不是摄像头设备
import cv2class Camera:def __init__(self, cam_preset_num=10):self.cam_preset_num = cam_preset_numdef get_cam_num(self):cnt = 0for device in range(0, self.cam_preset_num):stream = cv2.VideoCapture(device)grabbed = stream.grab()stream.release()if not grabbed:breakcnt = cnt + 1return cntif __name__ == '__main__':cam = Camera()cam_num = cam.get_cam_num()print(cam_num)
Ubuntu下获取方法2:
Ubuntu下所以的信息都是以文件形成储存,所以可以通过读取设备文件来区别索引号和pid 、vid
def get_usb_camera_index(self):# 获取USB摄像头的索引camera_path = Path('/sys/class/video4linux/')camera_list = list(camera_path.glob('video*'))camera_list.sort()index = 0for i in range(len(camera_list)):camera = camera_list[i]name_file = camera.joinpath('name')with open(name_file, 'r') as f:info = f.readline()if 'USB' in info:index = ibreakreturn index
在Ubuntu下链接多个usb相机时,通过OpenCV打开,只能通过,cv2.VideoCapture(index)来打开指定相机,但是一般情况哪个先上电,哪个index就是0,没法确定打开的是哪个相机。
最后通过端口重映射到新的固定名字,并且设置其权限为可读。使用对应的id端口映射到固定的名字上(看不懂无所谓,按教程来,往下走)终端输入:
sudo vi /etc/udev/rules.d/rplidar.rules
1.按i,进入编辑模式
2.复制以下代码,修改ID,自定义设备的名字,在此特别注意
在我使用的 ubuntu 18.04 环境下,插入一个 usb 相机,会出现两个 /dev/video*,这不是 bug 而是 v4l2 的特性。这里直接给出解决办法,参考了这个问题下的回答,加入 ATTR{index}=="0"来获取真实设备,对于多个 usb 摄像头的话,采用类似规则,ID 号查找的方式不变
KERNEL=="video*", ATTR{index}=="0",ATTRS{idVendor}=="038f", ATTRS{idProduct}=="6001", MODE:="0777", SYMLINK+="people_video"
3.按Esc,输入 :wq (保存退出)创建生效后重新插拔一下USB摄像头或者执行一下命令
sudo service udev reloadsudo service udev restart
查看USB摄像头是否重映射:
ls -l /dev |grep video
可以看到已经映射成功
4.在Ubuntu下打开摄像头
平时是这样的
cap = cv2.VideoCapture(0) # 打开USB摄像头对象
现在就可以精准打开指定摄像头了
cap = cv2.VideoCapture("/dev/people_video") # 打开人体识别摄像头cap = cv2.VideoCapture("/dev/goods_video") # 打开物体识别摄像头
这样就可以打开指定的摄像头,虽然有点曲线救国,但是完美解决问题