この時代にCVBS (NTSC)の信号をキャプチャしたくなりました。 最終的にはビデオの一部をGIFアニメにして、Slackか何かに添付したい。 方法は別に何でも良いですが、NTSC信号に対してADCをかけるという時点で普通のマイコンなどでは厳しいです。
その一方で、USBで接続するタイプのキャプチャデバイスが安価に売られており、今回はこれをRaspberry Piに挿して使ってみます。
デバイスについて
特に安価なビデオキャプチャデバイスは、同じような見た目をしています。 だいたい機能も同じで、
- コンポジットビデオ端子 or S端子を備える
- USB Video Classで接続する
- (音声用にアナログオーディオ端子(左/右)を備えるが、今回は使わないので割愛)
という感じです。 これらのデバイスは “EasyCAP” と呼ばれており、微妙に中身が異なる場合もあるらしい。
[EasyCAP]の記事一覧 | Jashi’s ROOM - 楽天ブログ
https://plaza.rakuten.co.jp/jashi/diary/ctgylist/?ctgy=16
Easycap - LinuxTVWiki
https://www.linuxtv.org/wiki/index.php/Easycap
ここではこんな感じの、AliExpressのセールにより$0.99で売られていたデバイスを使ってみます。
入手したデバイス
このデバイスでは「MacroSilicon MS2106E」が使われていました。 コンポジットビデオ(とS端子)の信号をUVCに変換する……という直球のICらしいです。
デバイス内部
広く使われているようで開発者向けの情報1があったり、Linuxでサポートされている情報2もありました。
実際、Raspberry Pi (Debian 12 bookworm, kernel 6.6) に繋いでみるとうまくいっている雰囲気。
$ dmesg
[ 6.455881] usb 1-1: New USB device found, idVendor=534d, idProduct=0021, bcdDevice=21.01
[ 6.462107] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 6.465417] usb 1-1: Product: AV TO USB2.0
[ 6.468588] usb 1-1: Manufacturer: MACROSILICON
[ 6.471678] usb 1-1: SerialNumber: 20200909
:
[ 6.507015] hid-generic 0003:534D:0021.0001: hiddev96,hidraw0: USB HID v1.10 Device [MACROSILICON AV TO USB2.0] on usb-20980000.usb-1/input4
:
[ 51.373883] usb 1-1: Found UVC 1.00 device AV TO USB2.0 (534d:0021)
[ 51.428499] usbcore: registered new interface driver uvcvideo
EasyCAPではなく、EasierCAPだった。
$ lsusb
Bus 001 Device 002: ID 534d:0021 MacroSilicon MS210x Video Grabber [EasierCAP]
実際にキャプチャ
CVBSのデコードのときと同様に、Raspberry Piでコンポジットビデオ出力を有効にして、それをキャプチャします。
本番はちゃんと権限を付けようと思いますが、とりあえずrootで試すと……
$ sudo python
>>> import cv2
>>> device_path = '/dev/v4l/by-id/usb-MACROSILICON_AV_TO_USB2.0_20200909-video-index0'
>>> cap = cv2.VideoCapture(device_path)
[ WARN:0@19.003] global ./modules/videoio/src/cap_gstreamer.cpp (2401) handleMessage OpenCV | GStreamer warning: Embedded video playback halted; module source reported: Could not read from resource.
[ WARN:0@19.013] global ./modules/videoio/src/cap_gstreamer.cpp (1356) open OpenCV | GStreamer warning: unable to start pipeline
[ WARN:0@19.016] global ./modules/videoio/src/cap_gstreamer.cpp (862) isPipelinePlaying OpenCV | GStreamer warning: GStreamer: pipeline have not been created
>>> cap.isOpened()
True
# 解像度、FPSを確認
>>> cap.get(cv2.CAP_PROP_FRAME_WIDTH)
480.0
>>> cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
320.0
>>> cap.get(cv2.CAP_PROP_FPS)
25.0
# 設定してみる
>>> cap.set(cv2.CAP_PROP_FRAME_WIDTH, 720)
True
>>> cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
True
>>> cap.set(cv2.CAP_PROP_FPS, 29.97)
True
# 効いていない!
>>> cap.get(cv2.CAP_PROP_FRAME_WIDTH)
480.0
>>> cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
320.0
>>> cap.get(cv2.CAP_PROP_FPS)
25.0
# 一旦置いておき1フレームだけキャプチャ
>>> ret, frame = cap.read()
>>> cv2.imwrite('frame.jpg', frame)
True
>>> cap.release()
こうしてキャプチャした画像は次のとおり。
キャプチャした画像
見切れているのは出力側のせい、これはテレビに映しても同様です。 720x480i (59.94 fps)で出力しているはずですが、480x320でキャプチャされているのは謎。 もしかしたらこれも出力側のせいかも知れません。
追記: 今回のキャプチャでは YUYV
で取得してしまったようです。 そしてこのキャプチャデバイスではYUYVだと480x320までしか設定できない。
$ v4l2-ctl -d /dev/video0 --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
Type: Video Capture
[0]: 'MJPG' (Motion-JPEG, compressed)
Size: Discrete 720x480
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 640x480
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 480x320
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 320x240
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)
[1]: 'YUYV' (YUYV 4:2:2)
Size: Discrete 480x320
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 320x240
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)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))
で、MJPGにすると720x480にも設定できました。 ただし、MJPGだとCPU使用率が上がってしまうのでこれはこれでどうするか……。 追記おわり。
その辺は置いておいても、ちょっとY/C分離が甘いですね。 3Dコムフィルタは使われていないんじゃないか、BPFか2Dコムフィルタ相当で分離されているんじゃないかという気がします。 もっともこれはテキストを出力したために気になるだけで、実際の映像になると目立たなくなるんじゃないでしょうか。
ということで、今後はこれを使ってどんどんキャプチャしていこうと思います。 おわり。