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)
script_path = os.path.dirname(os.path.abspath(__file__))
script_path = "swift " + script_path + "/avfcam_list.swift"
print(script_path)
# Check camera devices with a swift script
camera_devices = subprocess.check_output(script_path, shell=True)
json_dict = json.loads(camera_devices, object_pairs_hook=OrderedDict)
camera_devices = camera_devices.decode("utf-8")
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()
num_devices = len(json_dict['SPCameraDataType'])
if num_devices == 0:
print("No camera device found")
sys.exit()
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))
try:
val = int(input())
except ValueError:
print("Wrong device id")
val = 0
if val > num_devices-1 or val < 0:
print("Wrong device id")
val = 0
device_id = val
device_name = json_dict['SPCameraDataType'][device_id].get("_name")
print(" Use Camera device %d: %s" % (val, device_name))
print("Type 'q' to quit capturing")
cap = cv2.VideoCapture(device_id)
while(True):
# Capture frame-by-frame
ret, frame = cap.read()
# convert color frame to gray image
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# show the frame
cv2.imshow("Type 'q' to quit capturing", gray)
#cv2.imshow("Type 'q' to quit capturing", frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
if key == ord('s'):
filename = "./photo.jpg"
cv2.imwrite(filename,gray)
#cv2.imwrite(filename,frame)
# terminate resources
cap.release()
cv2.destroyAllWindows()実行すると、デバイス番号を聞かれるので数字で入力。(デバイスが複数ある場合のみ)
フレームが表示される
フレームにフォーカスした状態から’s’でキャプチャ。’q’で終了。
キャプチャした画像は“./photo.jpg”に保存される。
保存された画像がこちら
画像には意味はありません。
自室などを移すのが嫌だったので、手近にあるカラフルなものを使いました。
微妙に画角が違うのは、手で持って’s’キーを押したりしていたからです。
カメラの解像度を指定できるようにしました。 完成したスクリプトは次のようになります。 起動すると、ビデオデバイスを調査し2つ以上のデバイスが見つかったら、ユーザにデバイス番号を入力させます。デバイスが1つしかなければ自動で選定し、1つも見つからなければエラー終了します。
終了は’q’、’s’でその瞬間の画面をキャプチャーして保存します。 ファイル名はMacのScreen shotと同じ様式としました。(./Screen Shot +日時)
ソースコードはこちら(video_capture_test2.py)
(avfcam_list.swift)をスクリプトと同じ場所に置いてください。)
この機能を使ってRaspberry PiのHDMI出力をキャプチャすることで、設定画面の保存などに使えます。
キャプチャされたファイルは以下のとおり