Webカメラを使ってリアルタイムに顔検出してみる

# はじめに

Raspberry PIにmotionというソフトウェアをインストールして監視カメラとして使用し、映った人が誰かを識別するという取り組みをしているのですが、保存された静止画を使って識別するだけなので、少々物足りない感じがしているこの頃。

せっかくなので、動画をリアルタイムで処理して何かできないかと思って情報収集していたら、関係ありそうな記事があったので参考にしながらリアルタイムに顔検出するスクリプトを作ってみました。

[OpenCVでWebカメラ/ビデオカメラの動画をリアルタイムに変換してみる](http://qiita.com/hitomatagi/items/12a2eceaf65f142ec3df)

# OpenCVのインストール

手前味噌ですが、インストール手順は以下の記事をご参照下さい。

[Macで深層学習の環境をさくっと作る手順 with TensorFlow and OpenCV](http://qiita.com/mix_dvd/items/b49651cf1181a986506c)

また、顔検出に使う識別器を以下のコマンドで作業フォルダにコピーしておきます。

```
$ cp /usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_alt2.xml ./
```

# スクリプトの作成

参考サイトにて公開されていたスクリプトを修正し、以下のとおりとします。

```py3:sebcam.py
import cv2

if __name__ == '__main__':
    # 定数定義
    ESC_KEY = 27     # Escキー
    INTERVAL= 33     # 待ち時間
    FRAME_RATE = 30  # fps

    ORG_WINDOW_NAME = "org"
    GAUSSIAN_WINDOW_NAME = "gaussian"

    DEVICE_ID = 0

    # 分類器の指定
    cascade_file = "haarcascade_frontalface_alt2.xml"
    cascade = cv2.CascadeClassifier(cascade_file)
    
    # カメラ映像取得
    cap = cv2.VideoCapture(DEVICE_ID)

    # 初期フレームの読込
    end_flag, c_frame = cap.read()
    height, width, channels = c_frame.shape

    # ウィンドウの準備
    cv2.namedWindow(ORG_WINDOW_NAME)
    cv2.namedWindow(GAUSSIAN_WINDOW_NAME)

    # 変換処理ループ
    while end_flag == True:

        # 画像の取得と顔の検出
        img = c_frame
        img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        face_list = cascade.detectMultiScale(img_gray, minSize=(100, 100))
        
        # 検出した顔に印を付ける
        for (x, y, w, h) in face_list:
            color = (0, 0, 225)
            pen_w = 3
            cv2.rectangle(img_gray, (x, y), (x+w, y+h), color, thickness = pen_w)
        
        # フレーム表示
        cv2.imshow(ORG_WINDOW_NAME, c_frame)
        cv2.imshow(GAUSSIAN_WINDOW_NAME, img_gray)

        # Escキーで終了
        key = cv2.waitKey(INTERVAL)
        if key == ESC_KEY:
            break

        # 次のフレーム読み込み
        end_flag, c_frame = cap.read()

    # 終了処理
    cv2.destroyAllWindows()
    cap.release()
```

# 動作確認

以下のコマンドを実行するとウインドウが二つ表示され、元画像とグレイスケールに変換されて顔に四角が描画されたものが表示されるはず。

```sh:
$ python sebcam.py
```

できた(^-^)/

# おまけ（オプティカルフロー）

リアルタイムに検出した物体を追尾するというサンプルもあったので、ついでに試してみました。

[OpenCVでオプティカルフローをリアルタイムに描画する（Shi-Tomasi法、Lucas-Kanade法）](http://qiita.com/hitomatagi/items/772549d0fc3c89fb3cc4)

参考先の動画読込元が動画ファイルだったので、これをWebカメラに変更してみました。
そして、検出した特徴点がなくなると再検出するよう変更してあります。

```py3:LucasKande.py
import numpy as np
import cv2

DEVICE_ID = 0
cap = cv2.VideoCapture(DEVICE_ID)

# Shi-Tomasiのコーナー検出パラメータ
feature_params = dict( maxCorners = 100,
                       qualityLevel = 0.3,
                       minDistance = 7,
                       blockSize = 7 )

# Lucas-Kanade法のパラメータ
lk_params = dict( winSize  = (15,15),
                  maxLevel = 2,
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

# ランダムに色を１００個生成（値0～255の範囲で100行3列のランダムなndarrayを生成）
color = np.random.randint(0, 255, (100, 3))

# 最初のフレームの処理
end_flag, frame = cap.read()
gray_prev = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
feature_prev = cv2.goodFeaturesToTrack(gray_prev, mask = None, **feature_params)
mask = np.zeros_like(frame)

while(end_flag):
    # グレースケールに変換
    gray_next = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # オプティカルフロー検出
    feature_next, status, err = cv2.calcOpticalFlowPyrLK(gray_prev, gray_next, feature_prev, None, **lk_params)

    if len(feature_next[status == 1]) == 0:
        feature_prev = cv2.goodFeaturesToTrack(gray_prev, mask = None, **feature_params)
        mask = np.zeros_like(frame)
        feature_next, status, err = cv2.calcOpticalFlowPyrLK(gray_prev, gray_next, feature_prev, None, **lk_params)

    # オプティカルフローを検出した特徴点を選別（0：検出せず、1：検出した）
    good_prev = feature_prev[status == 1]
    good_next = feature_next[status == 1]
    
    # オプティカルフローを描画
    for i, (next_point, prev_point) in enumerate(zip(good_next, good_prev)):
        prev_x, prev_y = prev_point.ravel()
        next_x, next_y = next_point.ravel()
        mask = cv2.line(mask, (next_x, next_y), (prev_x, prev_y), color[i].tolist(), 2)
        frame = cv2.circle(frame, (next_x, next_y), 25, color[i].tolist(), -1)
    img = cv2.add(frame, mask)

    # ウィンドウに表示
    cv2.imshow('window', img)

    # ESCキー押下で終了
    if cv2.waitKey(30) & 0xff == 27:
        break

    # 次のフレーム、ポイントの準備
    gray_prev = gray_next.copy()
    feature_prev = good_next.reshape(-1, 1, 2)
    end_flag, frame = cap.read()

# 終了処理
cv2.destroyAllWindows()
cap.release()
```

できた！