Summary
opencvではデバイス番号を指定してカメラを特定するが、デバイスの番号は実際に接続されているUSBカメラの状況によって変わるので、デバイス番号をハードコーディングすると、意図しないデバイスからの入力になってしまう。
複数のカメラデバイスがRaspberry Piに接続されている場合にはユーザに選択させるようにしたい。Raspberry Piではv4l2-ctlコマンドの出力を使う。
Macでのやりかたはこちら
OpenCVではデバイス番号で指定したカメラをopenして使用します。 ところが、複数のカメラデバイスが存在すると0決め打ちでは思っているのと違うデバイスを掴んでしまうことがあります。
import cv2
cap = cv2.VideoCapture(0)
Raspberry Piではv4l2-ctlコマンドを使うと、接続されているデバイスの一覧を取得することができます。
コマンドラインからは以下のように実行でき、手元のRaspberry Pi環境では4つのデバイスが見つかります。しかし、video10以上は呼び出せません。また、同じデバイスでも番号の若い方だけが使えます。
$ v4l2-ctl --list-devices
bcm2835-codec-decode (platform:bcm2835-codec):
/dev/video10
/dev/video11
/dev/video12
bcm2835-isp (platform:bcm2835-isp):
/dev/video13
/dev/video14
/dev/video15
/dev/video16
mmal service 16.1 (platform:bcm2835-v4l2-0):
/dev/video0
UVC Camera (534d:2109): USB Vid (usb-3f980000.usb-1.4):
/dev/video1
/dev/video2
pythonからの外部コマンドの呼び出しには、subprocessモジュールを使用します。(v4l2-ctlコマンドはRaspbberry OSにデフォルトでインストールされています)
エスケープを意識しなくて良いようにshell=True
を指定し、帰りの文字列をvideo_devicesに代入します。文字列はバイナリパックされているので、printするには.decode("utf-8")
を指定して文字列に変換します。
その上で、以下のような条件を満たすものを取り出します。
具体的な処理は以下のような処理として記述した
Check camera devices with the v412-ctl command
v4l2-ctlの出力をretで受けて、utf-8
の文字列に変換し、改行\n
コードで分割します。最終的にはretにはv4l2-ctlの出力を行ごとに分割したリストが入ります。
Parse the lines
retのリストを1行ずつl
に取り出して(for l in ret:
)、空行でなく先頭がタブ\t
でない行をデバイス名としてdevices
に記録
先頭が\t
で始まる行はデバイス番号(/dev/video?
)として、最後のデバイス名のリストに追加(リストのリスト)
結果としてdevicesは、[デバイス名, デバイスID, デバイスID, … ]のようなリストがデバイスの個数分だけ格納されたリストが入ったものになる。
リスト内にデバイスIDが複数あっても、先頭のもの以外はopencvで開けないため、devices[i][1]が開ける可能性のあるデバイスID
Sort by device ID
devicesの中身を中身のリストの二番目の要素(使えるデバイスID)で昇順にsortする。
この結果、デバイス名は/dev/videoX
のXが小さい順に並ぶ。
devices.sort(key=sortsecond)
でkeyとして指定している sortsecond = lambda val: val[1]
により二番目の要素でのsortを実現。
List active device IDs
devicesのリストを参照して、list_devicesに[デバイスID数字のみ, デバイスID文字列,デバイス名]のリストとして入れ直す。list_dev_idsには「デバイスID数字のみ」を入れる(その「デバイスID数字のみ」が存在するかどうか後で簡単に確認できるようにするため)
/dev/video10
以降のデバイスIDはopencvから開けないので無視する
import subprocess
command = "v4l2-ctl --list-devices"
# Check camera devices with the v4l2-ctl command
ret = subprocess.check_output(command, shell=True)
ret = ret.decode("utf-8")
ret = ret.split("\n")
#print(ret)
# Parse the lines
devices = []
num_devices = 0
for l in ret:
if l!="" and l[0]!="\t":
dev_name = l
devices.append([dev_name])
num_devices += 1
elif l!="" and l[0]=="\t":
devices[num_devices-1].append(l.replace("\t",""))
# Sort by device ID
sortsecond = lambda val: val[1]
devices.sort(key=sortsecond)
# List active device IDs
list_devices = []
list_dev_ids = []
print("Select a camera devie:")
for l in devices:
dev_name = l[0]
dev_ref = l[1]
dev_id = int(dev_ref.replace("/dev/video",""))
if dev_id < 10:
list_devices.append([dev_id, dev_ref, dev_name])
list_dev_ids.append(dev_id)
print(" {:d}: {:<12s} {:s}".format(dev_id, dev_ref, dev_name))
num_active_devices = len(list_devices)
val = list_devices[0][0]
if num_active_devices > 1:
try:
val = int(input())
except ValueError:
print("Wrong device id")
val = list_devices[0][0]
if val not in list_dev_ids:
print("Wrong device id")
val = list_devices[0][0]
idx = list_dev_ids.index(val)
print("Use {:d}: {:<12s} {:s}".format(list_devices[idx][0], list_devices[idx][1], list_devices[idx][2]))
device_id = val
ここで、sortsecond = lambda val: val[1]
をkeyに指定すると、リストのリストの2番目の要素を用いてsortすることになります。
v4ls-ctlでデバイスを指定して--list-format-ext
を実行すると詳細な情報が出力される
$v4ls-ctl --list-formats-ext --device 1
ここで得られる情報を使って、cv2.CAP_PROP_FOURCCなどを設定すると良さそうに見える。
# フォーマット・解像度・FPSの設定
#cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M','J','P','G'))
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('Y','U','Y','V'))
cap.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT)
cap.set(cv2.CAP_PROP_FPS, FPS)
# フォーマット・解像度・FPSの取得
fourcc = decode_fourcc(cap.get(cv2.CAP_PROP_FOURCC))
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
fps = cap.get(cv2.CAP_PROP_FPS)
print("fourcc:{} fps:{} width:{} height:{}".format(fourcc, fps, width, height))
$v4ls-ctl --list-formats-ext --device 1
ioctl: VIDIOC_ENUM_FMT
Type: Video Capture
[0]: 'MJPG' (Motion-JPEG, compressed)
Size: Discrete 1920x1080
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.040s (25.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 1600x1200
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.040s (25.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 1360x768
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.040s (25.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 1280x1024
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.040s (25.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 1280x960
Interval: Discrete 0.020s (50.000 fps)
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 1280x720
Interval: Discrete 0.017s (60.000 fps)
Interval: Discrete 0.020s (50.000 fps)
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Size: Discrete 1024x768
Interval: Discrete 0.017s (60.000 fps)
Interval: Discrete 0.020s (50.000 fps)
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Size: Discrete 800x600
Interval: Discrete 0.017s (60.000 fps)
Interval: Discrete 0.020s (50.000 fps)
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Size: Discrete 720x576
Interval: Discrete 0.017s (60.000 fps)
Interval: Discrete 0.020s (50.000 fps)
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Size: Discrete 720x480
Interval: Discrete 0.017s (60.000 fps)
Interval: Discrete 0.020s (50.000 fps)
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Size: Discrete 640x480
Interval: Discrete 0.017s (60.000 fps)
Interval: Discrete 0.020s (50.000 fps)
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
[1]: 'YUYV' (YUYV 4:2:2)
Size: Discrete 1920x1080
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 1600x1200
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 1360x768
Interval: Discrete 0.125s (8.000 fps)
Size: Discrete 1280x1024
Interval: Discrete 0.125s (8.000 fps)
Size: Discrete 1280x960
Interval: Discrete 0.125s (8.000 fps)
Size: Discrete 1280x720
Interval: Discrete 0.100s (10.000 fps)
Size: Discrete 1024x768
Interval: Discrete 0.100s (10.000 fps)
Size: Discrete 800x600
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 720x576
Interval: Discrete 0.040s (25.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 720x480
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 640x480
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.200s (5.000 fps)
$v4ls-ctl --list-formats-ext --device 0
ioctl: VIDIOC_ENUM_FMT
Type: Video Capture
[0]: 'YU12' (Planar YUV 4:2:0)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[1]: 'YUYV' (YUYV 4:2:2)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[2]: 'RGB3' (24-bit RGB 8-8-8)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[3]: 'JPEG' (JFIF JPEG, compressed)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[4]: 'H264' (H.264, compressed)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[5]: 'MJPG' (Motion-JPEG, compressed)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[6]: 'YVYU' (YVYU 4:2:2)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[7]: 'VYUY' (VYUY 4:2:2)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[8]: 'UYVY' (UYVY 4:2:2)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[9]: 'NV12' (Y/CbCr 4:2:0)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[10]: 'BGR3' (24-bit BGR 8-8-8)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[11]: 'YV12' (Planar YVU 4:2:0)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[12]: 'NV21' (Y/CrCb 4:2:0)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
[13]: 'RX24' (32-bit XBGR 8-8-8-8)
Size: Stepwise 32x32 - 2592x1944 with step 2/2
完成したスクリプトは次のようになります。 起動すると、ビデオデバイスを調査し2つ以上のデバイスが見つかったら、ユーザにデバイス番号を入力させます。デバイスが1つしかなければ自動で選定し、1つも見つからなければエラー終了します。
終了は’q’、’s’でその瞬間の画面をキャプチャーして./photo.jpg
に保存します。
ソースコードはこちら(video_capture_pi_test.py)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# =======================================================
#
# select a video device on Raspberry Pi using v4l2-ctl
#
# video_capture_pi_test.py
# coded by Noboru Harada (noboru@ieee.org)
#
# Changes:
# 2021/07/25: First version
# 2021/07/27: Fixed a bug picking an idx
# =======================================================
import sys
import os
import cv2
import subprocess
= "v4l2-ctl --list-devices"
command
# Check camera devices with the v4l2-ctl command
= subprocess.check_output(command, shell=True)
ret = ret.decode("utf-8")
ret = ret.split("\n")
ret #print(ret)
# Parse the lines
= []
devices = 0
num_devices for l in ret:
if l!="" and l[0]!="\t":
= l
dev_name
devices.append([dev_name])+= 1
num_devices elif l!="" and l[0]=="\t":
-1].append(l.replace("\t",""))
devices[num_devices
# Sort by device ID
= lambda val: val[1]
sortsecond =sortsecond)
devices.sort(key
# List active device IDs
= []
list_devices = []
list_dev_ids print("Select a camera devie:")
for l in devices:
= l[0]
dev_name = l[1]
dev_ref = int(dev_ref.replace("/dev/video",""))
dev_id if dev_id < 10:
list_devices.append([dev_id, dev_ref, dev_name])
list_dev_ids.append(dev_id)print(" {:d}: {:<12s} {:s}".format(dev_id, dev_ref, dev_name))
= len(list_devices)
num_active_devices
= list_devices[0][0]
val if num_active_devices > 1:
try:
= int(input())
val except ValueError:
print("Wrong device id")
= list_devices[0][0]
val if val not in list_dev_ids:
print("Wrong device id")
= list_devices[0][0]
val
= list_dev_ids.index(val)
idx print("Use {:d}: {:<12s} {:s}".format(list_devices[idx][0], list_devices[idx][1], list_devices[idx][2]))
= val
device_id
# select a camera resolution
= [
res 1280, 720, "0: 1280x720 (HD)"],
[720, 480, "1: 720x480 (SD)"],
[1920, 1080, "2: 1920x1080 (Full HD)"]
[
]print("Select resolution:")
print(" %s" % res[0][2])
print(" %s" % res[1][2])
print(" %s" % res[2][2])
= 0
res_val try:
= int(input())
res_val except ValueError:
print("Wrong resolution settings")
print("Use default resolution setting")
if res_val > 3 or res_val < 0:
print("Use default resolution setting")
= 0
res_val = res[res_val]
res_width,res_height,res_str
print ("Capture with [ %s ]" % (res_str))
print("Type 'q' to quit capturing / Type 's' for screen shot")
= cv2.VideoCapture(device_id)
cap
# set video size
set(cv2.CAP_PROP_FPS, 60)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, res_width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, res_height)
cap.
while True:
# Capture frame-by-frame
= cap.read()
ret, frame
# show the frame
"Type 'q' to quit capturing / Type 's' for screen shot", frame)
cv2.imshow(
= cv2.waitKey(1) & 0xFF
key
if key == ord('q'):
break
if key == ord('s'):
= "./photo.jpg"
filename
cv2.imwrite(filename,frame)
# terminate resources
cap.release() cv2.destroyAllWindows()
実行すると、デバイス番号を聞かれるので数字で入力。(デバイスが複数ある場合のみ)