« ADSLはまだ終わらない・・・ | トップページ | 昔使っていたX68000の環境を復活させてエミュレータで使ってみた »

2023.03.04

macOS & Dockerで動作するリアルタイム画像認識デモ環境を作ってみた(2)

以前投稿しましたmacOS & Dockerで動作するリアルタイム画像認識デモ環境を作ってみた(1)では、
マシン:Mac mini (2018) Intel Core i7 (物理コア数6+メモリ16GB)
OS:macOS Catalina
仮想化製品:VirtualBox 6.1.12
の環境下でdocker machineのドライバとしてvirtualboxを指定してDockerコンテナを動かすというものでした。VIrtualBoxを使えばUSB機器をコンテナと繋ぐことができますので、この機能を使ってホストOSが利用するWebcamで撮影した動画に対してDockerコンテナ上で稼働するYOLOを適用しました。

この方式の難点として、Docker Desktop for Mac + VirtualBox 5 or 6 + boot2docker改 という、いささか特殊な環境を用意する必要があるというところでした。そこで、Dockerを使うにしても、もう少し汎用の環境にして、ホストOSから利用するWebcamで撮影した動画をDockerコンテナに配送し、DockerコンテナにてYOLOを適用する方法を試みます。もっともDockerを使わずにMac上に環境を構築すれば早いのですが、Macで稼働しているDockerコンテナ上で画像認識したい!と思いたってしまいましたもので。

環境:
マシン:Mac mini (2018) Intel Core i7 (物理コア数6+メモリ16GB)
OS:macOS Ventura 13.2.1
CLI環境:Homebrew導入済
仮想化製品:Lima limactl version 0.15.0
Docker:Homebrew版 Docker version 23.0.1, build a5ee5b1dfc

本記事の大まかな流れは以下のとおり。
1.ffmpegを使用してWebcam撮影動画を配送する
2.Lima + Dockerを導入する
3.Dockerイメージを作成する
4.動画をTCP TunnelさせてDockerコンテナに配送する
5.リアルタイム画像認識デモを実行する

本記事を記載するにあたっては多くのWebサイトの記事を参考にしています。主なもののみ記事中に出典記載しますが、ありがとうございます。

1.ffmpegを使用してWebcam撮影動画を配送する

まず、ffmpegを導入してWebcam撮影動画をUDPパケットで送信し、別途ffplayで受信して動画を表示する実験をしてみましょう。必要に応じて以下のとおりffmpeg、xquartzをインストールします。

 
$ brew install ffmpeg
$ brew cask install xquartz

Terminalに以下のとおりコマンドを入力すると、使用できるvideoカメラとaudioの一覧が表示されます。


$ ffmpeg -list_devices true -f avfoundation -i dummy
・・・
[AVFoundation indev @ 0x7f8a98a04540] AVFoundation video devices:
[AVFoundation indev @ 0x7f8a98a04540] [0] USB_Camera
[AVFoundation indev @ 0x7f8a98a04540] [1] mmhmm Camera
[AVFoundation indev @ 0x7f8a98a04540] [2] Capture screen 0
[AVFoundation indev @ 0x7f8a98a04540] AVFoundation audio devices:
[AVFoundation indev @ 0x7f8a98a04540] [0] AirParrot
[AVFoundation indev @ 0x7f8a98a04540] [1] mmhmm Audio
[AVFoundation indev @ 0x7f8a98a04540] [2] USB2.0 MIC

Terminalを2つ起動して、ffmpegにWebcam動画をudpパケットに載せて送信させて、ffplayでudpパケットを受けて動画を表示するコマンドを各々のTerminal上で実行します。
(上:送信側、下:受信側)


$ ffmpeg -f avfoundation -framerate 30 -i "0:" -s 640x360 -vcodec libx264 -preset ultrafast -tune zerolatency -x264-params "" -f mpegts udp://127.0.0.1:50001?pkt_size=1316

$ ffplay -hide_banner -fflags nobuffer udp://127.0.0.1:50001

数秒タイムラグがありますが、動画の送受信を確認できました。

2.Lima + Dockerを導入する

Limaは、macOSをホストOSとして、ゲストOSとしてLinuxを、お手軽に実行するためのソフトウェアです。Homebrewを使って簡単に導入できます。dockerコマンドからLimaで稼働する仮想マシンを制御できます。
私は、Limaからdocker-rootful.yamlを利用してdocker環境を起動しました。普通はdocker.yamlを使うのだと思いますが。docker-rootful.yamlの変更箇所は次のとおり。ご自分の環境に合わせてお好みで。


mounts:
- location: "~"
writable: true
- location: "/tmp/lima"
writable: true
- location: "/Volumes/tonopSSD/tonop"
writable: true
cpus: 10
memory: 12GiB
disk: 200GiB

Limaで実行するイメージを作成します。


$ limactll start ./docker-rootful.yaml

デフォルトの設定で実行すると最後に表示されるメッセージにしたがって次のコマンドを実行します。


$ docker context create lima-docker-rootful --docker "host=unix://[Path to home]/.lima/docker-rootful/sock/docker.sock"
$ docker context use lima-docker-rootful
$ docker run hello-world

contextがconflictしたら、適宜docker context rmコマンドで古いcontextを削除してから上のコマンドを実行します。hello-worldが実行されればOK。2回目からは以下のコマンドを使ってLima仮想マシンを起動できます。


$ limactl start docker-rootful

3.Dockerイメージを作成する

今回利用したDockerfileは以下のとおり。


FROM debian:11.6-slim
LABEL maintainer “tonop”

# Install dependencies
RUN apt-get update && apt-get install -y \
openssh-server
RUN mkdir /var/run/sshd
RUN echo 'root:hoge' | chpasswd
RUN echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config
RUN echo 'PermitRootLogin yes' > /etc/ssh/sshd_config
RUN echo 'Port 22' >> /etc/ssh/sshd_config
RUN echo 'Protocol 2' >> /etc/ssh/sshd_config
RUN echo 'X11Forwarding yes' >> /etc/ssh/sshd_config
RUN echo 'X11UseLocalhost no' >> /etc/ssh/sshd_config
RUN echo 'export LC_ALL=C' >> /root/.bashrc

# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so
@g' -i /etc/pam.d/sshd

# For installing applications
RUN apt-get install -y \
apt-utils \
x11-apps \
fswebcam \
eog \
less \
vim \
socat \
lsof \
net-tools \
netcat \
wget && \
rm -rf /var/lib/apt/lists/*

# Install Anaconda
COPY Anaconda3-2022.10-Linux-x86_64.sh /opt/
RUN bash /opt/Anaconda3-2022.10-Linux-x86_64.sh -b -p /opt/anaconda3 && \
rm /opt/Anaconda3-2022.10-Linux-x86_64.sh
RUN echo "PATH=/opt/anaconda3/bin:$PATH" >> ~/.bashrc
RUN echo "source activate yolo3" >> ~/.bashrc
# Install tools by conda
RUN /opt/anaconda3/bin/conda update -n base -c defaults conda -y
RUN /opt/anaconda3/bin/conda create -n yolo3 python=3.7
RUN /opt/anaconda3/bin/conda install -n yolo3 -c defaults matplotlib pandas
RUN /opt/anaconda3/bin/conda install -n yolo3 -c conda-forge opencv=4.4.0
RUN /opt/anaconda3/bin/conda install -n yolo3 -c pytorch pytorch torchvision

# port
EXPOSE 22
EXPOSE 50002

# Copy socatset.sh
COPY socatset.sh /root/
RUN chmod a+x /root/socatset.sh
# Run sshd daemon
CMD ["/usr/sbin/sshd", "-D"]

# EOF

このDockerfileを使ってDockerイメージyolo3を以下のコマンドにより作成しました。なお、予め同じディレクトリにAnaconda3-2022.10-Linux-x86_64.shをダウンロードして配置しておく必要があります。さらに、socatset.shも予め作成して同じディレクトリに配置しておく必要がありますが、こちらは後述します。


$ docker build -t yolo3:0.3 .

次に作成したDockerイメージを実行します。以下のスクリプトrun.shを作成して ./run.sh yolo3:0.3 を実行します。


#!/bin/sh

if [ "$#" -ne 1 ]; then
echo "Usage: run.sh DOCKER_IMAGE."
echo "lack of option."
exit 1
fi

image="$1"

open -a XQuartz
IP=$(ifconfig en0 | grep inet | awk '$1=="inet" {print $2}')
echo "docker run"

docker run --rm --name yolo3 -p 22000:22 -p 50002:50002 -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$IP:0.0 -v [Path to work]/work:/root/work "$image" &

# EOF

4.動画をTCP TunnelさせてDockerコンテナに配送する

今回作成したyolo3のDockerイメージは22番ポートと50002番ポートを各々Macの22000番ポートと50002番ボートと共有します。問題は共有ポートがTCPパケットのみ通信することでそのためUDPパケットをDockerコンテナに配送するために工夫が必要になります。そこで、ffmpeg (50001番ポート) -> UDPパケット -> TCPパケット(50002番ポート) -> Dockerコンテナ -> TCPパケット(50002番ポート) -> UDPパケット(50003番ポート) -> yolo3 とUDPをTCPでトンネルすることにしました。
(参考)socatを使用したUDPトンネル:Zenn

スマートではないかもしれませんが、Macのデスクトップに4つのTerminalを立ち上げます。

Terminal 1
Dockerコンテナ上で50002番ポートのTCPパケットをLISTENし、50003番ポートにUDPパケットとして変換して出力します。


# Dockerコンテナにログイン
$ ssh -X -p 22000 root@127.0.0.1
# password入力
# socatset.shをパックグラウンドで実行
$ ./socatset.sh &

ここでのsocatset.shは次のとおり。


#!/bin/sh
socat -s TCP-LISTEN:50002,reuseaddr,fork UDP:127.0.0.1:50003
# EOF

Terminal 2
Mac上で5001番ボートのUDPパケットを5002番ポートにTCPパケットとして変換して出力します。


$ ./socatset.sh

ここでのsocatset.shは次のとおり。


#!/bin/sh
socat -s UDP-LISTEN:50001,reuseaddr,fork TCP:127.0.0.1:50002

Terminal 3
ここまででパケット変換の待受が終わったので、Webcam動画を50001番ボートに出力します。


$ ./ffmpegrun.sh

ここで、ffmpegrun.shは次のとおり。


#!/bin/sh
ffmpeg -f avfoundation -framerate 30 -i "0:" -s 640x360 -vcodec libx264 -preset ultrafast -tune zerolatency -x264-params "" -f mpegts udp://127.0.0.1:50001?pkt_size=1316

うまくいくと、Termnal 1で"Connection refused"が連続して表示されるはずですが、うまくいかない場合はTerminal 2でps ax してsocatのプロセスIDを調べてから当該プロセスをkillし、再度起動してください。

Terminal 4
ssh -X -p 22000 root@127.0.0.1 を実行して、Dockerコンテナにsshでログインしてデモプログラムを実行します。詳細を後述します。

5.リアルタイム画像認識デモを実行する

DockerコンテナのworkディレクトリとしてマウントされているMacのworkディレクトリにYolo3 つまりpytorch-yolo-v3を展開(git clone)しておきます。このpytorch-yolo-v3の中にcam_demo.pyがありますのでcam_demo2.pyとしてコピーします。このcam_demo2.pyの中を検索するとcap = cv2.VideoCapture(0)との行が見つかりますので、cap = cv2.VideoCapture("udp://127.0.0.1:50003") に変更します。


cap = cv2.VideoCapture("0")

を次のとおり変更します。


cap = cv2.VideoCapture("udp://127.0.0.1:50003")

それから、sshログインしたTerminal 4から次のコマンドを実行します。


$ python cam_demo2.py

Termnal 1における"Connection refused"の表示が止まって、しばらく待つと動画認識結果を表示するWindowが現れるはずです。
Good Luck!!

# 今回試した方法は、手元では4.25FPS程度しか出ていないので、macOS & Dockerで動作するリアルタイム画像認識デモ環境を作ってみた(1)よりは遅いみたいですね〜

|

« ADSLはまだ終わらない・・・ | トップページ | 昔使っていたX68000の環境を復活させてエミュレータで使ってみた »

パソコン・インターネット」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)




« ADSLはまだ終わらない・・・ | トップページ | 昔使っていたX68000の環境を復活させてエミュレータで使ってみた »