はじめに
OpenCVで指定するデバイスIDとカメラデバイスの対応はUSBを抜き差ししたりPCを再起動したら変わることがあります。
本記事では、複数のカメラデバイスを使用する際にデバイスIDが変わっても同じデバイスにアクセスできるようにするための方法を3つ紹介します。
なお、使用OSは Windows 11 です。
Pythonのバージョンにこだわりがない方は@youichi_ioさんの記事が参考になると思います。(Python 3.7)
方法1: カメラのプロパティ(デフォルト)の違いを利用する
import cv2
def check_info(last_index):
for i in range(last_index):
try:
cap = cv2.VideoCapture(i)
if cap is None or not cap.isOpened():
raise ConnectionError
print(f"-*- DEVICE_ID: {i} -*-")
fps = cap.get(cv2.CAP_PROP_FPS)
contrast = cap.get(cv2.CAP_PROP_CONTRAST)
saturation = cap.get(cv2.CAP_PROP_SATURATION)
gamma = cap.get(cv2.CAP_PROP_GAMMA)
print(f"FPS: {fps}")
print(f"Contrast: {contrast}")
print(f"Saturation: {saturation}")
print(f"gamma: {gamma}")
except ConnectionError:
break
if __name__ == "__main__":
check_info(10)
上記コードを実行すれば、カメラのプロパティを確認できます。
カメラのデフォルトのプロパティはデバイスごとに異なるはずなので、次のようなコードで特定のプロパティをもつカメラのデバイスIDを指定することができます。
サンプルコード
import cv2
# 指定したいデバイスのプロパティ情報
FPS = 30.0
CONTRAST = 32.0
SATURATION = 64.0
GAMMA = 100.0
class DeviceInfo:
'''
Attributes
----------
fps: float
contrast: float
saturation: float
gamma: float
'''
def __init__(self):
self.fps = 0
self.contrast = 0
self.saturation = 0
self.gamma = 0
def get_info(self, id):
cap = cv2.VideoCapture(id)
if cap is None or not cap.isOpened():
raise ConnectionError
self.fps = cap.get(cv2.CAP_PROP_FPS)
self.contrast = cap.get(cv2.CAP_PROP_CONTRAST)
self.saturation = cap.get(cv2.CAP_PROP_SATURATION)
self.gamma = cap.get(cv2.CAP_PROP_GAMMA)
def identify(self, fps, contrast, saturation, gamma):
if self.fps != fps:
return False
if self.contrast != contrast:
return False
if self.saturation != saturation:
return False
if self.gamma != gamma:
return False
return True
def main():
DEVICE_ID = 0
print(f"デフォルトのデバイスID: {DEVICE_ID}")
for i in range(10):
try:
_deviceInfo = DeviceInfo()
_deviceInfo.get_info(i) # デバイスIDが i のカメラのプロパティ情報を取得
is_identified = _deviceInfo.identify(FPS, CONTRAST, SATURATION, GAMMA)
if is_identified:
DEVICE_ID = i
print(f"デバイス{i}: 全てのプロパティが一致しました")
else:
print(f"デバイス{i}: プロパティが一致しませんでした")
except ConnectionError:
break
print(f"デバイスIDが {DEVICE_ID} に更新されました")
if __name__ == "__main__":
main()
メリット
- Python+OpenCVだけでコードが完結する
- OSに依存せず使えそう
デメリット
- デバイス名がわからないので、どのデバイスを使ってるのか直感的にわかりにくい
方法2: C/C++でデバイス名をテキストファイルに出力してPythonで読み込む
山本ワールドさんのホームページを参考にしました。
C/C++でカメラのデバイス名とそのデバイスに対応している解像度とfpsをテキストファイルに出力する実行ファイルを公開してくださっているので、今回はそれをそのまま使わせてもらいます。
(ソースコードも公開してくださっているので、C/C++に理解のある方は本用途により適したコードを書いた方がスマートだと思います。)
今回は、Pythonしかよくわからない私のような方でもすぐ使える解決策を記しています。
- こちらのサイトの下の方に実行ファイルのダウンロードリンクがあるので、ダウンロード
- 作業用Pythonファイルと同じ場所にダウンロードした実行ファイル (camerainfo.exe) を置いて
subprocess.run()
- テキストファイル (camera.txt) がカレントディレクトリで作成される
- テキストファイルを読み込んでカメラ名のみ抽出
サンプルコード
import os
import subprocess
import re
class CameraName:
'''
Attributes
----------
name: str[]
カメラのデバイス名をID順に格納
'''
def __init__(self):
self.name = []
def get(self):
file_dir = os.path.dirname(__file__)
txt_file = "camera.txt"
subprocess.run(f"{file_dir}/camerainfo.exe")
with open(txt_file) as f:
self.name = []
datalist = f.readlines()
for data in datalist:
repattern = re.compile(r'^\S')
if repattern.match(data):
self.name.append(data.rstrip('\n'))
if __name__ == "__main__":
_camera = CameraDevice()
_camera.get()
for i, name in enumerate(_camera.name):
print(f"デバイス{i}: {name}")
メリット
- デバイス名を取得できるので直感的に使える
- ついでに解像度とfpsの情報も取得できる
デメリット
- 回りくどい
方法3: C/C++ & Pythonでデバイス名を取得するPYDファイルを生成する
以下のサイトを参考にしました。
- 参考サイト: Listing Multiple Cameras for OpenCV-Python on Windows
- ソースコード: https://github.com/yushulx/python-capture-device-list
手順
前準備
- あらかじめビルド用のパッケージをインストールしておく
pip install scikit-build cmake
- Anaconda の場合は
conda install -c conda-forge scikit-build
とconda install cmake
ビルド
git clone https://github.com/yushulx/python-capture-device-list.git
cd python-capture-device-list
python setup.py build install
- ビルド時にアクティブなPythonのバージョンに合わせてPYDファイルが生成される
- 特にエラーがなければ
_skbuild\win_amd64-3.x\cmake-install\device
にPYDファイルが生成されているはず - 生成されたPYDファイルがある device フォルダをまるごとコピーして作業用のPythonファイルと同じ場所にペースト
- あとはモジュールをインポートして
device.getDeviceList()
するだけ
サンプルコード
import device # モジュールをインポート
device_list = device.getDeviceList() # これだけで[デバイス名, 解像度]のリストが取得できる
for i, device in enumerate(device_list):
print(f"デバイス{i}: {device[0]}")
メリット
- デバイス名を取得できる
- 短いコードで済む
デメリット
- ビルド用の外部ライブラリが必要 (ビルド時)
- Pythonのバージョンによって生成されるPYDファイルの中身が変わるので,環境が違うとインポートエラーになったりする
それぞれどんな人におすすめか
方法1: OSを含め環境に依存したくない人におすすめ
方法2: WindowsユーザでPythonのバージョンに依存したくない人におすすめ
方法3: コードをできるだけシンプルにしたい人におすすめ
おわりに
初投稿なので、わかりにくいところがあっても多めに見ていただけると幸いです。
ちなみに、@youichi_ioさんが紹介されている記事でも本記事の 方法3 と同じようにPYDファイルを使用しています。 (https://github.com/pvys/CV-camera-finder からダウンロードできる
pymf.pyd
を使用する)
私も参考にさせていただいたのですが、モジュールのインポートがうまくいかなかったため、Developer Command Prompt for VS でdumpbin /dependents pymf.pyd
をしてみたところ、python37.dll
を参照していることがわかりました。
よって、おそらく Python 3.7 でないとモジュールのインポートがうまくいきません。
逆に、Python 3.7 でも問題ない場合はpymf.pyd
を利用するのが最も簡単だと思います。