【Python】OpenCVによる顔検出 (Haar Cascade)

#はじめに
今回はOpenCVを用いて、顔か検出をやってみたいと思います。顔検出の手法は色々あるのですが、今回はHaar cascadeを用います。

#環境
MacOS Mojave
Python 3.7

#Haar Casecade?
顔っぽさを表す特徴量 (Haar特徴量) から、これは顔であるかないかを判断する分類器のことです。この分類器は、高速化の為に複数の分類器が結合してできていることから、Cascade (結合) 分類器と呼ばれます。Haar特徴量は以下のような白黒の特徴量が用いられます。
![スクリーンショット 2020-03-24 16.45.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288555/cd68087c-a1ec-9936-add4-b51e391edc43.png)
図. Haar特徴量
(画像引用元:http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_objdetect/py_face_detection/py_face_detection.html)

なんだこの白黒は、、、と思ったんじゃないでしょうか。少し人間の顔がどうなっているのかを考えてみましょう。

人間の顔って高度に抽象化すると、大体は以下のようになるのではないでしょうか (ならないか...w)
![スクリーンショット 2020-03-24 16.53.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288555/e80b463d-cd82-e380-76d2-67b94dfece8a.png)
図. 高度に抽象化された顔

上図から、例えば目元は左から"黒白黒"の色の配置になっていると思いませんか。
![スクリーンショット 2020-03-24 17.01.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288555/8c81a79f-bb06-743c-e102-b2efcc94d5ef.png)
図. 高度に抽象化された顔の白と黒の配置

この高度に抽象化された顔っぽい白と黒の配置を表したのがHaar特徴量です。
入力画像から任意の領域を切り出し、この特徴量がたくさん存在すれば、顔だと判断します。
![スクリーンショット 2020-03-24 16.45.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288555/22cef118-7236-b0da-bc95-91d1433cf7e3.png)
図. 顔だと判断された画像 (目元など、Haar特徴量がたくさん存在する)
(画像引用元:http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_objdetect/py_face_detection/py_face_detection.html)

ここではざっくりとしたイメージの解説だけなので、詳細を知りたい方は以下の論文を読んでみてください。
https://www.cs.cmu.edu/~efros/courses/LBMV07/Papers/viola-cvpr-01.pdf

#顔検出
では、haar cascadeを用いて人の顔を認識してみたいと思います。画像は以下の画像 (woman.jpg) を使用しました。
![woman.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288555/82a8b182-199c-5a09-7474-a614472708ca.jpeg)

実行したコードは以下のようになります。

```Python:
import cv2
import matplotlib.pyplot as plt

#画像の読み込み (画像1066x1600)
img = cv2.imread("woman.jpg")
#顔のカスケード分類器を読み込む
face_cascade = cv2.CascadeClassifier("haarcascades/haarcascade_frontalface_default.xml")
#画像をグレースケールにする
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#顔検出を実行!
faces = face_cascade.detectMultiScale(gray)
#facesに顔の位置が入っているので、for文で読み取る
for (x,y,w,h) in faces:
    #矩形を顔の位置に矩形を描画する
    img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),10)
#色の順番を変更する
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#出力
plt.imshow(img)
plt.show()
```
結果
![スクリーンショット 2020-03-24 17.27.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288555/f23a0fde-0792-29ca-275c-a41e04852c56.png)

ちゃんと認識されてますね！
次にコードの中のポイントだけ解説していきます。

##カスケード分類器の読み込み

```Python:
#顔のカスケード分類器を読み込む
face_cascade = cv2.CascadeClassifier("haarcascades/haarcascade_frontalface_default.xml")
```
この部分では、上記で述べた顔を分類するためのcascade分類器を読み込んでいます。もし

```
OpenCV(4.2.0) /Users/travis/build/skvark/opencv-python/opencv/modules/objdetect/src/cascadedetect.cpp:1689: error: (-215:Assertion failed) !empty() in function 'detectMultiScale'
```
のようなメッセージが出てきたら、多分カスケード分類器のxmlファイルががないので、以下からダウンロード (もしくはclone) してください。
https://github.com/opencv/opencv

ダウンロードしたら、data/haarcascadesのフォルダを自分のpythonファイル(またはipynb)と同じフォルダ内にいれて実行してください。

##矩形描画
```Python:
for (x,y,w,h) in faces:
    #矩形を顔の位置に矩形を描画する
    img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),10)
```
顔の位置は(x,y,幅,高さ)で出力されます。これは以下のようになります。
![スクリーンショット 2020-03-24 17.44.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288555/edc4d81f-baa8-5059-0d05-efe3a144c6c9.png)

cv2.reactangleの引数は以下のようになります。
cv2.reactangle(画像,(左上x,左上y),(右下x,右下y),(色),線の太さ)
![スクリーンショット 2020-03-24 17.47.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288555/3c433ea7-1cc4-48ab-b3d8-d44bfca0df3c.png)

#終わりに
カスケード分類器は顔だけでなく胴体や下半身、猫なども検出できます。近々その記事も執筆したいと思います。もし、記事に対してのご意見、誤りなどがあればコメントください。

#Twitter
Twitterでも発信をしてます。もしよければフォローお願いします、、、！
https://twitter.com/ryuji33722052