OpenCVでリアルタイム顔検知を試してみる

　OpenCVは画像処理用のライブラリですが、機械学習の機能も持っています。特に有名なのがHaar-cascadeを使った顔検知で、学習機と検出器、両方の機能が用意されています。
　検出器は自作することも可能とのことですが、デフォルトでもいくつか用意されているので今回はそれを使って顔検知を試みてみます。

#まずは静止画
　今回の目標は、リアルタイムでの顔検知ですが、まずは公式のチュートリアルに沿って静止画の顔検知を試みてみます。

> [Haar Cascadesを使った顔検出](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_objdetect/py_face_detection/py_face_detection.html#)

　顔の写真ですが、公式に載っているもののオリジナルが見つからなかったので、今回もレナさんに登場していただきます。Lenna.pngという名前で保存してください。

![Lenna.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/448703/49951fd2-253f-2028-ed1c-292649320f38.png)

##コード
　コードは以下のとおりです。※公式から引用したものを一部改編


```python
import numpy as np
import cv2

face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

img = cv2.imread('Lenna.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x,y,w,h) in faces:
    img = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
    roi_gray = gray[y:y+h, x:x+w]
    roi_color = img[y:y+h, x:x+w]
    eyes = eye_cascade.detectMultiScale(roi_gray)
    for (ex,ey,ew,eh) in eyes:
        cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)

cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
```
　xmlファイルは、OpenCVをインストールした際に一緒にダウンロードされていますが、上記のコードのままではパスの指定がソースファイルと同じディレクトリに置かれていることになってしまっているため、検索で探し出してパス指定をしてやるなり、同じディレクトリにxmlファイルを置くなりして対応してください。
　検知は`detectMultiScale()`で行っています。まずface_cascadeで顔を検知して四角で囲み、その後その範囲内でeye_cascadeを使って目を検出し、四角を描画する、というプロセスを取っています。
　`detectMultiScale()`は指定されたカスケード検出器を使って、対象を検知して始点の座標(x,y)と幅・高さ(w,h)を返します。なお、detectMultiScale()が返すのは配列で、同じ写真の中に複数人映っている場合はその分だけ(x,y,w,h)が返ってきます。forループになっているのはそういう理由です。

##実行結果

![Lenna_face.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/448703/fa135930-555c-b18d-6bd3-957f1b26541e.png)

　顔と目をうまく囲うことができています。しかも、正面顔の場合、どうやら長方形ではなく**必ず正方形で囲む**ようになっている模様。これはとても良い。続いて、リアルタイム検知を試してみます。

#リアルタイム検知
　カメラからの画像取得自体は簡単です。以前記事にしたので[こちら](https://qiita.com/Ka-k/items/88b839ebec630dbe7474#%E3%82%AB%E3%83%A1%E3%83%A9%E3%81%AE%E6%98%A0%E5%83%8F%E3%82%92%E8%A1%A8%E7%A4%BA)もご覧ください。あとは、今回作った処理を加えてあげれば良いのですが、今まで作ってきたプログラムと比べてメモリを食いやすいらしく、結構簡単にハングします。パラメータ調整の際はお気をつけください。

##コード
```python
import numpy as np
import cv2

face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

cap = cv2.VideoCapture(1) 

while(True):
    ret, frame = cap.read()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    for (x,y,w,h) in faces:
        frame = cv2.rectangle(frame,(x,y),(x+w,y+h),(255,0,0),2)
        roi_gray = gray[y:y+h, x:x+w]
        roi_color = frame[y:y+h, x:x+w]
        eyes = eye_cascade.detectMultiScale(roi_gray)
        for (ex,ey,ew,eh) in eyes:
            cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
 
    cv2.imshow("frame",frame)

    k =  cv2.waitKey(1) & 0xFF # キー操作取得。64ビットマシンの場合,& 0xFFが必要
    prop_val = cv2.getWindowProperty("frame", cv2.WND_PROP_ASPECT_RATIO) # ウィンドウが閉じられたかを検知する用

# qが押されるか、ウィンドウが閉じられたら終了
    if k == ord("q") or (prop_val < 0):
        break

cap.release()
cv2.destroyAllWindows()
```
　`VideoCapture(1)`の()内の数字はカメラ番号なので、表示させたいカメラの番号を調べて、該当のものに書き換えてください。
　内蔵のカメラなら0か1、USBカメラなら1以降の数字が割り振られていると思います。普通に数字を0から順番に変えつつ総当たりで調べて良いと思います。
　これでカメラの映像が取得でき、人の顔を映して四角でちゃんと囲まれていれば成功です。

#まとめ
　人の顔を検知する、というのはかなり簡単にできました。ポイントになるのは`detectMultiScale()`が返すのが座標と幅、高さ、という点。
　つまりトリミングができるということなので、これを活用すればカメラから学習セット用の顔画像を取得することも簡単にできそうです。
　一方、デフォルトの検出器は精度にやや難があるので、そちらを自作する方法を勉強するのも面白そうです。