Summary
opencvではデバイス番号を指定してカメラを特定するが、デバイスの番号は実際に接続されているUSBカメラの状況によって変わるので、デバイス番号をハードコーディングすると、意図しないデバイスからの入力になってしまう。
複数のカメラデバイスがMacに接続されている場合にはユーザに選択させるようにしたい。
キャプチャーする画面サイズを選択できるようにした(2021.7.23追記)
OpenCVではデバイス番号で指定したカメラをopenして使用します。 ところが、複数のカメラデバイスが存在すると0決め打ちでは思っているのと違うデバイスを掴んでしまうことがあります。
import cv2
cap = cv2.VideoCapture(0)
いろいろとググって調べたところ、Macでswiftスクリプトを使うとデバイスの情報が取得できるようです。こちらを参考にしました。この記事で紹介されていたavfcam_list.swiftというスクリプトを実行すると、デバイスの情報がjson形式の文字列として取得できます。
コマンドラインからは以下のように実行でき、手元のMac環境では2つのデバイスが見つかります。 ここでBuild-inカメラは2番目に表示されるので、デバイスIDは1となり、0ではアクセスできないことがわかります。
$ swift ./avfcam_list.swift
{
"SPCameraDataType" : [
{
"_name" : "mmhmm Camera",
"manufacturer" : "mmhmm, inc.",
"spcamera_unique-id" : "mmhmmCameraDevice",
"spcamera_model-id" : "mmhmmCameraModel"
},
{
"_name" : "FaceTime HD Camera (Built-in)",
"manufacturer" : "Apple Inc.",
"spcamera_unique-id" : "0x8020000005ac8514",
"spcamera_model-id" : "UVC Camera VendorID_1452 ProductID_34068"
}
]
}
これができれば、pythonからこのスクリプトを外部コマンドとして呼び出せばよいことになります。外部コマンドの呼び出しには、subprocessモジュールを使用します。(下記の例ではパスは固定としていますが、完成版では実行しているpythonスクリプトと同じ場所のswiftスクリプトを実行します)
エスケープを意識しなくて良いようにshell=True
を指定し、帰りの文字列をvideo_devicesに代入します。文字列はバイナリパックされているので、printするには.decode("utf-8")
を指定して文字列に変換します。文字列をjson形式の辞書として読み込むには、json.loads()
を使用します。python 3.7より前の辞書は順序を保存しないため、collectionsからOrderedDictを importして使用します。json.loads()
にobject_pairs_hook=OrderedDict
を指定することで、OrderedDict形式になります。
import subprocess
import json
from collections import OrderedDict
# Check camera devices with a swift script
camera_devices = subprocess.check_output("swift ./avfcam_list.swift", shell=True)
json_dict = json.loads(camera_devices, object_pairs_hook=OrderedDict)
camera_devices = camera_devices.decode("utf-8")
print(camera_devices)
json_dictを表示すると以下のように、json_dict['SPCameraDataType']
の要素が、各デバイスの情報を値として持つOrderdDictのリストになっていることがわかります。この例ではデバイスが2つあるので、リストの要素はデバイスの情報を保持しているOrderedDict2つです。
# print(json_dict)の結果
OrderedDict([
('SPCameraDataType', [
OrderedDict([('_name', 'mmhmm Camera'), ('manufacturer', 'mmhmm, inc.'), ('spcamera_unique-id', 'mmhmmCameraDevice'), ('spcamera_model-id', 'mmhmmCameraModel')]),
OrderedDict([('_name', 'FaceTime HD Camera (Built-in)'), ('manufacturer', 'Apple Inc.'), ('spcamera_unique-id', '0x8020000005ac8514'), ('spcamera_model-id', 'UVC Camera VendorID_1452 ProductID_34068')])
])])
#json_str = json.dumps(json_dict)
#print(json_str)の結果
{"SPCameraDataType": [
{"_name": "mmhmm Camera", "manufacturer": "mmhmm, inc.", "spcamera_unique-id": "mmhmmCameraDevice", "spcamera_model-id": "mmhmmCameraModel"},
{"_name": "FaceTime HD Camera (Built-in)", "manufacturer": "Apple Inc.", "spcamera_unique-id": "0x8020000005ac8514", "spcamera_model-id": "UVC Camera
VendorID_1452 ProductID_34068"}
]}
ここまでわかれば、num_devices = len(json_dict['SPCameraDataType'])
として要素数を取得し、添字を使ってそれぞれの辞書にアクセすれば良いことになります。
num_devices = len(json_dict['SPCameraDataType'])
val = 0
if num_devices > 1:
print("Select a camera devie:")
for s in range(num_devices):
name = json_dict['SPCameraDataType'][s].get("_name")
print(" %d: %s" % (s, name))
完成したスクリプトは次のようになります。 起動すると、ビデオデバイスを調査し2つ以上のデバイスが見つかったら、ユーザにデバイス番号を入力させます。デバイスが1つしかなければ自動で選定し、1つも見つからなければエラー終了します。
終了は’q’、’s’でその瞬間の画面をキャプチャーして./photo.jpg
に保存します。
ソースコードはこちら(video_capture_test.py)
(avfcam_list.swift)をスクリプトと同じ場所に置いてください。)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# =======================================================
# opencvを使ってカメラから画像をキャプチャするスクリプト
#
# video_capture_test.py
# coded by Noboru Harada (noboru@ieee.org)
#
# Changes:
# 2021/07/04: First version
# =======================================================
import sys
import os
import subprocess
import json
from collections import OrderedDict
import numpy as np
import cv2
# Get path for the swift script (supporse to be in the same location with this python script)
= os.path.dirname(os.path.abspath(__file__))
script_path = "swift " + script_path + "/avfcam_list.swift"
script_path print(script_path)
# Check camera devices with a swift script
= subprocess.check_output(script_path, shell=True)
camera_devices = json.loads(camera_devices, object_pairs_hook=OrderedDict)
json_dict
= camera_devices.decode("utf-8")
camera_devices print(camera_devices)
# see how the OrderdDict looks like
#json_str = json.dumps(json_dict)
#print(json_str)
#print(json_dict)
#print(json_dict.keys())
#print(json_dict['SPCameraDataType'])
if json_dict.get('SPCameraDataType') == None:
print("No camera device found")
sys.exit()
= len(json_dict['SPCameraDataType'])
num_devices if num_devices == 0:
print("No camera device found")
sys.exit()
= 0
val if num_devices > 1:
print("Select a camera devie:")
for s in range(num_devices):
= json_dict['SPCameraDataType'][s].get("_name")
name print(" %d: %s" % (s, name))
try:
= int(input())
val except ValueError:
print("Wrong device id")
= 0
val
if val > num_devices-1 or val < 0:
print("Wrong device id")
= 0
val
= val
device_id = json_dict['SPCameraDataType'][device_id].get("_name")
device_name print(" Use Camera device %d: %s" % (val, device_name))
print("Type 'q' to quit capturing")
= cv2.VideoCapture(device_id)
cap
while(True):
# Capture frame-by-frame
= cap.read()
ret, frame
# convert color frame to gray image
= cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray
# show the frame
"Type 'q' to quit capturing", gray)
cv2.imshow(#cv2.imshow("Type 'q' to quit capturing", frame)
= cv2.waitKey(1) & 0xFF
key
if key == ord('q'):
break
if key == ord('s'):
= "./photo.jpg"
filename
cv2.imwrite(filename,gray)#cv2.imwrite(filename,frame)
# terminate resources
cap.release() cv2.destroyAllWindows()
実行すると、デバイス番号を聞かれるので数字で入力。(デバイスが複数ある場合のみ)
フレームが表示される