[OpenCV] 複数のWebカメラを使用する場合、USBポートの番号からデバイスを識別するクラスを作ってみました
1 はじめに
CX事業本部の平内(SIN)です。
OpenCVからVideoデバイスにアクセスする場合、デバイスIDを指定する方法しかありません。
DEVICE_ID = 0 # /dev/video0 cap = cv2.VideoCapture (DEVICE_ID)
しかし、Videoデバイスが複数接続された場合、このデバイスIDは、いつも同じとは限りません。 今回は、複数接続時に、安定して同じデバイスにアクセスするためのクラスを作成してみました。
2 環境
作業した環境は、RaspberryPiです。
Raspberry Piは、Model 4B(メモリ4G)で、OSは、本年5月の最新版(Raspbian GNU/Linux 10 (buster) 2020-05-27-raspios-buster-full-armhf.img です。
$ cat /proc/cpuinfo | grep Revision Revision : c03112 $ lsb_release -a No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 10 (buster) Release: 10 Codename: buster $ uname -a Linux raspberrypi 4.19.118-v7l+ #1311 SMP Mon Apr 27 14:26:42 BST 2020 armv7l GNU/Linux
3 デバイスID
最初に、デバイスIDについて確認します。以下、USBカメラを接続した場合、OSに認識されている様子です。 (※ USBカメラ以外の情報及び、ループバック(/dev/Video10,11,12)については、省略されています。)
接続されていない場合
$ lsusb $ ls -la /dev/video*
1台目のカメラ(Logicool)を接続した場合
$ lsusb Bus 001 Device 003: ID 046d:0892 Logitech, Inc. OrbiCam $ ls -la /dev/video* crw-rw---- 1 root video 81, 3 Jul 22 22:02 /dev/video0 crw-rw---- 1 root video 81, 4 Jul 22 22:02 /dev/video1
1台目のカメラ(Logicool)は、/dev/video0でアクセス可能です。
2台目のカメラ(Elecom)を追加した場合
$ lsusb Bus 001 Device 003: ID 046d:0892 Logitech, Inc. OrbiCam Bus 001 Device 004: ID 056e:701c Elecom Co., Ltd $ ls -la /dev/video* crw-rw---- 1 root video 81, 3 Jul 22 22:02 /dev/video0 crw-rw---- 1 root video 81, 4 Jul 22 22:02 /dev/video1 crw-rw---- 1 root video 81, 5 Jul 22 22:03 /dev/video2 crw-rw---- 1 root video 81, 6 Jul 22 22:03 /dev/video3
追加された2台目のカメラ(Elecom)は、/dev/video2でアクセス可能になります。
以下、Logicoolにアクセスしているコード
DEVICE_ID = 0 # /dev/video0(Logicool) cap = cv2.VideoCapture (DEVICE_ID)
しかし、この状態でOSを再起動すると、同じコードで、Elecomの方にアクセスしてしまう場合があります。
これは、デバイスID(/dev/videoX)がOSに認識された順番で割り振られる事が原因です。
lsusbで確認すると、Device 00xの順番が入れ替わってしまっている事が確認できます。
$ lsusb Bus 001 Device 004: ID 046d:0892 Logitech, Inc. OrbiCam Bus 001 Device 003: ID 056e:701c Elecom Co., Ltd
4 USB機器、ポート情報
Linuxでは、/dev/videoXに登録される際に、USB機器の情報やポート番号が、シンボリックリンクとして追加されます。
$ ls -la /dev/v4l/by-id lrwxrwxrwx 1 root root 12 Jul 22 23:11 usb-046d_HD_Pro_Webcam_C920_21E9E21F-video-index0 -> ../../video2 lrwxrwxrwx 1 root root 12 Jul 22 23:11 usb-046d_HD_Pro_Webcam_C920_21E9E21F-video-index1 -> ../../video3 lrwxrwxrwx 1 root root 12 Jul 22 23:11 usb-Sonix_Technology_Co.__Ltd._ELECOM_8MP_Webcam_SN0001-video-index0 -> ../../video0 lrwxrwxrwx 1 root root 12 Jul 22 23:11 usb-Sonix_Technology_Co.__Ltd._ELECOM_8MP_Webcam_SN0001-video-index1 -> ../../video1
$ ls -la /dev/v4l/by-path lrwxrwxrwx 1 root root 12 Jul 22 23:11 platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.1:1.0-video-index0 -> ../../video0 lrwxrwxrwx 1 root root 12 Jul 22 23:11 platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.1:1.0-video-index1 -> ../../video1 lrwxrwxrwx 1 root root 12 Jul 22 23:11 platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.2:1.0-video-index0 -> ../../video2 lrwxrwxrwx 1 root root 12 Jul 22 23:11 platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.2:1.0-video-index1 -> ../../video3
この情報を確認すれば、機器の種類や、接続されているUSBポート番号が、どのデバイスIDに紐付いているかを確認することが出来ます。
下記は、接続したポートによってシンボリックリンクが変わるようです。
USBポート1に接続した場合 usb-0:1.1:1となっている
pi@raspberrypi:~ $ ls -la /dev/v4l/by-path lrwxrwxrwx 1 root root 12 Jul 22 23:35 platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.1:1.0-video-index0 -> ../../video0 lrwxrwxrwx 1 root root 12 Jul 22 23:35 platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.1:1.0-video-index1 -> ../../video1
USBポート4に接続した場合 usb-0:1.4.1:1となっている
pi@raspberrypi:~ $ ls -la /dev/v4l/by-path lrwxrwxrwx 1 root root 12 Jul 22 23:30 platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-video-index0 -> ../../video0 lrwxrwxrwx 1 root root 12 Jul 22 23:30 platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-video-index1 -> ../../video1
5 デバイス一覧クラス
先のコマンドを使用して、ポート番号の情報とともに、デバイス情報を出力するクラスです。
import subprocess class UsbVideoDevice(): def __init__(self): self.__deviceList = [] try: cmd = 'ls -la /dev/v4l/by-id' res = subprocess.check_output(cmd.split()) by_id = res.decode() except: return try: cmd = 'ls -la /dev/v4l/by-path' res = subprocess.check_output(cmd.split()) by_path = res.decode() except: return # デバイス名取得 deviceNames = {} for line in by_id.split('\n'): if('../../video' in line): tmp = self.__split(line, ' ') if( "" in tmp): tmp.remove("") name = tmp[8] deviceId = tmp[10].replace('../../video','') deviceNames[deviceId]=name # ポート番号取得 for line in by_path.split('\n'): if('usb-0' in line): tmp = self.__split(line, '0-usb-0:1.') tmp = self.__split(tmp[1], ':') port = int(tmp[0]) tmp = self.__split(tmp[1], '../../video') deviceId = int(tmp[1]) if deviceId % 2 == 0: name = deviceNames[str(deviceId)] self.__deviceList.append((deviceId , port, name)) def __split(self, str, val): tmp = str.split(val) if('' in tmp): tmp.remove('') return tmp # 認識しているVideoデバイスの一覧を表示する def disp(self): for (deviceId, port, name) in self.__deviceList: print("/dev/video{} port:{} {}".format(deviceId, port, name)) # ポート番号(1..)を指定してVideoIDを取得する def getId(self, port): for (deviceId, p, _) in self.__deviceList: if(p == port): return deviceId return -1
6 実行例
クラスを使用しているコードです。
index.py
import cv2 from usbVideoDevice import UsbVideoDevice usbVideoDevice = UsbVideoDevice() print("情報一覧") usbVideoDevice.disp() print("\nポート番号とデバイスIDの一覧") for port in range(4): deviceId = usbVideoDevice.getId(port + 1) if (deviceId != -1): print("PORT:{} /dev/video{}".format(port + 1, deviceId))
ポート1のみに接続されている場合
$ python3 index.py 情報一覧 /dev/video0 port:1 usb-Sonix_Technology_Co.__Ltd._ELECOM_8MP_Webcam_SN0001-video-index0 ポート番号とデバイスIDの一覧 PORT:1 /dev/video0
ポート2のみに接続されている場合
$ python3 index.py 情報一覧 /dev/video0 port:2 usb-Sonix_Technology_Co.__Ltd._ELECOM_8MP_Webcam_SN0001-video-index0 ポート番号とデバイスIDの一覧 PORT:2 /dev/video0
ポート2及び、3に接続されている場合
$ python3 index.py 情報一覧 /dev/video0 port:2 usb-Sonix_Technology_Co.__Ltd._ELECOM_8MP_Webcam_SN0001-video-index0 /dev/video2 port:3 usb-046d_HD_Pro_Webcam_C920_21E9E21F-video-index0 ポート番号とデバイスIDの一覧 PORT:2 /dev/video0 PORT:3 /dev/video2
そして、このクラスを利用して、ポート番号でOpenCVを初期化するコードは、以下のようになります
import cv2 from usbVideoDevice import UsbVideoDevice usbVideoDevice = UsbVideoDevice() PORT = 1 cap = cv2.VideoCapture (usbVideoDevice.getId(PORT))
これで、意識するべきは、カメラを接続するUSBポートの番号だけということになります。
7 最後に
今回は、変化するデバイスIDとUSBポートの番号を紐付けるクラスを作成してみました。 複数のVideoデバイスを利用する場合は、このような仕組みが必須かも知れません。