Raspberry Pi 上の OpenCV でPaPeRo i に人の顔を数えさせる

PaPeRo i のカメラで撮影した画像の中の人の顔の数を PaPeRo iに発話させる方法の一例を紹介します。
顔の検知及びカウントには、OpenCV を使用します。
OpenCVはRaspberry Pi 上にインストールし、Pythonアプリから呼び出して使用します。
使用したOpenCVのバージョンは3.2.0、Raspberry Pi は Raspberry Pi 3 model B v1.2、RaspbianはStretchです。

手順(PaPeRo i 側)

(1) PaPeRo i 制御用WebSocket通信アドオンシナリオをまだインストールしていない場合は、「PaPeRo iをRaspberry Pi上のpythonから操作する」の「PaPeRo iにアドオンシナリオをインストール」に従ってインストール作業を行います。

手順(Raspberry Pi 側)

(1) OpenCVをPython3.xから利用できるようにする為には、ソースからビルドする必要があります。
まずは、ビルドに必要なツール及びライブラリをインストールします。

$ sudo apt-get install cmake libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libgtk2.0-dev libatlas-base-dev gfortran

(2) ビルドに使う作業場所を作ります。

$ cd ~
$ mkdir work

ビルド作業の為に、SDカードの空き容量が約4.3GB必要です。
足りない場合は、ext4 形式でフォーマットしたUSBメモリをマウントして、容量を確保します。

$ ls /dev/sd*
/dev/sda  /dev/sda1
$ sudo mount -t ext4 /dev/sda1 /home/pi/work
$ sudo chown pi:pi work

(3) 作業用ディレクトリにOpenCVのソースをダウンロードし、展開します。

$ cd work
$ mkdir opencv_work
$ cd opencv_work
$ wget --no-check-certificate https://github.com/opencv/opencv/archive/3.2.0.zip
$ unzip 3.2.0.zip
$ rm 3.2.0.zip
$ wget --no-check-certificate https://github.com/opencv/opencv_contrib/archive/3.2.0.zip
$ unzip 3.2.0.zip
$ rm 3.2.0.zip

(4) ビルド設定を行います。

$ cd opencv-3.2.0
$ mkdir build
$ cd build
$ cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D INSTALL_PYTHON_EXAMPLES=ON -D OPENCV_EXTRA_MODULES_PATH=~/work/opencv_work/opencv_contrib-3.2.0/modules ..

(5) ビルドします。約4時間半かかります。

$ make

(6) インストールします。

$ make install

ここまでの作業に成功すれば、Python3.xからOpenCV(モジュール名はcv2)が使えるようになっているはずです。

$ python3
Python 3.5.3 (default, Jan 19 2017, 14:11:04)
[GCC 6.3.0 20170124] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> cv2.__version__
'3.2.0'
>>> quit()

(7) pythonの通信パッケージws4pyをインストールします。また、画像ファイル転送の為にparamikoとscpも使いますので、それらもインストールします。

$ sudo pip3 install ws4py
$ sudo pip3 install paramiko
$ sudo pip3 install scp

(8) 「PaPeRo iをRaspberry Pi上のpythonから操作する」の「Raspberry Piへ通信ライブラリをインストール」に従い、ライブラリを配置します。
以下の説明では、ライブラリを ~/papero の下に配置したものとします。

(9) 下記の内容をコピー・ペーストして、cv_numface.py を作成し、~/papero の下に置きます。
※ ****ユーザ名****、****パスワード**** の部分につきましては、PaPeRo i に一般ユーザでログインする際に使用するユーザ名とパスワードに置き換えて下さい。

import argparse
import time
from enum import Enum

from paramiko import SSHClient,AutoAddPolicy
from scp import SCPClient
import cv2

import pypapero


class State(Enum):
    st0 = 10
    st1 = 11
    st2 = 12
    st3 = 13
    st4 = 14
    end = 999


def main(papero, host, do_disp):
    prev_time = time.monotonic()
    past_time = 0
    interval_time = 0
    state = State.st0
    first = True
    print("HOST=" + host)
    PORT = 22
    USER = "****ユーザ名****"
    PSWD = "****パスワード****"
    scp = None
    ssh = SSHClient() 
    ssh.set_missing_host_key_policy(AutoAddPolicy())
    ssh.connect(host, port=PORT, username=USER, password=PSWD)
    scp = SCPClient(ssh.get_transport())
    cascade_file = '/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_default.xml'
    face_cascade = cv2.CascadeClassifier(cascade_file)
    num_face_now = 0
    num_face = 0
    while state != State.end:
        messages = papero.papero_robot_message_recv(0.1)
        now_time = time.monotonic()
        delta_time = now_time - prev_time
        prev_time = now_time
        if messages is not None:
            msg_dic_rcv = messages[0]
        else:
            msg_dic_rcv = None
        if papero.errOccurred != 0:
            print("------Error occured(main()). Detail : " + papero.errDetail)
            break
        if state == State.st0:
            papero.send_start_speech("検知した人の顔の数を発話します。座布団のボタンで終了します。")
            past_time = 0.0
            state = State.st1
        elif state == State.st1:
            past_time += delta_time
            if past_time > 0.5:
                papero.send_get_speech_status()
                state = State.st2
        elif state == State.st2:
            if msg_dic_rcv is not None:
                if msg_dic_rcv["Name"] == "getSpeechStatusRes":
                    if str(msg_dic_rcv["Return"]) == "0":
                        state = State.st3
                    else:
                        past_time = 0
                        state = State.st1
        elif state == State.st3:
            past_time += delta_time
            if past_time >= interval_time:
                papero.send_take_picture("JPEG", filename="tmp.jpg", camera="VGA")
                past_time = 0
                state = State.st4
        elif state == State.st4:
            if msg_dic_rcv is not None:
                if msg_dic_rcv["Name"] == "takePictureRes":
                    scp.get("/tmp/tmp.jpg")
                    img = cv2.imread("tmp.jpg")
                    if img is not None:
                        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
                        faces = face_cascade.detectMultiScale(gray, 1.3, 5)
                        num_face_now = len(faces)
                        print("num_face_now=" + str(num_face_now))
                        if num_face_now != num_face:
                            papero.send_start_speech("人数は"+str(num_face_now)+"人です")
                        num_face = num_face_now
                        if do_show:
                            for (x,y,w,h) in faces:
                                cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)
                            cv2.imshow('image', img)
                            cv2.waitKey(1)
                    state = State.st3
        if msg_dic_rcv is not None:
            if msg_dic_rcv["Name"] == "detectButton":
                state = State.end


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description = "Usage:")
    parser.add_argument("host", type=str, help = "Host IP address")
    parser.add_argument("-img", help = "Display image", action='store_true')
    command_arguments = parser.parse_args()
    simulator_id = ""
    robot_name = ""
    host = command_arguments.host
    do_show = command_arguments.img
    ws_server_addr = "ws://" + host + ":8088/papero"
    papero = pypapero.Papero(simulator_id, robot_name, ws_server_addr)
    main(papero, host, do_show)
    papero.papero_cleanup()

(10) cv_numface.py を実行します。

$ cd ~/papero
$ python3 cv_numface.py PaPeRoiのIPアドレス

実行すると、PaPeRo i が「検知した人の顔の数を発話します。座布団のボタンで終了します」と発話した後、カメラに映った顔の数をカウントし、数に変化があった時に、その数を発話するようになります。

Raspberry Pi のデスクトップ上に開いたターミナルで実行する場合、

$ python3 cv_numface.py PaPeRoiのIPアドレス -img

のように -img オプションをつけて実行すると、カメラで撮影された画像と検知した顔の範囲が表示されます。