どうも、Reveです。実に3か月ぶりの更新です…orz
少し前に、仕事の関係でラズパイからWebカメラの映像を流し続けるシステムを簡易的に用意したので、今回はその実装をつらつら書いていこうと思います。なお、実装自体は特に機密事項なんてありませんのでご安心を(謎
概要
前述のとおり、電源を入れたら自動的にWebカメラの映像を流すシステムを作ります。動作は以下の通り。
- Webカメラとモニターをラズパイに接続
- ラズパイの電源ON
- デスクトップ画面が出てきたら、Webカメラからの映像を自動的に流す
実装
事前準備(ハードウェア)
まずは以下のものを用意します。
- Raspberry Pi (pico以外)
- micro SDカード 8GB~
- Webカメラ (USB接続)
- HDMI接続のディスプレイ
- マウス、キーボード (USB接続)
- 5V電源 (USB充電器 / モバイルバッテリー)
- OS書き込み用のPC (Windows / Mac / Ubuntu x86)
一応、注意点としてラズパイはpico以外(ラズパイOSが動くもの)を選んでください。できれば初代とzero以外のModel Bがオススメ(AはUSBコネクタが少ないので)。
またラズパイ4は映像端子がミニHDMIのため、変換ケーブルかアダプタが必要です。
事前準備(ソフトウェア)
以下のURLから、専用のソフトウェアをOS書き込み用のPCにインストールして、ソフトウェアからマイクロSDにOSを書き込みます(詳細は省略)。
なお、OSは「Raspberry Pi OS (32bit)」を前提としています(他は未検証)。
開発環境の構築
ラズパイOSを起動できたら、まずターミナルで以下のコマンドを入力して、必要なパッケージをインストールします。
1 2 3 |
>> sudo apt-get install usbutils python3-opencv libcanberra-gtk3-module v4l-utils qv4l2 # 下のコマンドはラズパイでは不要(?) >> sudo adduser (ユーザー名) video |
環境構築はこれだけですが、後述のプログラム開発で映らない場合は qv4l2 コマンドでカメラが映るか確かめられます。
プログラム開発
続いて、OpenCVでカメラの映像を流し続けるプログラムを作ります(Python)。コードはこちらをお借りします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
import cv2 import multiprocessing import multiprocessing.sharedctypes import time import numpy WIDTH=640 HEIGHT=480 def camera_reader(out_buf, buf1_ready): try: capture = cv2.VideoCapture(0, cv2.CAP_V4L2) except TypeError: capture = cv2.VideoCapture(0) if capture.isOpened() is False: raise IOError if isinstance(capture.get(cv2.CAP_PROP_CONVERT_RGB), float): capture.set(cv2.CAP_PROP_CONVERT_RGB, 0.0) else: capture.set(cv2.CAP_PROP_CONVERT_RGB, False) capture.set(cv2.CAP_PROP_BUFFERSIZE, 4) capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('Y', 'U', 'Y', 'V')) capture.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH) capture.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT) capture.set(cv2.CAP_PROP_FPS, 30) while(True): try: capture_start_time = time.time() ret, frame = capture.read() if ret is False: raise IOError #print("Capture FPS = ", 1.0 / (time.time() - capture_start_time)) bgr_frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_YUYV) #cv2.imshow('frame2', bgr_frame) buf1_ready.clear() memoryview(out_buf).cast('B')[:] = memoryview(bgr_frame).cast('B')[:] buf1_ready.set() print("Capture+Conversion+Copy FPS = ", 1.0 / (time.time() - capture_start_time)) except KeyboardInterrupt: # 終わるときは CTRL + C を押す break capture.release() if __name__ == "__main__": buf1 = multiprocessing.sharedctypes.RawArray('B', HEIGHT*WIDTH*3) buf1_ready = multiprocessing.Event() buf1_ready.clear() p1=multiprocessing.Process(target=camera_reader, args=(buf1,buf1_ready), daemon=True) p1.start() captured_bgr_image = numpy.empty((HEIGHT, WIDTH, 3), dtype=numpy.uint8) while True: try: display_start_time = time.time() buf1_ready.wait() captured_bgr_image[:,:,:] = numpy.reshape(buf1, (HEIGHT, WIDTH, 3)) buf1_ready.clear() cv2.imshow('frame', captured_bgr_image) cv2.waitKey(1) print("Display FPS = ", 1.0 / (time.time() - display_start_time)) except KeyboardInterrupt: # 終わるときは CTRL + C を押す print("Waiting camera reader to finish.") p1.join(10) break cv2.destroyAllWindows() |
デバイス名の固定
手動だとほぼ問題ないのですが、自動起動の場合、プログラムを立ち上げても権限やデバイス名などでカメラとの接続に失敗する場合があります。そこで、udevルールファイルを作成して自動起動サービスに連携させます。
まず、以下のコマンドで接続しているUSBデバイスの情報を取得します(参照)。
1 2 3 4 5 6 7 8 9 |
# 接続しているUSBデバイス一覧 >> lsusb Bus 002 Device 002: ID 8087:8001 Intel Corp. Bus 003 Device 016: ID 054c:06c1 Sony Corp. : Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub # IDを指定して詳細を表示 >> lsusb -s 003:016 -v (詳細が下にずらーっと表示される) |
Webカメラの情報が取得出来たら、「idVendor(ベンダーID)」と「idProduct(プロダクトID)」の値を覚えておき、エディタを開いてファイルを作成し、
1 |
>> sudo nano /etc/udev/rules.d/99-webcam.rules |
以下の一文をファイルに記入して保存します(各項目について:参照1、参照2、参照)。
- KERNEL:元のデバイス名(接続時)
- SUBSYSTEM:デバイス制御のサブシステム(キャプチャ機器は video4linux)
- SUBSYSTEMS:サブシステムの親 (USB)
- ATTR{…}:デバイスの属性。ここでは idVendor と idProduct を指定
- SYMLINK:デバイスファイルのシンボリックリンク (+= は追加)
- TAG:紐づけするシステム
- MODE:権限
- ENV{SYSTEMD_WANTS}:依存関係。次に作成する自動起動サービスと連携
1 |
KERNEL=="video*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ATTRS{idVendor}=="05a3", ATTRS{idProduct}=="9530", SYMLINK+="webcam", TAG+="systemd", MODE="0755", ENV{SYSTEMD_WANTS}="webcam-streamer.service" |
正しく記載されていれば、以下のコマンドでWebカメラが指定のデバイス名で認識されていると分かります。
1 2 |
>> tail -f /var/log/syslog (接続したデバイス名などが表示) |
ちなみに、上記のルールファイルはこのカメラを使用した場合のものです。
自動起動サービスの設定
まずは、好きな場所(今回はデスクトップを想定)に daemon.env というファイルを作成し、以下の様に編集します。
1 2 3 |
LD_PRELOAD="/usr/lib/arm-linux-gnueabihf/libatomic.so.1" PYTHONPATH=/usr/lib/python39.zip:/usr/lib/python3.9:/usr/lib/python3.9/lib-dynload:/usr/local/lib/python3.9/dist-packages:/usr/lib/python3/dist-packages:/usr/lib/python3.9/dist-packages DISPLAY=:0.0 |
次に、ターミナルで以下のコマンドを入力して
1 |
>> sudo nano /etc/systemd/system/webcam-streamer.service |
以下の通りに編集します。今回はGUIが無いと動かないので、システム環境の指定を graphical.target としています。(ユニットのパラメータについて:参照)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[Unit] Description=Webcam streamer BindTo=dev-webcam.device After=dev-webcam.device [Service] WorkingDirectory=/home/pi/Desktop EnvironmentFile=/home/pi/Desktop/daemon.env ExecStart=/usr/bin/python3 /home/pi/Desktop/webcam-multiprocess.py TimeoutStartSec=10 Restart=on-failure RestartSec=10 Type=simple #User=root User=pi [Install] WantedBy=graphical.target #WantedBy=multi-user.target |
終わったら、以下のコマンドでサービスの自動起動を有効にします。
1 2 3 4 5 |
# ファイル読み込み >> sudo systemctl daemon-reload # 自動起動の設定 >> sudo systemctl start webcam-streamer.service >> sudo systemctl enable webcam-streamer.service |
正しく動作しているかは、以下のコマンドで確認できます。
1 2 3 4 |
# 指定のサービスの動作確認 >> sudo systemctl status webcam-streamer.service # 全ユニットファイルの自動起動を確認 >> systemctl list-unit-files --type=service |
正常に読み込まれていれば、次からの起動時にWebカメラの映像を自動的に流してくれるようになります。