mechArm 270 Pi 受け渡し動作の成立を探る Log#6 | 届いたUSBカメラを最小手順で確かめ、番号ではなく固有名で同定する
新しいUSBカメラが届いた。受け渡し動作に人の手を映すための目として増やす候補で、まず要るのは「このカメラが本当に映るか」の確認だけだった。制御パイプラインに組み込む前に、接続してデバイスとして見えるか、1フレーム取れるか、その絵が出るか。ここを最小で通す。前の回で、設計書が並べたブロックを上から全部作ってから初めて実機に出して遠回りした。だから今回は逆から進める。動く最小の一点を先に通して、確かめてから次に足す。

カメラを挿す前と後で、デバイス一覧の差分を取る
Linux では USB カメラは /dev/video* というノード(OSがカメラを指すためのファイル)として現れる。だが番号は接続順で入れ替わるので、番号そのものは当てにできない。当てにできるのは「挿す前に無くて、挿した後に増えたもの」という差分と、機器の固有名だ。
まず挿す前。mechArm の中の Raspberry Pi に SSH で入り、いまあるノードを一覧する。
$ ssh ubuntu@192.168.2.1 -i ~/.ssh/id_rsa_pi 'ls /dev/video* 2>/dev/null; echo ---; ls -l /dev/v4l/by-id/ 2>/dev/null'
/dev/video10
/dev/video11
/dev/video12
/dev/video13
/dev/video14
/dev/video15
/dev/video16
---/dev/video10 から 16 まで並んでいるが、これはカメラではなく Pi 内蔵の画像処理・コーデック用ノード。/dev/v4l/by-id/(機器の固有名でノードを指す索引)は空。つまりこの時点でカメラは1台も繋がっていない。
ここでカメラを物理的に挿す。挿した直後にもう一度、今度は接続イベントのカーネルログ(dmesg)と by-id の索引を見る。
$ ssh ubuntu@192.168.2.1 -i ~/.ssh/id_rsa_pi 'dmesg | tail -20; echo ---; ls -l /dev/v4l/by-id/ 2>/dev/null; v4l2-ctl --list-devices 2>/dev/null'
[ 35.477829] brcmfmac: brcmf_cfg80211_set_power_mgmt: power save disabled
[ 35.848662] IPv6: ADDRCONF(NETDEV_CHANGE): wlan0: link becomes ready
[ 39.989275] brcmfmac: brcmf_cfg80211_set_power_mgmt: power save disabled
[ 40.265245] bcmgenet fd580000.ethernet: configuring instance for external RGMII (RX delay)
[ 40.265492] bcmgenet fd580000.ethernet eth0: Link is Down
[ 42.873668] brcmfmac: brcmf_cfg80211_set_power_mgmt: power save enabled
[ 42.929523] netlink: 'wpa_supplicant': attribute type 213 has an invalid length.
[ 43.040573] IPv6: ADDRCONF(NETDEV_CHANGE): wlan0: link becomes ready
[ 323.272906] usb 1-1.4: USB disconnect, device number 3
[ 351.556390] bcmgenet fd580000.ethernet eth0: Link is Up - 1Gbps/Full - flow control rx/tx
[ 351.556430] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[ 359.186366] systemd-journald[147]: File /var/log/journal/f07b339385ed40258a652b84d43ef278/user-1000.journal corrupted or uncleanly shut down, renaming and replacing.
[ 376.028219] usb 1-1.4: new high-speed USB device number 4 using xhci_hcd
[ 376.141728] usb 1-1.4: New USB device found, idVendor=0c45, idProduct=6367, bcdDevice= 1.00
[ 376.141752] usb 1-1.4: New USB device strings: Mfr=2, Product=1, SerialNumber=3
[ 376.141765] usb 1-1.4: Product: Innomaker-U20CAM-720P
[ 376.141774] usb 1-1.4: Manufacturer: Innomaker
[ 376.141782] usb 1-1.4: SerialNumber: SN0001
[ 376.146174] uvcvideo: Found UVC 1.00 device Innomaker-U20CAM-720P (0c45:6367)
[ 376.160039] input: Innomaker-U20CAM-720P : Innoma as /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.4/1-1.4:1.0/input/input1
---
total 0
lrwxrwxrwx 1 root root 12 Oct 13 14:40 usb-Innomaker_Innomaker-U20CAM-720P_SN0001-video-index0 -> ../../video0
lrwxrwxrwx 1 root root 12 Oct 13 14:40 usb-Innomaker_Innomaker-U20CAM-720P_SN0001-video-index1 -> ../../video1差分がはっきり出た。dmesg の末尾に uvcvideo: Found UVC 1.00 device Innomaker-U20CAM-720P (0c45:6367) とある。UVC(USB Video Class)は USB カメラの共通規格で、これに準拠していれば OS 側は追加のドライバなしでカメラとして扱える。プラグ&プレイで認識された、ということ。
by-id には固有名が2本増えた。...-video-index0 → /dev/video0 と ...-video-index1 → /dev/video1。撮影に使うのは前者(キャプチャノード)で、後者は UVC のメタデータ用なので撮影には使わない。挿す前の video10〜16 はカメラではないので、この2本が今回増えたカメラのすべて。
固有名はこれになる。
/dev/v4l/by-id/usb-Innomaker_Innomaker-U20CAM-720P_SN0001-video-index0シリアル番号 SN0001 が入っているので、抜き差しや再起動でノード番号が変わってもこの名前は変わらない。前の回で「番号ではなく固有名で確認する」と決めた運用が、そのままここに効く。
Pi には fswebcam も v4l2-ctl も無く、cv2 だけで撮る
固有名が分かったので、1枚撮る。カメラから1フレーム読んで保存できれば「映る」の確認は済む。ただし撮影に使えるツールが Pi 側に何が入っているか分からないので、まず定番の fswebcam があるか調べ、無ければ代わりに何が使えるかを同時に返す形にした。
$ ssh ubuntu@192.168.2.1 -i ~/.ssh/id_rsa_pi '
DEV=/dev/v4l/by-id/usb-Innomaker_Innomaker-U20CAM-720P_SN0001-video-index0
if command -v fswebcam >/dev/null; then
fswebcam -d "$DEV" -r 1280x720 --no-banner /tmp/cam_test.jpg && ls -l /tmp/cam_test.jpg
else
echo "fswebcam なし。使えるもの:"; command -v v4l2-ctl ffmpeg; python3 -c "import cv2; print(\"cv2\", cv2.__version__)" 2>&1
fi'
fswebcam なし。使えるもの:
cv2 4.5.3-devfswebcam は無く、v4l2-ctl も ffmpeg も無い。使えるのは OpenCV(cv2 4.5.3)だけだった。ちょうどいい。既存の上カメラ・横カメラも cv2 で読んでいるので、撮影手段がこれで揃う。
cv2 で撮る。UVC カメラは開いた直後の数フレームが真っ暗や空で返ることがあるので、最初の数枚は捨ててから保存する。
$ ssh ubuntu@192.168.2.1 -i ~/.ssh/id_rsa_pi '
python3 - <<'"'"'PY'"'"'
import cv2
dev = "/dev/v4l/by-id/usb-Innomaker_Innomaker-U20CAM-720P_SN0001-video-index0"
cap = cv2.VideoCapture(dev, cv2.CAP_V4L2)
if not cap.isOpened():
print("[FAIL] open できない:", dev); raise SystemExit(1)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
ok = False
for i in range(5): # 最初の数フレームは空で返ることがあるので捨てる
ok, frame = cap.read()
if ok and frame is not None:
break
cap.release()
if not ok or frame is None:
print("[FAIL] read できない"); raise SystemExit(1)
h, w = frame.shape[:2]
cv2.imwrite("/tmp/cam_test.jpg", frame)
print(f"[OK] {w}x{h} 保存 /tmp/cam_test.jpg")
PY'
[OK] 1280x720 保存 /tmp/cam_test.jpg[OK] 1280x720 保存。1280×720 の1フレームが取れた。手元の Mac に落として目視する。
$ scp -i ~/.ssh/id_rsa_pi ubuntu@192.168.2.1:/tmp/cam_test.jpg ~/Desktop/
cam_test.jpg 100% 175KB 12.9MB/s 00:00175KB の JPEG が落ちた。ファイルとしては撮れている。ただし中身は白飛びしていて、何を映しているか判別しづらかった。右側の照明に露出が持っていかれ、全体が明るく飛んでいる。開いた直後で自動露出が収束しきる前の1枚だったのが一因。
このカメラは固定焦点で、ピントは遠側にある
白飛びとは別に、絵がぼけていた。捨てるフレーム数を5から15に増やして自動露出の収束を待ち、もう一度撮ってもぼけは残った。近くの物に向けているとボケ、という状態。
ここで距離を変えて確かめた。カメラを近くの物ではなく、1m弱ほど離れた面に向けて撮ると、絵がくっきり合った。アーム本体、黒いカッターマット(方眼と四隅のマーカー)、箱の中の赤いブロック、積み上げた白いブロック、グリッパー、爪先の青いシールまで、全部が判別できる状態で写った。
これで分かったのは、このカメラが固定焦点(ピント位置が固定で動かない)で、そのピントが遠側(1m弱あたり)に置かれているということ。近くはボケ、1m弱で合う。仕様表を読む前に、距離を振って実物で確かめたので、この個体の性格として確定した。近接を鮮明に映す用途には向かず、少し離れた範囲を見る用途に向く。
近接には向かないので、正面から人を映すカメラに回す
この特性は、用途を選べば欠点ではなく素性になる。人は受け渡しのとき1m前後に立つ。ベースの後ろから正面を撮れば、その距離は固定焦点の合焦帯にちょうど乗る。広角なので人と作業面が1枚に入る。近接がボケることは、人を映す分には問題にならない。
役割を、近くを詰めて見る目から、正面に立つ人を捉える目に変えた。人の在・不在や、受け渡し面に入ってくる手を映すための3台目。ここから先の画角の良し悪しは、置く台と高さ・俯角が決まってからの話になる。
Mac 側で4台目として認識させ、既存3台と同じ経路に揃える
既存の2台の外部カメラ(上からの iPhone 14 Pro Max、横からの iPhone 11)は Mac 側に映っている。認識は固有名で解決している。新しいカメラの映像も同じ受け渡し本線に食わせるなら、Pi 側に挿したままだと映像が Pi 側にしか出ず、Mac 側の処理へ運ぶ経路を別に作ることになる。Mac 側の USB に挿し替えれば、既存2台と同じ土俵に並ぶ。
Pi でいったん通したことが、Mac 側でも映る保証になる。UVC 準拠でドライバ不要、macOS も UVC に標準対応しているので、iPhone 2台と同じ映像入力の仕組み(AVFoundation)経由で拾えるはず。挿し替えて確かめる。
まず Mac 側で cv2 が開けるカメラを番号で数える。
$ python3 -c "
import cv2
for i in range(8):
cap = cv2.VideoCapture(i, cv2.CAP_AVFOUNDATION)
if cap.isOpened():
print(i, cap.get(cv2.CAP_PROP_FRAME_WIDTH), cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
cap.release()
"
0 1920.0 1080.0
1 1920.0 1080.0
2 1920.0 1080.0
OpenCV: out device of bound (0-2): 3
OpenCV: camera failed to properly initialize!
[ WARN:0@6.544] global cap.cpp:480 open VIDEOIO(AVFOUNDATION): backend is generally available but can't be used to capture by index
OpenCV: out device of bound (0-2): 4
OpenCV: camera failed to properly initialize!
[ WARN:0@6.557] global cap.cpp:480 open VIDEOIO(AVFOUNDATION): backend is generally available but can't be used to capture by index
OpenCV: out device of bound (0-2): 5
OpenCV: camera failed to properly initialize!
[ WARN:0@6.564] global cap.cpp:480 open VIDEOIO(AVFOUNDATION): backend is generally available but can't be used to capture by index
OpenCV: out device of bound (0-2): 6
OpenCV: camera failed to properly initialize!
[ WARN:0@6.570] global cap.cpp:480 open VIDEOIO(AVFOUNDATION): backend is generally available but can't be used to capture by index
OpenCV: out device of bound (0-2): 7
OpenCV: camera failed to properly initialize!
[ WARN:0@6.577] global cap.cpp:480 open VIDEOIO(AVFOUNDATION): backend is generally available but can't be used to capture by index番号 0/1/2 の3台が開けた(挿す前の状態)。ただし番号だけでは、どれがどのカメラか分からない。Mac 側は番号ではなく、AVFoundation が機器ごとに持つ固有 ID(Unique ID)で個体を見分ける。固有名の一覧はこちらで撮る。
$ system_profiler SPCameraDataType
Camera:
Iriun Camera:
Model ID: d
Unique ID: 5A0B78EA-4C72-485C-B87D-086EC8E5E180
FaceTime HDカメラ:
Model ID: FaceTime HDカメラ
Unique ID: 47B4B64B-7067-4B9C-AD2B-AE273A71F4B5
iPhone14 Pro MAXのカメラ:
Model ID: iPhone15,3
Unique ID: 87B9DFFE-E236-4162-9F3C-8D6800000001挿す前は3台。横カメラの Iriun、Mac 内蔵の FaceTime(既存の解決では除外する対象)、上カメラの iPhone 14。ここに新しいカメラを挿して、もう一度同じコマンドを打つ。
$ system_profiler SPCameraDataType
Camera:
Iriun Camera:
Model ID: d
Unique ID: 5A0B78EA-4C72-485C-B87D-086EC8E5E180
FaceTime HDカメラ:
Model ID: FaceTime HDカメラ
Unique ID: 47B4B64B-7067-4B9C-AD2B-AE273A71F4B5
Innomaker-U20CAM-720P :
Model ID: UVC Camera VendorID_3141 ProductID_25447
Unique ID: 0x21400000c456367
iPhone14 Pro MAXのカメラ:
Model ID: iPhone15,3
Unique ID: 87B9DFFE-E236-4162-9F3C-8D6800000001差分で Innomaker-U20CAM-720P の行が増えた。4台目として、Mac 側でも UVC として認識された。Model ID の VendorID_3141 ProductID_25447 は10進表記で、16進に直すと 0c45 / 6367。Pi 側の dmesg にあった 0c45:6367 と一致する。同じ個体だと突き合わせられた。
index の対応を1回だけ突き合わせ、常用は固有名で引く
Mac 側で各番号から1枚ずつ撮り、どの番号がどのカメラかを目で対応づける。番号は接続順で変わるので常用しないが、「番号 ↔ 実際の写り ↔ 固有 ID」の対応を1回取っておくと、後で固有名から引くときの裏取りになる。
$ python3 -c "
import cv2
for i in range(6):
cap = cv2.VideoCapture(i, cv2.CAP_AVFOUNDATION)
if cap.isOpened():
ok=False
for _ in range(15): # AE収束待ち(Pi側と同じ)
ok, f = cap.read()
if ok:
cv2.imwrite(f'/tmp/mac_cam_{i}.jpg', f)
print(i, int(cap.get(3)), int(cap.get(4)), '-> /tmp/mac_cam_%d.jpg' % i)
cap.release()
"
$ open /tmp/mac_cam_*.jpg4枚が撮れて、対応が確定した。
index 0 = 新カメラ(Innomaker U20CAM-720P)。アームを至近から見上げる絵。解像度は 1280×720。
index 1 = Mac 内蔵の FaceTime。既存の解決では除外する対象。
index 2 = 横カメラ(Iriun / iPhone 11)。アームを真横から見る絵。
index 3 = 上カメラ(iPhone 14 Pro Max)。マットを真上から見下ろす俯瞰。
新カメラは今 index=0 を取った。解像度でも見分けられる。新カメラだけが 1280×720(720p)で、iPhone 2台は 1920×1080(1080p)。ただしこの番号は接続順で変わるので、常用はしない。登録に使うのは固有名で、表示名に U20CAM または Innomaker を含むもので引く(部分一致)。Model ID を補助にする。Unique ID の 0x21400000c456367 は挿す USB ポートに依存して変わる可能性があるので、第一のキーにはしない。
映像の中身は、いまマットに転がして撮ったものなので、正面カメラとしての画角の良し悪しはここでは評価しない。それは台と向きが決まってからにする。この回で確かめたのは、このカメラが Mac 側で UVC として生き、4台目として既存の仕組みに乗せられる、というところまで。
次に要るのは正面カメラの台
新しいカメラは Mac 側で認識され、正面カメラに転用できることが確定した。残っているのは物理側で、置くための台がまだ無い。台が無いと正面の向きに固定できず、画角の評価にも進めない。だから次に用意するのは、ベースの後ろから正面を見下ろす向きに固定できる台。高さと俯角は、人の何を映すか(在・不在/手/顔と上体)が決まれば決め打ちできる。既存の上・横カメラをスタンドや三脚で立てているのと同じ系統で揃える。
カメラを既存の映像解決の仕組みに正面カメラとして登録する作業は、コードを変える作業になるので、実装とレビューの経路に回す。そのとき渡す確定情報は、この回で全部揃った。役割は正面カメラ(人認識用)。引くキーは表示名に U20CAM か Innomaker を含むもの。補助キーは Model ID の UVC Camera VendorID_3141 ProductID_25447。解像度は 1280×720。開いた直後は自動露出が収束するまで数フレーム捨てる。固定焦点で1m前後に合い、広角で端に樽型の歪みが出るので、人は画角の中央寄りに置く。
