« Intel MacでGPUを使わずに大規模言語モデルと戯れてみた | トップページ | Intel MacでLlama 2モデルと戯れてみる »

2023.07.08

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

macOS & Dockerで動作するリアルタイム画像認識デモ環境を作ってみた(1)
macOS & Dockerで動作するリアルタイム画像認識デモ環境を作ってみた(2)
という記事に書きましたが、「macOS & Dockerで動作するリアルタイム画像認識デモ環境を作ってみた」シリーズ第3弾です。

(1)はVirtualBoxを仮想マシンのドライバとして使うもので、特殊な環境を作る必要がありました。(2)はどうも安定性に欠けるようです。遅延が酷いし。というわけで再トライしてみます。

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

今回採った方法は、PythonでWebcam画像をソケット送信し、Dockerコンテナ内でPythonのソケット受信をマルチスレッド内で常時行って、YOLO v8を利用して画像認識したもの、になります。

yolo v8のコードをultralytics/ultralyticsからgit cloneします。

Dockerfile-cpuに少し手を入れて、以下のとおりとしました。


# Ultralytics YOLO 🚀, GPL-3.0 license
# Builds ultralytics/ultralytics:latest-cpu image on DockerHub https://hub.docker.com/r/ultralytics/ultralytics
# Image is CPU-optimized for ONNX, OpenVINO and PyTorch YOLOv8 deployments

# Start FROM Ubuntu image https://hub.docker.com/_/ubuntu
#FROM ubuntu:rolling
FROM debian:11.6-slim
LABEL maintainer "tonop"

# Downloads to user config dir
ADD https://ultralytics.com/assets/Arial.ttf https://ultralytics.com/assets/Arial.Unicode.ttf /root/.config/Ultralytics/

# Install linux packages
ENV DEBIAN_FRONTEND noninteractive
RUN apt update
RUN TZ=Etc/UTC apt install -y tzdata
RUN apt install --no-install-recommends -y python3-pip git zip curl htop libgl1-mesa-glx libglib2.0-0 libpython3-dev gnupg g++
RUN alias python=python3

# Create working directory
RUN mkdir -p /usr/src/ultralytics
WORKDIR /usr/src/ultralytics

# Copy contents
# COPY . /usr/src/app (issues as not a .git directory)
RUN git clone https://github.com/ultralytics/ultralytics /usr/src/ultralytics

# Install pip packages
RUN python3 -m pip install --upgrade pip wheel
RUN pip install --no-cache '.[export]' albumentations gsutil notebook \
--extra-index-url https://download.pytorch.org/whl/cpu

# Cleanup
ENV DEBIAN_FRONTEND teletype

# 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 \
wget && \
rm -rf /var/lib/apt/lists/*

# port
EXPOSE 22
EXPOSE 50002
EXPOSE 8888

# Run sshd daemon
CMD ["/usr/sbin/sshd", "-D"]

# Usage Examples -------------------------------------------------------------------------------------------------------

# Build and Push
# t=ultralytics/ultralytics:latest-cpu && sudo docker build -f docker/Dockerfile-cpu -t $t . && sudo docker push $t

# Pull and Run
# t=ultralytics/ultralytics:latest-cpu && sudo docker pull $t && sudo docker run
-it --ipc=host -v "$(pwd)"/datasets:/usr/src/datasets $t

Mac miniのmacOSでクライアントを動かしてwebcamの動画をDockerコンテナに送信し、Dockerコンテナ上で動画を受信して物体認識を行い結果動画をX Windowに表示します。

この仕組みにryanbekabe/client.pyのコードを利用しました。ありがとうございます。まずは、macOS上のクライアントからWebcamの画像をDockerコンテナ上で稼働するサーバに送信してサーバが動画をX Windowに表示できるが試してみましょう。
client.py


import cv2
import io
import socket
import struct
import time
import pickle
import zlib

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 50002))
connection = client_socket.makefile('wb')

cam = cv2.VideoCapture(0)

cam.set(3, 640);
cam.set(4, 480);

img_counter = 0

encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90]

while True:
ret, frame = cam.read()
result, frame = cv2.imencode('.jpg', frame, encode_param)
# data = zlib.compress(pickle.dumps(frame, 0))
data = pickle.dumps(frame, 0)
size = len(data)


print("{}: {}".format(img_counter, size))
client_socket.sendall(struct.pack(">L", size) + data)
img_counter += 1

cam.release()

受信側ではマルチスレッドを使って別スレッドでひたすら受信するモジュールを作成し、サーバがこのモジュールを利用します。
モジュール
recvframe.py


import socket
import sys
import cv2
import pickle
import numpy as np
import struct
import zlib
import threading

HOST = ''
PORT = 50002
frame = []
stop = False

def worker(*args):
global frame, stop

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print('Socket created')

s.bind((HOST,PORT))
print('Socket bind complete')
s.listen(10)
print('Socket now listening')

conn, addr = s.accept()

data = b""
payload_size = struct.calcsize(">L")
print("payload_size: {}".format(payload_size))
while True:
while len(data) < payload_size:
#print("Recv: {}".format(len(data)))
data += conn.recv(4096)

#print("Done Recv: {}".format(len(data)))
packed_msg_size = data[:payload_size]
data = data[payload_size:]
msg_size = struct.unpack(">L", packed_msg_size)[0]
#print("msg_size: {}".format(msg_size))
while len(data) < msg_size:
data += conn.recv(4096)
frame_data = data[:msg_size]
data = data[msg_size:]

frame = pickle.loads(frame_data, fix_imports=True, encoding="bytes")
#frame = cv2.imdecode(frame, cv2.IMREAD_COLOR)
# cv2.imshow('ImageWindow',frame)
# cv2.waitKey(1)

if stop == True:
break

def startThread():
th = threading.Thread(target=worker, args=(0, ))
th.start()

サーバ
cam_demo.py


import cv2
import recvframe

recvframe.startThread()

while True:
while len(recvframe.frame) < 1:
pass
frame = cv2.imdecode(recvframe.frame, cv2.IMREAD_COLOR)
cv2.imshow("ImageWindow", frame)
if cv2.waitKey(1) & 0xff == ord('q'):
recvframe.stop = True
break
cv2.destroyAllWindows()

cam_demo.pyからrecvframe.pyのstartThread()を呼び出して受信機能を別スレッドで実行します。recvframe.frameとrecvframe.stopはグローバル変数になっていて、recvframe.frameは最新の受信フレームデータです。recvframe.stopはフラグで、子スレッドを終了させたいときにTrue値に変更します。imageWindowをアクティブにした状態で'q'キーを押下すると子スレッドを終了させてWindowaを閉じてプログラムを終了します。

いよいよ用意したコードを実行していきます。まず、Dockerコンテナを起動します。起動shellはこちら。


#!/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}')
#docker-machine start yolo8:latest-cpu2
#eval $(docker-machine env yolo8:latest-cpu2)
echo "docker run"

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

# EOF

上のDockerfile-cpuで作成したイメージ名をyolodemoとした場合、このように起動します。起動した後Dockerコンテナにログインします。


$ ./run.sh yolodemo
$ ssh -X -p 22000 root@127.0.0.1

Dockerコンテナ側
上のPython codeがあるディレクトリに移動して次のコマンドを実行。


$ python3 cam_demo.py

macOS側


$ python3 client.py

うまくいけば、X Windowが開いてWebcamが撮影した動画が表示されるはず。

いよいよYolo v8を使ってみましょう。
Yolo v8を利用した推論を行うPythonサンプルコードはこちら。
example.py


from ultralytics import YOLO
import sys
import cv2
import recvframe

args = sys.argv
if len(args) < 2:
print("lack of argument. e.g. yolo8n.pt")
exit(-1)

model = YOLO(args[1]) # yolov8s.pt
recvframe.startThread()

while True:
while len(recvframe.frame) < 1:
pass
frame = cv2.imdecode(recvframe.frame, cv2.IMREAD_COLOR)
results = model(frame)
annotated_frame = results[0].plot()
cv2.imshow("ImageWindow", annotated_frame)
if cv2.waitKey(1) & 0xff == ord('q'):
recvframe.stop = True
break
cv2.destroyAllWindows()

ここで
results = model(frame)
でフレームデータに対してYolo学習モデルを適用して推論を行います。
annotated_frame = results[0].plot()
で認識(推論)した結果のアノテーションを追加したフレームデータを取得します。
以下のコマンドによりコードを実行します。適宜事前にダウンロードしたモデルデータファイルをパラメータとして指定してください。


$ python3 example.py yolov8n.pt

2023070801
やりました!

|

« Intel MacでGPUを使わずに大規模言語モデルと戯れてみた | トップページ | Intel MacでLlama 2モデルと戯れてみる »

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

コメント

コメントを書く



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




« Intel MacでGPUを使わずに大規模言語モデルと戯れてみた | トップページ | Intel MacでLlama 2モデルと戯れてみる »