LoginSignup
8
6

More than 1 year has passed since last update.

【Python OpenCV】複数のカメラデバイスを識別する方法3選

Last updated at Posted at 2023-03-04

はじめに

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しかよくわからない私のような方でもすぐ使える解決策を記しています。

  1. こちらのサイトの下の方に実行ファイルのダウンロードリンクがあるので、ダウンロード
  2. 作業用Pythonファイルと同じ場所にダウンロードした実行ファイル (camerainfo.exe) を置いてsubprocess.run()
  3. テキストファイル (camera.txt) がカレントディレクトリで作成される
  4. テキストファイルを読み込んでカメラ名のみ抽出

サンプルコード

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ファイルを生成する

以下のサイトを参考にしました。

手順

前準備

  • あらかじめビルド用のパッケージをインストールしておく
    • pip install scikit-build cmake
    • Anaconda の場合は conda install -c conda-forge scikit-buildconda 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 を利用するのが最も簡単だと思います。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6