【ドラレコ】Raspberry pi Zere2 W に繋いだ2個のカメラを安定させる【スマートバイク】

カメラ2個を安定させるにはセルフパワーのUSBハブにする

ラズパイでバイク用ドラレコを作ろうと試行錯誤しています。

これまでの調査で、前後2台のカメラでフルHD画像を保存することが出来るようになりました。

一日録画して安定性を見ているのですが、片方のカメラがエラーとなり撮影できない事が多いです。

Mar 23 11:07:42 raspberrypi dashcam[836]: [video4linux2,v4l2 @ 0x3be5310] ioctl(VIDIOC_DQBUF): No such device
Mar 23 11:07:42 raspberrypi dashcam[836]:     Last message repeated 1 times
Mar 23 11:07:42 raspberrypi dashcam[836]: /dev/video2: No such device

特に、USBケーブルを延長しているとよく発生するような気がします。

原因として考えられるのが、カメラの電力不足です。バスパワーのUSBハブ(青い方)を使っていたのですが、これをセルフパワーのUSBハブ(白い方)に変更してみます。これでカメラへの電力供給不足が解消され、カメラのエラーが発生しなくなれば、電力問題が原因だった事になりますね。

セルフパワーのUSBハブに変更してから、エラーはなくなりました!

USBケーブルを延長しているのは関係なく、単に電力不足だったようです。Raspberry PiでUSB機器を使う場合は電力供給が問題になりますね。

【ドラレコ】マイクを付ける【スマートバイク】

もしもの時には音も重要

小型のマイクを取り付けて、音も録音出来るようにしたいと思います。

SPH0645LM4H搭載 I2S MEMSマイクモジュール

このモジュールはモノラルですが、小さいためバイクに収めるのも楽そうです。ドラレコの音声としては事故発生時の記録として衝撃音などが録音されていれば十分と考えていますのでこういった安価で小さなマイクモジュールで十分かなと思います。

www.adafruit.com

ラズパイに配線しましょう。下記に配線とドライバインストール方法が詳しく記載されています。

learn.adafruit.com

ドライバのインストールのためカーネルのビルドが必要なようです。インストールスクリプトが用意されているのでそれに従ってインストールを実施します。

インストールが完了したら、後日アップデートしたときに標準カーネルへと戻ってしまわないように、

$ sudo apt-mark hold raspberrypi-kernel
$ sudo apt-mark hold firmware*

を実行して、勝手にカーネルがバージョンUPされないようにしておきます。

録画と録音を同時に行う

ffmpegで動画を撮影しつつ、録音する設定にしていきましょう。

このサイト、

I2S Mems microphone & FFMPEG RTP Settings [Solved] - Raspberry Pi Forums

が参考になりました。

最終的に、次のようなコマンドで録画と録音ができました。

ffmpeg \
-use_wallclock_as_timestamps 1 \
-rtbufsize 30M \
-itsoffset -2.01 \
-f alsa -ac 2 -ar 8000 -acodec pcm_s32le -thread_queue_size 256 -i dmic_sv \
-f v4l2 -input_format h264 -video_size 1920x1080 -framerate 30  -thread_queue_size 256  \
-i /dev/video0 -c:v copy \
-acodec aac -ar 8000 -b:a 128k -vol 512 \
-f segment -strftime 1 -segment_time 60 \
-reset_timestamps 1 \
/media/usb0/front_%Y-%m-%d_%H-%M-%S.avi

今までの録画設定にオーディオ関係のオプションを追加しています。

-ac 2 

チャンネルは2に設定します。 ボリュームを上げるため、また音声デバイス指定をdmic_svという文字列で行うために、

Raspberry Pi Wiring & Test | Adafruit I2S MEMS Microphone Breakout | Adafruit Learning System

を実施しています。これを実施するとモノラルでもチャンネル設定が2になります。

-vol <n>

ボリュームを設定します。nは256単位で設定します。元のボリュームが少し小さめだったので200%を指定しています。

-vol 256  = 100%
-vol 512  = 200%
-vol 768  = 300%
-vol 1024 = 400%
-vol 2048 = 800%
-f <alsa> 

音声入力をに設定します。

-ar <rate>

音声のサンプリングレートを指定します。音質を表します。

-acodec pcm_s32le

入力音声のコーディックを指定します。このマイクはS32LEですので、これを指定します。指定しないとエラーになってしまいます。

-b:a:音声のビットレート(bitrate:audio)

音声のビットレートを指定するコマンドです。音量を何段階で表すかを表します。一般的では-b:a 192k (192kbps)と指定します。

-i dmic_sv

音声デバイスを指定します。普通は、hw:0とか、hw:1という風に割り当てられている番号で指定するのですが、ディスプレイを繋いでいると、HDMIディスプレイは音声出力を持っているためhw:0が割り振られ、マイクモジュールがhw:1になります。ディスプレイを繋いでいないと、マイクモジュールにhw:0が割り振られます。このように、接続デバイス数によって番号が変化するので不便ですので、先の手順で.asoundrcファイル内で付けた名前である、dmic_svを使用して接続します。

なお、ユーザ設定の場合は~/.asoundrcファイルに、システム全体に反映させる場合は/etc/asound.confに設定します。

後方カメラに音声を追加することにしました。現在のスクリプトはこんな感じです。

#!/usr/bin/env python3

from usbVideoDevice import UsbVideoDevice  # https://smartphone-zine.hatenablog.com/entry/2023/02/25/065957
import subprocess
import psutil  # フォルダ残量確認
import os  # ファイル操作に使う
import sys  # プログラムを途中で終了させるのに使う
from operator import itemgetter  # イテラブルから任意の要素を抜き出す
import signal  # 非同期イベントにハンドラを設定する
import threading
import time  # スリープ用


# データを保存するフォルダ EX) folder = '/media/usb0/'
folder = '/media/usb0/'

# 保存形式
extension = ".avi"

# デスク使用率がこの%を超えたら古いファイルを削除
dsk_usage_ratio = 90.0

# 前方カメラのUSBポート番号と画像サイズ
front_cam_port = 1
front_cam_size = '1920x1080'

# 後方カメラのUSBポート番号
rear_cam_port = 4
rear_cam_size = '1280x720'

video_front_p = None  # 録画用のプロセスです
video_rear_p = None  # 録画用のプロセスです

# カメラのVideoポート判定
usbVideoDevice = UsbVideoDevice()
video_front = "/dev/video" + str(usbVideoDevice.getId(front_cam_port))
video_rear = "/dev/video" + str(usbVideoDevice.getId(rear_cam_port))


# Ctrl+C or KILL で止められた時の処理
def sig_handler(signum, frame) -> None:
    print('\n\n\n------------sig_handler()\n\n\n')
    global video_front_p
    global video_rear_p

    if not video_front_p is None:
        #video_front_p.terminate()
        video_front_p.kill()

    if not video_rear_p is None:
        #video_rear_p.terminate()
        video_rear_p.kill()

    sys.exit(1)  # プログラムを途中で終了させる


# 録画処理
def record():
    global video_front_p
    global video_rear_p
    video_front_p = subprocess.Popen(
        ['ffmpeg',
         '-use_wallclock_as_timestamps', '1',
         '-rtbufsize', '300M',
         '-f', 'v4l2', '-input_format', 'h264', '-video_size', front_cam_size, '-framerate', '30',
         '-i', video_front, '-c:v', 'copy',
         '-f', 'segment', '-strftime', '1', '-segment_time', '60',
         '-reset_timestamps', '1',
         folder + '%Y-%m-%d_%H-%M-%S_f' + extension])
    video_rear_p = subprocess.Popen(
        ['ffmpeg',
         '-use_wallclock_as_timestamps', '1',
         '-rtbufsize', '30M',
         '-itsoffset', '-1.90',
         '-f', 'alsa', '-ac', '2', '-ar', '8000', '-acodec', 'pcm_s32le', '-thread_queue_size', '256', '-i', 'dmic_sv',
         '-f', 'v4l2', '-input_format', 'h264', '-video_size', rear_cam_size, '-framerate', '30',
         '-i', video_rear, '-c:v', 'copy',
         '-acodec', 'aac', '-ar', '8000', '-b:a', '128k', '-vol', '512',
         '-f', 'segment', '-strftime', '1', '-segment_time', '60',
         '-reset_timestamps', '1',
         folder + '%Y-%m-%d_%H-%M-%S_r' + extension])

def diskfree():
    file_list = []
    # ディスク使用率を取得
    dsk = psutil.disk_usage(folder)
    print('\n------------usage:' + str(dsk.percent))
    if dsk.percent <= dsk_usage_ratio:
        return

    # ファイルデータ取得
    for file in os.listdir(folder):
        file_info = os.stat(folder + file)
        file_list.append([folder + file, file_info.st_mtime, int(file_info.st_size / 1024)])

    # 古い順にソート
    file_list.sort(key=itemgetter(1))

    # ファイルの削除
    for file in file_list:
        if dsk.percent > dsk_usage_ratio:
            print('\n------------[DELETE]' + file[0])
            os.remove(file[0])
            dsk = psutil.disk_usage(folder)
            print('\n------------usage:' + str(dsk.percent))
        else:
            break


def main():
    # スクリプトが止められたらプロセスを停止する
    signal.signal(signal.SIGTERM, sig_handler) # KILLされたとき
    signal.signal(signal.SIGINT, sig_handler) # Ctrl+Cされたとき

    # 録画開始、メインスレッド
    record()

    while True:
        # ディスクが使用率を超えたら古いファイルを削除する
        thread = threading.Thread(target=diskfree)
        thread.start()

        # 暫く待つ
        time.sleep(60)


if __name__ == '__main__':
    main()

今日はここまでです。

【ヤマハFZS25】GIVIじゃなくても十分!モトボワットBBアッパーラック付きを取り付けてみました!

バイクで困るのが荷物が載らないこと

バイクの積載量って本当に少ないですよね。

今までずっと車での移動だったのですがバイクでの移動に変わって何が不便かというとやはり荷物が運べない事が一番不便だなと思います。

私の場合はFZS25を普段の足としても使いたいので、バイクを購入する前からリアボックスは必須だと思ってました。

youtu.be

GIVIはちょっとお高い

ほしいなと思っていたのはやはり定番のGIVI。ちょっと大きめの47Lのタイプを狙っていました。

GIVI

公式サイトはこちら。

GIVI

定価で、52800円!ほしいけど高いんですよねー!(ちょうどこの記事を書いている今はアマゾンの特選タイムセールでちょっとお安くなってるみたいですね。)

安いリアボックスを発見!!

そこで安いものがないか見てたところ、「モトボワットリアボックス 47L」というのを見つけました!なんといっても安いです!定価で12,820円です!(ちょうど今はセール中で、8760円になってました!なんとGIVI V47NNの6分の1の値段で買えちゃいます!)

モトボワットBB バイク トップケース・リアボックス BB47ADV モトボワットリアボックス 47L

このモトボワットBBリアボックスが良さそう!!

Amazonレビューを見ても悪い評価は少ないので、まあ問題ないだろうということでこの製品に決定です!

アッパーラック付きのタイプも有りまして、結局こちらを購入しました。

このアッパーラックにはとても期待しています。小型なテントやシュラフならばネットを使って簡単に固定することができますね!47Lの大容量+このアッパーラックがあれば、宿泊ツーリングでも大活躍間違いなしです!

今はまだ寒いですが(取り付けたのは2月です)、もう少し暖かくなったらキャンプツーリングにも行きたいですね。

モトボワットBB バイク トップケース・リアボックス BB47ADV-XT モトボワットリアボックス 47L+アッパーラック

購入しました!

というわけでAmazonで購入し、無事届いたので早速取り付けてみたいとおもいます。

こうやってみるとやはり結構大きなサイズですね。

フルフェイスが2個まで問題無く収納できる大容量のリアボックスです。

開封の儀

早速開けてみましょう。

慎重にカッターで開封していきます。

中には、しっかりとした分厚い梱包材の下から、リアボック本体が現れました。

やっぱり47リットルはなかなか大きいですね。

鍵は左に回して解錠します。蓋を開けてみます。

うん、やっぱり内部も広々していて十分な広さです。

中には使用上の注意が入っていますが、これは説明書ではないです。注意事項が書いてあるだけです。

説明書はこれだけ。取り付け方とか一切ありません!いい感じに取り付けろってことでしょう。

あとは、黄色い反射ステッカーが付属します。でもいかにも反射材ですって色なのであまり気に入らないんですよね。

ベースプレートはこんな感じでいっぱい穴が空いてます。

内容物はこれですべてです。

これをバイクのリアキャリアに取り付けていくわけです。まずはどんな感じでつくのか、試してみましょう。

2箇所の爪を引っ掛けてから、カチッとはめ込むようです。

では、実際にキャリアに載せてみましょう。この沢山ある穴の中から、リアキャリアの形状にあわせて最適な穴を選択、

するんだと思います。たぶんこの波型の金具が、キャリアの下側に入って、 四角いプレートを、穴の中にはめ込んで、ナットとネジで固定していく、ようです。

ナットはちゃんとロックナットになっていますね、ナイロン製のインサートが入ったタイプです。

振動などによる緩みを大幅に軽減してくれるタイプですね。

モトボワットBB取り付けレビュー

さて試行錯誤しながら、位置を決めていきます。これば正解だという場所はないのでしょうが、しっかり固定できているなら問題ないと思います。

まずは仮止めです。とりあえず、2箇所は前後にずれないように考慮して位置決めしていきました。

それから残り2箇所は、左右にずれないように考慮して位置決めしていきます。

さて、ここから試行錯誤すること約20分。

パズルのようにああでもない、こうでもないと、取り付けては外し、外しては取り付けて、ガッチリ固定出来る場所を探っていきます。

最終的にはこの位置で落ち着きました。

下から見るとこうなっています。しっかり固定できているようです。

あとは、蓋を5本のネジで止めてしまえばOKです。ここはあまりキツく締めなくても大丈夫ですね。

最後に、リアボックを取り付けます。しっかりとはまっているか確認します。

開けると、こんな感じ、アッパーラックがシートにあたってちょうど良い感じに平行に蓋が開いています。荷物整理のときには蓋の部分に物をおけるので、積み込み作業が捗りそうですね。

完成!そして近所の公園まで初走行です!

というわけで、リアボックス装着しての初走行です!

ちなみに、付属していた、蛍光色のイエローの反射テープはこちら。

この蛍光色がちょっと好みではなかったので、蛍光色ではない普通の黄色の反射テープを購入、貼り付けています。車体のオレンジと違和感なくマッチしているので、この黄色のほうが好みです。

GIVIもデザイン的には流線型でスタイリッシュ、とてもかっこいいのでスポーツタイプに似合いそうです。でも荷物を載せるのにはちょっと不便ですね。「モトボワットBB」は無骨な四角いやつですが逆にストリートファイターのFZS25にはマッチしていて、ちょっとツアラーモデルっぽくなって気に入っています。四角いのでスペースを隅まで広々と使えて大ききなサイズの荷物も格納できるので、これから重宝しそうです。

以上、モトボワットリアボックス 47Lの取り付けレビューでした!

【ドラレコ】Raspberry pi Zere2 W を起動したら、前方カメラ、後方カメラの録画を開始する【スマートバイク】

前後カメラの録画プログラムを考える

ドラレコの要件としては、

  1. 前後カメラの録画
  2. 録画は最高画質で行う(もしもの時の資料)
  3. コーディックはH264(データ量が少ないので長い時間記録を残せる)
  4. 録画データは1分毎に別ファイルにする。
  5. メモリが一杯になったら、古いファイルから消していく
  6. 本体とは別のSDカードに記録する。SDカードは差し替え可能であること。

となります。

1. 前後カメラの録画は、カメラ2台用意したのでOK。Raspberry pi Zero 2 W次第ですが、パフォーマンス的にもなんとかなりそうです。

2. 録画は最高画質で行うも、大丈夫かな?ここもRaspberry pi Zero 2 W次第な部分ですね。とりあえずプログラム中で解像度を切り替え出来るようにしときましょう。

3. コーディックはH264は、カメラのハードウェアでH264にしてくれるので、2台のカメラともH264でできそう

4. 録画データは1分毎に別ファイルは、ffmpegの機能で出来ます。

5番目の要件「SDカードのメモリが一杯になったら古いファイル消す」の部分はプログラムを書かないと実現できなさそうです。これが一番大変かも?

でははずは、6. 本体とは別のSDカードに記録するから対応していきましょう。

動画保存用のSDカードをどうするか?

まずは、

6. 本体とは別のSDカードに記録する。SDカードは差し替え可能であること。

を行うために、SDカードリーダーとSDカードを用意します。

ドラレコに使用するSDカードは、消耗品です。常に古いデータを消して新しいデータを書き込み続けるのですが、そもそもSDカードやUSBメモリもですが、これらは書き換え可能回数はきまっていて、書き換えているとそのうち書き込めなくなります。MLCチップを使ったものが書き換え可能回数が多いので、ドラレコにはMLCチップのmicroSDカードを選択します。 万全を期すなら、SDカードは1年ぐらいで交換してあげるのが良いらしいです。

簡単に交換できるように、ラズパイのOSが入っているSDカードとは別に、SDカードを用意することにします。

USBハブにカードリーダーを入れます。製品はコレにしました。

耐久性の高いMLCのSDカードとして、これを選択しました。

USBメモリという手もあるのですが、通常ドラレコmicroSDを採用しておりSDカードの方が入手しやすいと思うので、SDカードリーダーとSDカードという構成としました。

動画で見るとわかりやすいですが物凄くピッタリ収まってます。 【ドラレコ】microSDリーダーを用意する - YouTube

USBメモリの自動マウント

SDカードが認識されているか確認

sudo blkid /dev/sda1

中略
Device     Boot Start       End   Sectors  Size Id Type
/dev/sda1       32768 249526271 249493504  119G  7 HPFS/NTFS/exFAT

/dev/sda1 として認識されていますね。

USBメモリバイスの情報を表示します。

sudo blkid /dev/sda1

/dev/sda1: UUID="9C33-6BBD" BLOCK_SIZE="512" TYPE="exfat"

あとでマウントするためにUUIDとTYPEの情報を使用します。

マウントするフォルダを作成します。

sudo mkdir /mnt/usb1

/etc/fstabを編集します。 UUID="9C33-6BBD...の1行を追加して、リブートします。自動でマウントされていれば、成功です。

sudo vim /etc/fstab

UUID="9C33-6BBD"  /mnt/usb1             exfat    defaults,noatime,nofail  0       0       #←この行を追加

再起動したらdfコマンドでマウントされたか確認します。

pi@raspberrypi:~ $ df
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/root       14900440 9836872   4424900  69% /
devtmpfs          185492       0    185492   0% /dev
tmpfs             218772       0    218772   0% /dev/shm
tmpfs              87512     992     86520   2% /run
tmpfs               5120       4      5116   1% /run/lock
/dev/mmcblk0p1    261108   52102    209006  20% /boot
/dev/sda1      124730368     384 124729984   1% /mnt/usb1
tmpfs              43752       0     43752   0% /run/user/1000

ただし、この方法だと、新しいメモリカードを差し替えるたびに、この作業をしないといけません。

なぜなら、UUIDはメモリカード毎に異なるからです。これは不便すぎですね。不採用です。

/etc/fstabを編集して元に戻すことにしました。

UUID="9C33-6BBD...の1行をコメントアウトしてリブートします。

作成したフォルダも消しておきます。

sudo rmdir /mnt/usb1

usbmountで自動マウント

SDカードを別のものにするたびにfstabを編集するのは不便なので、差し替えて再起動すれば自動マウントしてくれるように構成しましょう。

usbmountというパッケージを使うと手軽に自動マウントできるようになります。

インストール及び自動起動設定

sudo apt install usbmount -y
sudo mkdir /etc/systemd/system/systemd-udevd.service.d

設定ファイルをつくます。

sudo vim /etc/systemd/system/systemd-udevd.service.d/00-my-custom-mountflags.conf
[Service]
PrivateMounts=no

保存して再起動。dfでマウントされていることを確認します。

パーテーションを2つ持つSDカードだと、usb0,usb1としてそれぞれのパーティションがマウントされているのがわかります。

$ df
Filesystem     1K-blocks    Used Available Use% Mounted on
省略
/dev/sda1         261108   52108    209000  20% /media/usb0
/dev/sda2       14585536 3782972  10151892  28% /media/usb1
省略

電源をOFFし、SDカードを入れ替えてみます。今度は1パーテーションのみのSDカードに差し替えました。

フォーマットはXFSにしておく

XFSならバッファーオーバーランエラーが発生しないらしい。 Ubuntuのパソコンを用意して、SDカードをXFSでフォーマットします。UbuntuPCにSDカードを差し込みます。

Ubuntuにはgparted をインストールしておきます。このツールでフォーマットを行います。

xfs フォーマットするためにxfsprogsパッケージをインストールしておきます。

設定ファイルを確認します。

sudo vim /etc/usbmount/usbmount.conf

FILESYSTEMS= の行を探して、xfsがなければ追記します。

FILESYSTEMS="vfat ext2 ext3 ext4 hfsplus exfat xfs"

FS_MOUNTOPTIONS= の行を探して、次のように設定します。これをしておかないと、いちいちrootにならないと書き込み出来ないためです。

#FS_MOUNTOPTIONS=""
FS_MOUNTOPTIONS="-fstype=xfs,iocharset=utf8,codepage=932,uid=500,gid=500,dmask=000,fmask=011"

保存して再起動します。再起動したらdfで確認します。

pi@raspberrypi:~ $ df
Filesystem     1K-blocks    Used Available Use% Mounted on
省略
/dev/sda1      124730368     384 124729984   1% /media/usb0

無事ラズパイでSDカードを自動マウント出来るようになりました。

書き込みできるか確認します

pi@raspberrypi:/media/usb0 $ cd /media/usb0
pi@raspberrypi:/media/usb0 $ touch hoge
pi@raspberrypi:/media/usb0 $ ls
hoge
pi@raspberrypi:/media/usb0 $ rm hoge
pi@raspberrypi:/media/usb0 $ ls
pi@raspberrypi:/media/usb0 $ 

できました。

プログラム作る。

それでは、本題です。「2台のカメラでH264最高画質で録画し、1分毎に動画ファイルを作る」というプログラムを作っていきます。

ffmpegでファイル分割

次のように設定すると、60秒毎に1ファイルのファイルに分割してくれます。これを使えばファイル分割の要件もクリアですね。

まずは、最高画質 h264 mp4形式で1分ごとに保存します。

ffmpeg -rtbufsize 30M \
-f v4l2 -input_format h264 -video_size 1920x1080 -framerate 30 \
-i /dev/video0 -c:v copy \
-f segment -strftime 1 -segment_time 60 \
-segment_format_options movflags=+faststart -segment_format mp4 \
-reset_timestamps 1 \
/media/usb0/front_%Y-%m-%d_%H-%M-%S.mp4

ファイルの頭が途切れる。困った。aviにしてみようか。

最高画質 h264 avi形式で1分ごとに保存します。1280x720

ffmpeg -rtbufsize 300M \
-use_wallclock_as_timestamps 1 \
-f v4l2 -input_format h264 -video_size 1920x1080 -framerate 30 \
-i /dev/video2 -c:v h264_v4l2m2m \
-f segment -strftime 1 -segment_time 60 \
-reset_timestamps 1 \
-pix_fmt yuv420p \
/media/usb0/rear_%Y-%m-%d_%H-%M-%S.avi

よしよし、aviは良いみたいです。

h264も試してみる。うまく分割されるかな?

ffmpeg \
-f v4l2 -input_format h264 -video_size 1920x1080 -framerate 30 \
-i /dev/video0 -c:v copy \
-f segment -strftime 1 -segment_time 60 \
-reset_timestamps 1 \
/media/usb0/front_%Y-%m-%d_%H-%M-%S.h264

生の画像データをそのまま格納するだけなので、一番安定している。ただ一番再生が大変そう。

mp4はファイルの切替時に崩れる事が多い感じだが、aviは安定している。.h264は再生ソフトがないと再生できない。ファイルフォーマットはaviで決定ですね。

では、お手軽にpythonスクリプトを組んで行きましょう。

ファイル名はdashcam.pyとします。dashcam=英語でドラレコの事だそうです。ダッシュボードに置くカメラだからかな?

#!/usr/bin/env python3

from usbVideoDevice import UsbVideoDevice  # https://smartphone-zine.hatenablog.com/entry/2023/02/25/065957
import subprocess
import psutil  # フォルダ残量確認
import os  # ファイル操作に使う
import sys  # プログラムを途中で終了させるのに使う
from operator import itemgetter  # イテラブルから任意の要素を抜き出す
import signal  # 非同期イベントにハンドラを設定する
import threading
import time  # スリープ用


# データを保存するフォルダ EX) folder = '/media/usb0/'
folder = '/media/usb0/'

# 保存形式
extension = ".avi"

# デスク使用率がこの%を超えたら古いファイルを削除
dsk_usage_ratio = 90.0

# 前方カメラのUSBポート番号と画像サイズ
front_cam_port = 1
front_cam_size = '1920x1080'

# 後方カメラのUSBポート番号
rear_cam_port = 4
rear_cam_size = '1280x720'

video_front_p = None  # 録画用のプロセスです
video_rear_p = None  # 録画用のプロセスです

# カメラのVideoポート判定
usbVideoDevice = UsbVideoDevice()
video_front = "/dev/video" + str(usbVideoDevice.getId(front_cam_port))
video_rear = "/dev/video" + str(usbVideoDevice.getId(rear_cam_port))


# Ctrl+C or KILL で止められた時の処理
def sig_handler(signum, frame) -> None:
    print('\n\n\n------------sig_handler()\n\n\n')
    global video_front_p
    global video_rear_p

    if not video_front_p is None:
        #video_front_p.terminate()
        video_front_p.kill()

    if not video_rear_p is None:
        #video_rear_p.terminate()
        video_rear_p.kill()

    sys.exit(1)  # プログラムを途中で終了させる


# 録画処理
def record():
    global video_front_p
    global video_rear_p
    video_front_p = subprocess.Popen(
        ['ffmpeg',
         '-rtbufsize', '300M',
         '-f', 'v4l2', '-input_format', 'h264', '-video_size', front_cam_size, '-framerate', '30',
         '-i', video_front, '-c:v', 'copy',
         '-f', 'segment', '-strftime', '1', '-segment_time', '60',
         '-reset_timestamps', '1',
         folder + '%Y-%m-%d_%H-%M-%S_f' + extension])
    video_rear_p = subprocess.Popen(
        ['ffmpeg',
         '-rtbufsize', '30M',
         '-f', 'v4l2', '-input_format', 'h264', '-video_size', rear_cam_size, '-framerate', '30',
         '-i', video_rear, '-c:v', 'copy',
         '-f', 'segment', '-strftime', '1', '-segment_time', '60',
         '-reset_timestamps', '1',
         folder + '%Y-%m-%d_%H-%M-%S_r' + extension])

def diskfree():
    file_list = []
    # ディスク使用率を取得
    dsk = psutil.disk_usage(folder)
    print('\n------------usage:' + str(dsk.percent))
    if dsk.percent <= dsk_usage_ratio:
        return

    # ファイルデータ取得
    for file in os.listdir(folder):
        file_info = os.stat(folder + file)
        file_list.append([folder + file, file_info.st_mtime, int(file_info.st_size / 1024)])

    # 古い順にソート
    file_list.sort(key=itemgetter(1))

    # ファイルの削除
    for file in file_list:
        if dsk.percent > dsk_usage_ratio:
            print('\n------------[DELETE]' + file[0])
            os.remove(file[0])
            dsk = psutil.disk_usage(folder)
            print('\n------------usage:' + str(dsk.percent))
        else:
            break


def main():
    # スクリプトが止められたらプロセスを停止する
    signal.signal(signal.SIGTERM, sig_handler) # KILLされたとき
    signal.signal(signal.SIGINT, sig_handler) # Ctrl+Cされたとき

    # 録画開始、メインスレッド
    record()

    while True:
        # ディスクが使用率を超えたら古いファイルを削除する
        thread = threading.Thread(target=diskfree)
        thread.start()

        # 暫く待つ
        time.sleep(60)


if __name__ == '__main__':
    main()

実行可能ファイルにする

chmod 755 dashcam.py

実行してみる

./dashcam.py

さてここでまた問題発生、前後とも1920x1080で同時に撮影すると処理が追いつかず片方のカメラがfpsが5くらいまで落ちてしまう。

仕方ありません。泣く泣く、後方のカメラを1280x720に変更します。これで安定して30fps出ています。流石にラズパイZero2Wの限界でしょうか?USBカメラ2台ともフルHD録画とsdカード保存はゼロには荷が重いようです。

ただ、ラズパイZero2WでH264で前後カメラで録画出来るのは快挙ですよね!

ラズパイ起動時にドラレコの録画を開始する。

スクリプトも完成しましたので、サービス登録して起動時にスクリプトを起動するように設定しましょう。dashcam.serviceという名前のユニットファイルを作成します。

vim dashcam.service

次の内容で保存します。

# dash cam unit file
[Unit]
Description=dashcam
# 保存先USBメディアが使用可能になるのを待つ
RequiresMountsFor=/media/usb0

[Service]
# Type=simpleはコマンドを実行したタイミングで起動完了と判断します
Type=simple

# 作業ディレクトリ指定
WorkingDirectory=/home/pi/

# サービスの起動コマンド
ExecStart=/home/pi/dashcam.py

# Pythonのprint 関数の内容をログに残す設定
Environment=PYTHONUNBUFFERED=1

# 停止完了までに待機する時間
TimeoutStopSec=5

# /bin/sleepを使うと起動時にWaitかけることができる
ExecStartPre=/bin/sleep 5

# ログ出力関連
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=dashcam

[Install]
WantedBy = multi-user.target

SyslogIdentifier=dashcam の名前を使って使ってログを設定。

sudo vim /etc/rsyslog.d/dashcam.conf
if $programname == 'dashcam' then {
    action(type="omfile" file="/var/log/dashcam.log")
}

syslog再起動

sudo systemctl restart rsyslog

ユニットファイルは、/etc/systemd/systemに配置します。ここは管理者が変更した設定ファイルが配置されるディレクトリです。

ユニットファイルを配置したらdaemon-reloadで反映し、dashcamサービスの起動・終了のテストを行い、dashcamサービスを自動起動するように設定しましょう。

sudo cp dashcam.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl start dashcam
sudo systemctl stop dashcam
sudo systemctl enable dashcam

ログを確認してみましょう。

tail -f /var/log/dashcam.log

エラーが出てきます。

ModuleNotFoundError: No module named 'psutil'

あ、これは多分psutilがsudoで実行出来ないからですね。sudoで実行できるようにインストールします。

sudo pip3 install psutil

これで解決しました。

さて、これでサービスとしてdashcamを実行出来るようになりました。ドラレコスクリプトも完成ですね。

おっと、まだスクリプトにバグが有るようです。削除処理ですね。

Feb 27 08:41:51 raspberrypi dashcam[1009]: ------------usage:90.4
Feb 27 08:41:51 raspberrypi dashcam[1014]: frame=1159046 fps= 36 q=-1.0 size=N/A time=08:49:25.13 bitrate=N/A speed=   1x
Feb 27 08:41:51 raspberrypi dashcam[1009]: Exception in thread Thread-529:
Feb 27 08:41:51 raspberrypi dashcam[1009]: Traceback (most recent call last):
Feb 27 08:41:52 raspberrypi dashcam[1009]:   File "/usr/lib/python3.9/threading.py", line 954, in _bootstrap_inner
Feb 27 08:41:52 raspberrypi dashcam[1009]:     self.run()
Feb 27 08:41:52 raspberrypi dashcam[1009]:   File "/usr/lib/python3.9/threading.py", line 892, in run
Feb 27 08:41:52 raspberrypi dashcam[1009]:     self._target(*self._args, **self._kwargs)
Feb 27 08:41:52 raspberrypi dashcam[1009]:   File "/home/pi/dashcam.py", line 88, in diskfree
Feb 27 08:41:52 raspberrypi dashcam[1009]:     file_info = os.stat(folder + file)
Feb 27 08:41:52 raspberrypi dashcam[1009]: FileNotFoundError: [Errno 2] No such file or directory: '/media/usb02023-02-25_16-17-45_r.avi'

フォルダとファイル名の間にスラッシュが抜けていました。(上のdashcam.pyはバグ修正済みです)

今日はここまでです。

長時間稼働試験

丸一日稼働させてみてffmpegが安定稼働するか見ていますが、たまに

Conversion failed!

というエラーでffmpegが終了してしまします。

Google検索すると、-max_muxing_queue_size 512すると良いとか書かれていたので試してみます。

しかしmax_muxing_queue_sizeを付けると負荷が高くなり、FPSが7くらいまで落ちてしまい安定しませんでした。もう少し様子を見て、ダメなら拡張子を.AVIから.H465に変更してみようかと思います。

【ドラレコ】バイクにディスプレイを搭載してリアカメラの画像を写せないか【スマートバイク】

AliExpressで横長のディスプレイを購入しました。これをバイクに取り付けてバックミラー見たく出来ないかなぁと夢想してます。

www.waveshare.com

7.9インチ静電容量式タッチスクリーンlcd、400*1280、hdmi互換、ips、raspberry pi/jetson nano/pc windows 10/10/8/8/7をサポート|デモボード| - AliExpress

ラズパイに取り付けます。

取り付け方は、ディスプレイメーカーのWikiに詳しく記載されてます。助かるなぁ。

7.9inch HDMI LCD - Waveshare Wiki

sudo vim /boot/config.txt

次のように追加します。

#for waveshare 7.9inch HDMI LCD
hdmi_group=2
hdmi_mode=87
hdmi_timings=400 0 100 10 140 1280 10 20 20 2 0 0 0 60 0 43000000 3
display_rotate=1

display_rotateはディスプレイの方向です。今回は横で使いたいので、1にしています。

また、これで反映されない場合、 dtoverlay=vc4-fkms-V3D をコメントアウトします。

私の環境だと、dtoverlay=vc4-fkms-V3D をコメントアウトしないと、display_rotateが反映されませんでした。

【ドラレコ】バックミラーみたいに出来ないか試してみる。

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はどうか?

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

OpenCVでカメラ画像を画面に表示

これもうまくいかない。同じように画面が時折崩れる。

そこで、このカメラを、パソコンに繋いでみよう。

  • Macに繋いで、QuickTime Playerで表示してみる。→問題ない。
  • ノートパソコン上のUbuntuで、guvcviewで表示してみる→問題ない。

  • Raspberry pi 4 で、uvcviewで表示してみる→画像がときおり崩れる。JPEGエラー。

  • Raspberry pi Zero 2 W で、uvcviewで表示してみる→画像がときおり崩れる。JPEGエラー。

つまり、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の刺さっている場所)を渡せば、今そのポート番号に接続されているカメラの番号を返してくれている。 これで、ポート番号でカメラ指定出来るようになった。

今日はここまでです。

【ドラレコ】ラズパイで作るバイク用ドラレコ用のGPSを初期設定する【スマートバイク】

ラズパイで作るバイク用ドラレコ用のGPSを初期設定する

Raspberry Pi用にGPSモジュールを購入しました。使えるように準備していきます。

  • ピンコネクタ(メス)の取り付け
  • USBシリアル変換モジュールのはんだ付け
  • u-center(GPSの設定を行うアプリ)のインストール
  • WindowsPCとGPSの接続確認
  • GPSの通信速度設定

GPSモジュールはこちらを購入しました

GPS/GLONASS受信機(Galileo/BeiDou可)u‐blox M8搭載 みちびき3機受信対応: センサ一般 秋月電子通商-電子部品・ネット通販

USBシリアル変換モジュールはこちらです

FT234X 超小型USBシリアル変換モジュール: 半導体(モジュール) 秋月電子通商-電子部品・ネット通販

コネクタ(メス)

購入したGPSモジュールはケーブルがむき出しなので、ラズパイと気軽に接続出来るようにメスのコネクタを取り付けておきます。

youtu.be

USBシリアル変換モジュール

Windows用アプリでGPSモジュールの初期設定をするために使います。ピンが付属しますがはんだ付けされていないのではんだ付けします。

接続

USBシリアル変換モジュールとGPSを次のように接続します。

  • 5V ー 赤
  • GND ー 黒
  • TXD ー 緑(RXD)
  • RXD ー 橙(TXD)

youtu.be

バイスマネージャで接続を確認

USBシリアル変換モジュールをWindows機に接続します。初回はドライバーソフトウェアがインストールされます。インストールされたら、デバイスマネージャを開きCOMポート番号を確認します。このWindows機の場合はCOM6に割り当てられています。

u-centerのインストール

u-centerを使いGPSの動作確認と設定を行います。

「u-center 2」の方ではなく「u-center」の方をダウンロード、インストールします。

https://www.u-blox.com/en/product/u-center

インストールしたらu-centerを起動します。

接続先の指定

Receiver - Connection - COM6(先ほど調べたCOMポート)

しばらく待って画面上にGPSが取得した情報が表示されればOKです。

gpsdの動作安定の為、通信速度を1152000に変更します

設定画面を開く

View - Message View

UBX - CFG(Config) - PRT(Ports) を選択

1152000に変更

Sendボタンを押す

一旦接続を切る 

Receiver - Connection - Disconnect

ボーレートを変更

Receiver - Baudlate - 1152000 を選択。

変更すると再度接続され、画面上の情報が更新されます。

変更内容を不揮発性メモリに書き込み

UBX - CFG - CFG を開く

Save Current Configuration を選択

Sendボタンを押す

これで準備はOKです。Windows機とUSBシリアル変換モジュールを外し、Raspberry Piに接続して利用します。今日はここまでです。