pygameを使えば、カメラの画像を取得してCUIの画面(フレームバッファ)に書き込めないか・・・
Raspberry Piのタッチパネル対応アプリ開発TIPS - karaage. [からあげ]
を試してみる。
pip3 install pygame
タッチディスプレイも使いたかったのですが、残念ながら、タッチディスプレイをCUIで使う方法がわわかりません。
evtest - 入力デバイスのイベントモニタおよびクエリツール
sudo apt install evtest
表示してみましょう。
$ sudo evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: CS-USB-IMX307: UVC Camera
/dev/input/event1: Mitsumi Electric Apple Extended USB Keyboard
/dev/input/event2: Mitsumi Electric Apple Extended USB Keyboard System Control
/dev/input/event3: Mitsumi Electric Apple Extended USB Keyboard Consumer Control
/dev/input/event4: CS-USB-IMX307: UVC Camera
/dev/input/event5: WaveShare WaveShare
Select the device event number [0-5]:
今回のディスプレイのメーカ名が「/dev/input/event5: WaveShare WaveShare」として表示されています。どうもこのevent5ってのが怪しい。
Select the device event number [0-5]: 5 [enter]
とすると、イベントが表示されます。タッチパネルを触ると反応がありました!!やはり「/dev/input/event5」がタッチパネルのようです!!
ただし、このevent5と限らず、再起動すると「/dev/input/event0: WaveShare WaveShare」となりました。
$ evtest
No device specified, trying to scan all of /dev/input/event*
Not running as root, no devices may be available.
Available devices:
/dev/input/event0: WaveShare WaveShare
/dev/input/event1: CS-USB-IMX307: UVC Camera
/dev/input/event2: CS-USB-IMX307: UVC Camera
うーん、pygameもうちょっと勉強しないとわからないかも。
ffmpeg使ってカメラの画像をリアルタイムに画面に表示できるか
v4l2-ctl -v width=1920,height=1080,pixelformat=H264 --device /dev/video0
v4l2-ctl -p 30 --device /dev/video0
v4l2-ctl --device /dev/video0 --stream-mmap=0 --stream-to=- | ffmpeg -y -i pipe:0 -pix_fmt bgra -s 1280x400 -f fbdev /dev/fb0
出るけど、なんかぼやけてる。
ffmpegだけならどうか?
ffmpeg -y -i /dev/video0 -input_format h264 -pix_fmt bgra -s 1280x400 -f fbdev /dev/fb0
Input #0, video4linux2,v4l2, from '/dev/video0':
Duration: N/A, start: 6311.371615, bitrate: 110592 kb/s
Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 640x360, 110592 kb/s, 30 fps, 30 tbr, 1000k tbn
Stream mapping:
Stream #0:0 -> #0:0 (rawvideo (native) -> rawvideo (native))
Press [q] to stop, [?] for help
Output #0, fbdev, to '/dev/fb0':
Metadata:
encoder : Lavf59.16.100
Stream #0:0: Video: rawvideo (BGRA / 0x41524742), bgra(pc, gbr/unknown/unknown, progressive), 1280x400, q=2-31, 491520 kb/s, 30 fps, 30 tbn
Metadata:
encoder : Lavc59.18.100 rawvideo
-input_format h264を指定していますが、全く効果ない感じです。相変わらずおかしい動きです。
しかし、yuyv422 640x360の入力ならばパフォーマンスは十分、ほぼリアルタイムでカメラ画像を画面に表示出来ています。
どうも、設定が悪かったようです。この構文で h264にも反応するようになりました。-f v4l2 を明示的に指定すればOKです。
ffmpeg \
-f v4l2 -input_format h264 -video_size 640x360 -framerate 30 -i /dev/video0 \
-f fbdev -s 1280x400 -vf hflip -pix_fmt bgra /dev/fb0
ただし、h264 だと遅延が1秒くらいはある感じで、使い物になりません。
フォーマットをyuyv422にすればほとんど遅延なし。(このカメラのyuyv422対応解像度は640x360のみ)
ffmpeg \
-f v4l2 -input_format yuyv422 -video_size 640x360 -framerate 30 -i /dev/video0 \
-f fbdev -s 1280x400 -vf hflip -pix_fmt bgra /dev/fb0
解像度をmmpeg最大(1920x1080)にしてみる。
ffmpeg \
-f v4l2 -input_format mjpeg -video_size 1920x1080 -framerate 30 -i /dev/video0 \
-f fbdev -s 1280x400 -vf hflip -pix_fmt bgra /dev/fb0
これでは遅延と画面の崩れが酷く使い物にならない。
mmpegによるカメラのディスプレイ表示は、
- h264 640x360 遅延と画面の崩れ
- mmpeg 1920x1080 遅延と画面の崩れ
- yuyv422 640x360 遅延、画面の崩れもなく良好。
このような結果になった。
色々調べた結果、「Corrupt JPEG data: premature end of data segment」というエラーが起きている。
これは、処理能力が足りず、JPEGを受信出来ていないようだ。ラズパイの限界なのかもしれない。
ffmpeg -y -i /dev/video0 -input_format h264 -pix_fmt bgra -s 1280x400 -f fbdev /dev/fb0
しかし、実際には、カメラはドラレコの為に1920x1080、H.264で録画中なわけなので、録画しつつ画面に表示する方法を模索しないといけない。
どうしたものか?
OpenCVでカメラ画像を取得して画面に表示してはどうだろうか?
>>> import cv2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'cv2'
おっと、opencvがインストールされていないようだ。インストールしましょう。
pip install --upgrade pip
バージョン指定しないと、インストールに失敗しました。
この問題を回避するため、バージョン指定でインストールが必要です。
sudo pip3 --default-timeout=1000 install opencv-contrib-python==3.4.14.51
しかし、import cv2でエラーになります。
$ python3
Python 3.9.2 (default, Mar 12 2021, 04:06:34)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.9/dist-packages/cv2/__init__.py", line 5, in <module>
from .cv2 import *
ImportError: libhdf5_serial.so.103: cannot open shared object file: No such file or directory
依存関係ライブラリのインストール
$ sudo apt install libhdf5-dev libhdf5-serial-dev libhdf5-103
$ sudo apt install libatlas-base-dev
$ sudo apt install libjasper-dev
import cv2して確認してみる。
$ python3
Python 3.9.2 (default, Mar 12 2021, 04:06:34)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
RuntimeError: module compiled against API version 0xe but this version of numpy is 0xd
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.9/dist-packages/cv2/__init__.py", line 5, in <module>
from .cv2 import *
ImportError: numpy.core.multiarray failed to import
>>>
エラーになった。今度はnumpyのエラーだ。numpyをアップグレードする。
pip3 install numpy --upgrade
これでエラー解消された。
sudo apt install v4l2loopback-dkms
2つのデバイスを作成したい場合は次のようにします。
pi@raspberrypi:~ $ v4l2-ctl --list-devices
bcm2835-codec-decode (platform:bcm2835-codec):
/dev/video10
/dev/video11
/dev/video12
/dev/video18
/dev/video31
/dev/media2
bcm2835-isp (platform:bcm2835-isp):
/dev/video13
/dev/video14
/dev/video15
/dev/video16
/dev/video20
/dev/video21
/dev/video22
/dev/video23
/dev/media0
/dev/media1
CS-USB-IMX307: UVC Camera (usb-3f980000.usb-1.1):
/dev/video2
/dev/video3
/dev/media4
CS-USB-IMX307: UVC Camera (usb-3f980000.usb-1.4):
/dev/video0
/dev/video1
/dev/media3
42,43の2つの仮想デバイスを追加します。
pi@raspberrypi:~ $ sudo modprobe v4l2loopback video_nr=42,43
追加されたか確認します。
pi@raspberrypi:~ $ v4l2-ctl --list-devices
bcm2835-codec-decode (platform:bcm2835-codec):
/dev/video10
/dev/video11
/dev/video12
/dev/video18
/dev/video31
/dev/media2
bcm2835-isp (platform:bcm2835-isp):
/dev/video13
/dev/video14
/dev/video15
/dev/video16
/dev/video20
/dev/video21
/dev/video22
/dev/video23
/dev/media0
/dev/media1
Dummy video device (0x0000) (platform:v4l2loopback-000):
/dev/video42
Dummy video device (0x0001) (platform:v4l2loopback-001):
/dev/video43
CS-USB-IMX307: UVC Camera (usb-3f980000.usb-1.1):
/dev/video2
/dev/video3
/dev/media4
CS-USB-IMX307: UVC Camera (usb-3f980000.usb-1.4):
/dev/video0
/dev/video1
/dev/media3
追加されているのが確認できました。
あとは、再起動時にもこの設定で読み込まれるように設定しておきましょう。
echo "options v4l2loopback video_nr=42,43" | sudo tee -a /etc/modprobe.d/v4l2loopback.conf
echo v4l2loopback | sudo tee -a /etc/modules-load.d/modules.conf
sudo systemctl restart systemd-modules-load.service
sudo reboot
ffmpegを使って、データをvideo42, video43に流し込みます。
ffmpeg \
-f v4l2 -input_format h264 -video_size 1920x1080 -framerate 30 -i /dev/video0 \
-f v4l2 -c:v copy /dev/video42 -f v4l2 -c:v copy /dev/video43
video42からデータを取得して、画面に表示してみる。
ffmpeg \
-f v4l2 -i /dev/video42 \
-f fbdev -vf hflip -s 1280x400 -pix_fmt bgra /dev/fb0
できた。しかし、やはり、 h264 1920x1080 のデータはリアルタイム表示には向かない。カクカクしている。
同じく、video43をデータ保存してみる。
ffmpeg \
-y \
-f v4l2 -i /dev/video43 \
-c:v copy testshot.mp4
出来た。
高解像度 → カクカクしてディスプレイ表示は出来ない。
低解像度 → ディスプレイ表示OK。しかしドラレコの録画としては画質が低すぎて事故の際の参考にしかならない。(もしもの時に相手のナンバーとか判別つかない)
うーん困った。高解像度で保存しつつ、ディスプレイ表示出来る方法を模索しなければならない。
案1:もう一台カメラを装着すれば万事解決。
案2:ffmpgeではなくPythonで自前のコードを書いてみたらどうか?
video42とvideo43に流す解像度をかえられないか。かえられる。しかし、高解像度だと間に合わない。
ffmpeg \
-f v4l2 -input_format mjpeg -video_size 1920x1080 -framerate 30 -i /dev/video0 \
-f v4l2 -c:v mjpeg -s 640x360 /dev/video42 \
-f v4l2 -c:v copy /dev/video43
1280x720ではどうか。
h264はやはり画像がぼやける。
ffmpeg \
-f v4l2 -input_format h264 -video_size 1280x720 -framerate 30 -i /dev/video0 \
-f v4l2 -c:v copy /dev/video42 \
-f v4l2 -c:v copy /dev/video43
mjpegは優秀だがたまに画面が崩れる。
ffmpeg \
-f v4l2 -input_format mjpeg -video_size 1280x720 -framerate 30 -i /dev/video0 \
-f v4l2 -c:v copy /dev/video42 \
-f v4l2 -c:v copy /dev/video43
ffmpeg \
-f v4l2 -input_format mjpeg -video_size 1280x720 -framerate 30 -i /dev/video2 \
-f v4l2 -c:v copy /dev/video42 \
-f v4l2 -c:v copy /dev/video43
色々試して、一番安定しているのがこれ。
mjpegで1280x720に解像度を抑えめにする。
ffmpeg \
-re -f v4l2 -input_format mjpeg -video_size 1280x720 -framerate 30 -i /dev/video0 \
-f v4l2 -c:v copy /dev/video42 \
-f v4l2 -c:v copy /dev/video43
電子バックミラー側は、hflip, fps=15をつける。
- hflipは左右反転して鏡像をつくる。
- ミラーとしてはfps=15くらいで十分
ffmpeg \
-f v4l2 -i /dev/video42 \
-f fbdev -vf "hflip, fps=15" -pix_fmt bgra /dev/fb0
これで、だいぶ落ち着く。ただしmjpegエラーが発生する。
[mjpeg @ 0x1e77890] EOI missing, emulating
[mjpeg @ 0x1e77890] mjpeg_decode_dc: bad vlc: 0:0 (0x1e78f00)
[mjpeg @ 0x1e77890] error dc
[mjpeg @ 0x1e77890] error y=42 x=68
[mjpeg @ 0x1e77890] overread 8
あとは上記エラーがなくなれば実用レベルになりそうなのだが・・・
他のカメラだとどうだろうか。同じくmjpeg 1280x720 30fps出せるはずの、BUFFALO BSWHD06M USB Cameraを繋いで動作確認してみる。
あれ。こちらは一切エラーも起きないでとても安定動作している。うーん、AliExpressで買ったカメラに問題あるのかも。安いカメラだったからなぁ・・・
色々ためそう。今度はv4l2経由の1280x720
v4l2-ctl -v width=1280,height=720,pixelformat=MJPEG --device /dev/video0
v4l2-ctl -p 30 --device /dev/video0
v4l2-ctl --device /dev/video0 --stream-mmap=3 --stream-to=- | ffmpeg -y -i pipe:0 -pix_fmt bgra -s 1280x400 -f fbdev /dev/fb0
これもダメでした。
640x360ならどうか?
ffmpeg \
-re -f v4l2 -input_format mjpeg -video_size 640x360 -framerate 30 -i /dev/video0 \
-f v4l2 -c:v copy /dev/video42 \
-f v4l2 -c:v copy /dev/video43
ffmpeg \
-f v4l2 -i /dev/video42 \
-f fbdev -vf "hflip, fps=15" -pix_fmt bgra /dev/fb0
撮影した動画をコンソールで確認
ffmpeg -re -i out.mp4 -c:v rawvideo -pix_fmt bgra -f fbdev /dev/fb0
これもうまくいかない。同じように画面が時折崩れる。
そこで、このカメラを、パソコンに繋いでみよう。
つまり、PCでは大丈夫だが、ラズパイだと崩れる。ラズパイでも解像度が低ければ崩れない。
これらから、単純に「ラズパイのCPU・GPUでは、能力的にカメラの高解像度データをリアルタイムに画面に表示できない」と結論付ける事ができる。
というわけで、
「ラズパイゼロ2で、電子バックミラー(高解像度)は無理、解像度落とせばOK」
という結果となりました。
ただ、ドラレコとしてはバックモニターの解像度を落とすわけにはいかないです。もしもの事故の時の証拠がメインの目的であり、電子バックミラーはオマケ機能なのです。
電子バックミラーの選択肢としては
- PCクラスのCPUを用意する。
- 電子バックミラー用にもう1台カメラ用意する → 割と現実的かも。ただし3台のカメラの通信量耐えられるか心配ではある。
- ちらつくけど我慢して使う。→ バックミラーとして致命的なので無理。
- 潔く諦める。
今回は、ドラレコ作成、バックミラーは目的ではありませんので、一旦見送ってもいいかな。
複数のUSBカメラがある場合、どれが後方カメラなのか判断つかない問題
複数のUSBカメラをさした場合、指した順番でvideo0, video2に割り振られる。
再起動すると、video0, video2が入れ替わる可能性もある。
どちらが前方カメラで、どちらが後方カメラなのかを認識して、後方カメラをディスプレイに映したい(バックミラーなので)
そこで下記の記事を参考に、後方カメラを判定する。
dev.classmethod.jp
私の環境ではデバイス名の箇所がそのままでは動作しなかったので修正して使用している。
vim usbVideoDevice.py
import subprocess
class UsbVideoDevice():
def __init__(self):
self.__deviceList = []
try:
cmd = 'ls -la /dev/v4l/by-id'
res = subprocess.check_output(cmd.split())
by_id = res.decode()
except:
return
try:
cmd = 'ls -la /dev/v4l/by-path'
res = subprocess.check_output(cmd.split())
by_path = res.decode()
except:
return
# ポート番号取得
for line in by_path.split('\n'):
if('usb-0' in line):
tmp = self.__split(line, '-usb-0:1.')
tmp = self.__split(tmp[1], ':')
port = int(tmp[0])
tmp = self.__split(tmp[1], '../../video')
deviceId = int(tmp[1])
if deviceId % 2 == 0:
#name = deviceNames[str(deviceId)]
self.__deviceList.append((deviceId , port))
def __split(self, str, val):
tmp = str.split(val)
if('' in tmp):
tmp.remove('')
return tmp
# 認識しているVideoデバイスの一覧を表示する
def disp(self):
for (deviceId, port) in self.__deviceList:
print("/dev/video{} port:{}".format(deviceId, port))
# ポート番号(1..)を指定してVideoIDを取得する
def getId(self, port):
for (deviceId, p) in self.__deviceList:
if(p == port):
return deviceId
return -1
実行してみる。
$ python3
Python 3.9.2 (default, Mar 12 2021, 04:06:34)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from usbVideoDevice import UsbVideoDevice
>>> usbVideoDevice = UsbVideoDevice()
>>> usbVideoDevice.disp()
/dev/video2 port:1
/dev/video0 port:4
>>> print(usbVideoDevice.getId(1))
2
>>> print(usbVideoDevice.getId(4))
0
>>>
getIdでポート番号(USBの刺さっている場所)を渡せば、今そのポート番号に接続されているカメラの番号を返してくれている。
これで、ポート番号でカメラ指定出来るようになった。
今日はここまでです。