物体検出と表題にあるが今回は人体のみを対象に検出を行ってみようと思う。
前回作成したRaspberry Pi Zeroを使ってストリーム配信された動画から検出したいと思う。
構成は次の通り。

それをケースに入れてみた(写真右)、X68000 XVIというSHARPの16Bit PCのケースを見つけたので購入してみた。筆者はその昔X68000 XVI Compactを保有していて懐かしさのあまり思わずポチってしまったわけである。
違いは筐体の大きさとFDDのサイズ5インチと3.5インチの違いだった記憶があるが、興味がある方はネットで検索してみてくれ。

さて、物体検出をするためのアプリケーション(ライブラリ)はOpenCVを使用することとした。
せっかくなので最新のソフトウェア導入ということを考え、Python3.7系とOpenCV4を入れようと思う。

しかし、Python3.7系だとOpenCV4のbuildがうまく通らない。

OpenCV4は変えずにPython3.6系のPython3.6.9と合わせて導入することとした。

Python3.6.9インストール

事前にpyenv+virtualenvの環境は作成しておくことを前提とする。

Pythonのインストール時に共有ライブラリを作成するよう、下記のようにインストールする。

$ CONFIGURE_OPTS="--enable-shared --enable-optimizations" pyenv install 3.6.9 -v

OpenCV4インストール

(ロゴもつけておいた)

Buildするためのライブラリをインストール

OpenCV4のインストール前にシステムの更新など必要ライブラリをインストールする。

$ sudo apt update
$ sudo apt upgrade
$ sudo apt -y install build-essential cmake unzip pkg-config
$ sudo apt -y install libjpeg-dev libpng-dev libtiff-dev libffi-dev
$ sudo apt -y install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev gstreamer1.0-tools
$ sudo apt -y install libxvidcore-dev libx264-dev
$ sudo apt -y install libgtk-3-dev
$ sudo apt -y install libcanberra-gtk*
$ sudo apt -y install libatlas-base-dev gfortran
$ sudo apt -y install python3-dev
$ sudo apt -y install cmake-qt-gui

OpenCV4本体のインストール

$ cd ~/Downloads
$ wget -O opencv.zip https://github.com/opencv/opencv/archive/4.1.1.zip
$ wget -O opencv_contrib.zip https://github.com/opencv/opencv_contrib/archive/4.1.1.zip
$ unzip opencv.zip
$ unzip opencv_contrib.zip
$ cd ~/opencv-4.1.1
$ mkdir build
$ cd build
$ cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D OPENCV_EXTRA_MODULES_PATH=~/Downloads/opencv_contrib-4.1.1/modules \
-D ENABLE_NEON=ON \
-D ENABLE_VFPV3=ON \
-D BUILD_TESTS=OFF \
-D WITH_GSTREAMER=ON \
-D WITH_V4L=ON \
-D OPENCV_ENABLE_NONFREE=ON \
-D BUILD_opencv_python3=yes \
-D INSTALL_PYTHON_EXAMPLES=OFF \
-DPYTHON3_EXECUTABLE=/home/pi/.pyenv/shims/python \
-DPYTHON3_INCLUDE_DIR=/home/pi/.pyenv/versions/3.6.9/include/python3.6m \
-DPYTHON3_LIBRARY=/home/pi/.pyenv/versions/3.6.9/lib/libpython3.so \
-D BUILD_EXAMPLES=OFF ..
$ make -j4
$ sudo make install
$ sudo ldconfig

Python仮想環境の作成とOpenCVの設定

$ pyenv virtualenv 3.6.9 cv
$ cd ~/.pyenv/versions/cv/lib/python3.6/site-packages/
$ ln -s /usr/local/lib/python3.6/site-packages/cv2/python-3.6/cv2.cpython-36m-arm-linux-gnueabihf.so cv2

動作確認

Pythonを起動して確認。

$ pyenv local cv
$ python
import cv2
img = cv2.imread('/home/pi/Downloads/opencv-4.1.1/samples/data/building.jpg', cv2.IMREAD_GRAYSCALE)
print(img)
[[ 1 3 4 … 223 224 225]
[ 2 3 3 … 222 222 222]
[ 2 2 1 … 225 225 224]
…
[ 74 70 65 … 28 28 27]
[ 75 70 64 … 23 24 24]
[ 76 70 62 … 19 20 20]]

Raspberry PiにはX Windowを入れていないため、
画像での確認は出来ないが動いていそうである。

ちなみにBuildで落ちる場合は以下の設定をして、Swapを有効にしてからBuildする。

$ sudo vi /etc/dphys-swapfile
CONF_SWAPSIZE=2048 <-- Default 100から変更
$ sudo dphys-swapfile install
$ sudo dphys-swapfile swapon
swapfileサイズが2048になっている事を確認
$ free -m

OpenCVの動作確認が終了したので実際に、人体検出を実装してみよう。
人体検出にはOpenCVにカスケード型分類器が実装されているので今回はこちらを利用させてもらう。(https://docs.opencv.org/4.1.1/d1/de5/classcv_1_1CascadeClassifier.html)
このカスケード型分類器にはいくつかモデルが用意されており、人体の他に「顔」、「目」、「笑顔」、「自動車ナンバー」などがある。
各モデルはopencv.zip解凍後に、data/haarcascadesディレクトリにXMLファイルが格納されている。また、リアルタイムにブラウザに検出した人体にマーキングするように実装してみよう。

ディレクトリ構成. <カレントディレクトリ>
__ templates
__ index.html
__ data
__ haarcascade_fullbody.xml
__ haarcascade_upperbody.xml
__ haarcascade_lowerbody.xml
__ run.py

ソースコードは以下の通り。

[run.py]

import cv2
import os
from flask import Flask, render_template, Response
import time

app = Flask(__name__)


def replace_frame(cap, models):
    while True:
        # Frame取得
        ret, frame = cap.read()
        if not ret:
            continue

        # Predict
        for model in models:
            human = model.detectMultiScale(frame)
            for part in human:
                # 検出箇所に四角形のマーカーを入れる
                cv2.rectangle(frame, tuple(part[0:2]), tuple(part[0:2] + part[2:4]), (255, 255, 255), thickness=2)
                print('find human!')
        # ブラウザへStream送信のためJPEGに変換
        ret, img = cv2.imencode('.jpg', frame)
        yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + img.tobytes() + b'\r\n')


@app.route('/predict')
def predict():
    cascade = []
    # 全身
    # cascade.append(cv2.CascadeClassifier('./data/haarcascades/haarcascade_fullbody.xml'))
    # 下半身
    cascade.append(cv2.CascadeClassifier('./data/haarcascades/haarcascade_lowerbody.xml'))
    # 上半身
    # cascade.append(cv2.CascadeClassifier('./data/haarcascades/haarcascade_upperbody.xml'))
    # 今回は下半身だけ対象としてみる。
    # Raspberry Pi Zero 上で起動しているmjpeg-streamerのListenアドレスと動画streamのを指定
    cap = cv2.VideoCapture('http://192.168.0.202:8080/?action=stream')
    return Response(replace_frame(cap, cascade), mimetype='multipart/x-mixed-replace; boundary=frame')


@app.route('/')
def index():
    return render_template('index.html')


if name == 'main':
    app.run(host='0.0.0.0', port=8080, debug=True)

dataディレクトリにopencv.zip解凍後のdata/haarcascades内にある下記ファイルをコピーしておく。
・haarcascade_fullbody.xml
・haarcascade_upperbody.xml
・haarcascade_lowerbody.xml

[templates/index.html]

<html>
  <head>
    <title>Flask Streaming</title>
  </head>
  <body>
    <h1>Flask Streaming by predict human</h1>
    <img src="{{url_for('predict')}}">
  </body>
</html>

それでは起動して確認。

$ python run.py

別のPCからブラウザで動作確認。
http://192.168.0.203:8080/predict
※192.168.0.203はRaspberry Pi3 B+のIPアドレス

カメラの設置位置を2Fの高さから下方に向けているため少々精度が悪くなっているように感じる。

以下が、実際の動画から取得した画像

今回使用した人体モデルは正面画と背面画のみを対象としているようなので上方からの画像に対しては精度が悪かったと思われる。

KNOWN LIMITATIONS
1) The detectors only support frontal and back views but not sideviews.
Sideviews are trickier and it makes a lot of sense to include additional
modalities for their detection, e.g. motion information. I recommend
Viola and Jones' ICCV 2003 paper if this further interests you.

今回の方法は、DeepLearningを使用しないため比較的軽く動作する。
ソースコード上に複数のモデルを処理できるように書いてあるが、Raspberry Piで処理できるのはせいぜい1モデル。
2つ以上にすると動画のコマ落ちが激しく実用に耐えられないということがわかった。

OpenCVには他にもできそうな機能があるので、後日公開したいと思う。