【入門者向け解説】openCV顔検出の仕組と実践(detectMultiScale)

#概要と環境
openCVの顔検出方法について調べて試してみました。入門者向けに解説します。
以下はopenCV関連記事です。
> - [OpenCV3.3とPython3.6をAnacondaでWindows10へインストール](http://qiita.com/FukuharaYohei/items/5d49938ffd33d198f0c0)
> - [openCVの顔検出でパラメータを指定して手っ取り早く検出精度を高める](http://qiita.com/FukuharaYohei/items/116932920c99a5b73b32)
- [openCVで複数画像ファイルから顔検出をして切り出し保存](http://qiita.com/FukuharaYohei/items/457737530264572f5a5b)
- [openCVで効率的に大量画像を顔検出するためのtips](http://qiita.com/FukuharaYohei/items/457737530264572f5a5b)

検証環境は下記のとおりです。
![00.ArchitectureopenCV.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/95b56a0b-91ad-1f57-ac13-326f1e0edf6e.jpeg)

#openCVの顔検出でできたこと
人物画像に対して顔検知して四角枠で囲みました。
![10.Sample00.jpg](https://qiita-image-store.s3.amazonaws.com/0/134273/bbce9be3-664a-fc04-1adb-74f6c30e8f03.jpeg)

複数人でも大丈夫。すごい！
![10.Sample01.jpg](https://qiita-image-store.s3.amazonaws.com/0/134273/82c3ee89-b57c-2476-c0c4-a9a4512cffa0.jpeg)

変顔だって検知します！(後ろの人も検知できてます)
![10.Sample02.jpg](https://qiita-image-store.s3.amazonaws.com/0/134273/4ebe3dcd-7a6c-0517-2cdf-43ff9b70929e.jpeg)

顔検知から鼻検知に切り替えることもできます。
![10.Sample03_Nose.jpg](https://qiita-image-store.s3.amazonaws.com/0/134273/e6e0a2ac-dd15-a829-43b8-5e84394070d2.jpeg)

#oepnCVの顔検出の仕組み
論文や具体的なアルゴリズムを見て調べて確認していないので、以降の解説は誤りがあるかもしれません(指摘いただけたら嬉しいです)。半日程度で筆者が理解した内容です。
おそらく動画[「Viola Jones face detection and tracking explained」](https://www.youtube.com/watch?v=WfdYYNamHZ8)を見れば、かなり理解が深まるでしょうが、1.5時間という長時間を悟って心が折れました・・・

##顔検出方法概要
判定画像(読み込んだ画像)全体から一部を切り取って様々な基準で判定していきます。1回でも「顔でない」と判定されれば、後続の判定は行わず、次の画像の一部に対して判定器にかけていけいます。全判定をしないことにより、顔検出処理の高速化を実現しています(個々の判定精度は低いですが、多く重ねることにより全体として精度を保ちます)。
![20.DetectOverview.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/c354c448-3ac9-d4e5-9038-d93298bd2625.jpeg)

左側の切り取りプロセスがわかりやすいように、openCVのAPIのパラメータを変えて、あえて誤検知させてみました。非常に多くの枠で顔を検知している、つまり少なくてもこれだけの枠部分は判定をしているということです。判定画像全体を切り取って個々に判定器にかけているのがわかります。
![30.Sample01.many.jpg](https://qiita-image-store.s3.amazonaws.com/0/134273/e72cb601-1047-cc50-e4ef-61e03cd83d8d.jpeg)

##判定方法
###Harr-like検出器概要
Haar-like検出器を使って顔判定します。判定プロセスはこんな感じ。
![40.HarrLike.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/02fc48db-822c-e0b6-dd62-19969b45b1a8.jpeg)
鼻中央部分は光があたるので明るく、鼻の周辺は影ができて暗くなるため、上記の判定方法が有効です。判定単体で見ると単純で素早くできますが、その半面、判定精度は低いです。そのため多くの判定をすることで全体としての精度を高めます。
上記のような判定に使うHaar-like検出器は大きく3種類があります。
![HarrLike](http://www.bogotobogo.com/python/OpenCV_Python/images/FaceDetection/HaarFeatures.png)

###判定方法の弱点
弱点として正面の顔でないと検出精度が著しく落ちます。例えば、下図のような横顔は検出できませんでした。
![30.Sample02Angles.jpg](https://qiita-image-store.s3.amazonaws.com/0/134273/38b70247-2fbc-c74c-bf8d-6908cdbfb88a.jpeg)

また、顔を回転させても検出できませんでした。
![30.Sample02.angles.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/e6d08a17-3245-5a86-0c0e-cf56bc27a019.jpeg)

#Pythonコード
以下のコードを使って検証しました。試行錯誤の跡も残しています。最後に載せているリンクからそのままコピペした部分も多いです。学習済モデル実行時に指定できるコードは記事[「openCVの顔検出でパラメータを指定して手っ取り早く検出精度を高める」](http://qiita.com/FukuharaYohei/items/116932920c99a5b73b32)に書いています。また、フォルダにある複数画像を読み込んで、検出した顔部分を切り出して保存するコードは記事[「openCVで複数画像ファイルから顔検出をして切り出し保存」](http://qiita.com/FukuharaYohei/items/457737530264572f5a5b)を参照ください。

```py3
import cv2

# 分類器ディレクトリ(以下から取得)
# https://github.com/opencv/opencv/blob/master/data/haarcascades/
# https://github.com/opencv/opencv_contrib/blob/master/modules/face/data/cascades/

cascade_path = "./models/haarcascade_frontalface_default.xml"

# 他のモデルファイル(参考)
#cascade_path = "./models/haarcascade_frontalface_alt.xml"
#cascade_path = "./models/haarcascade_frontalface_alt2.xml"
#cascade_path = "./models/haarcascade_frontalface_alt_tree.xml"
#cascade_path = "./models/haarcascade_profileface.xml"
#cascade_path = "./models/haarcascade_mcs_nose.xml"

# 使用ファイルと入出力ディレクトリ
image_file = "test.jpg"
image_path = "./inputs/" + image_file
output_path = "./outputs/" + image_file

# ディレクトリ確認用(うまく行かなかった時用)
#import os
#print(os.path.exists(image_path))

#ファイル読み込み
image = cv2.imread(image_path)

#グレースケール変換
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

#カスケード分類器の特徴量を取得する
cascade = cv2.CascadeClassifier(cascade_path)

#物体認識（顔認識）の実行
#image – CV_8U 型の行列．ここに格納されている画像中から物体が検出されます
#objects – 矩形を要素とするベクトル．それぞれの矩形は，検出した物体を含みます
#scaleFactor – 各画像スケールにおける縮小量を表します
#minNeighbors – 物体候補となる矩形は，最低でもこの数だけの近傍矩形を含む必要があります
#flags – このパラメータは，新しいカスケードでは利用されません．古いカスケードに対しては，cvHaarDetectObjects 関数の場合と同じ意味を持ちます
#minSize – 物体が取り得る最小サイズ．これよりも小さい物体は無視されます
facerect = cascade.detectMultiScale(image_gray, scaleFactor=1.1, minNeighbors=2, minSize=(30, 30))

#print(facerect)
color = (255, 255, 255) #白

# 検出した場合
if len(facerect) > 0:
    
    #検出した顔を囲む矩形の作成
    for rect in facerect:
        cv2.rectangle(image, tuple(rect[0:2]),tuple(rect[0:2]+rect[2:4]), color, thickness=2)

    #認識結果の保存
    cv2.imwrite(output_path, image)
```

##使用した学習済モデルのファイル取得
Git Hubから[顔検出](https://github.com/opencv/opencv/tree/master/data/haarcascades)と[鼻検出](https://github.com/opencv/opencv_contrib/blob/master/modules/face/data/cascades/haarcascade_mcs_nose.xml)の学習済モデルファイルを取得して使っています。

検証していませんが、どの学習済モデルを使うかは[stackoverflow](https://stackoverflow.com/questions/4440283/how-to-choose-the-cascade-file-for-face-detection)と記事[「OpenCV 使用可能なCascadeClassifierの種類と効果」](http://symfoware.blog68.fc2.com/blog-entry-1556.html)が参考になります。

##顔検出をするメソッド"cascade.detectMultiScale"
顔検出に使っているのはメソッド"cascade.detectMultiScale"です。[公式サイト](http://docs.opencv.org/java/3.0.0/org/opencv/objdetect/CascadeClassifier.html#detectMultiScale)で見るとわかりますが、類似メソッドとして"cascade.detectMultiScale2"と"cascade.detectMultiScale3"がありますが、パラメータが違うのみで、基本的な処理は変わらないようです。[stackoverflow](https://stackoverflow.com/questions/45428619/three-detectmultiscale-methods-in-opencv)で聞きました。
**パラメータ"scaleFactor"と"minNeighbors"が検出精度を高める上で非常に重要です。**筆者の試行錯誤は記事[「openCVで効率的に大量画像を顔検出するためのtips」](http://qiita.com/FukuharaYohei/items/457737530264572f5a5b)に書き留めています。
以下の情報を参考にして理解しました。
> - [物体検出（detectMultiScale）をパラメータを変えて試してみる（scaleFactor編）](http://workpiles.com/2015/04/opencv-detectmultiscale-scalefactor/)
> - [物体検出（detectMultiScale）をパラメータを変えて試してみる（minNeighbors編）]
(http://workpiles.com/2015/04/opencv-detectmultiscale-minneighbors/)
> - [OpenCV detectMultiScale() minNeighbors parameter
](https://stackoverflow.com/questions/22249579/opencv-detectmultiscale-minneighbors-parameter)

#参考リンク
今回、勉強に使ったサイトをリンクとして載せておきます。

|サイト|コメント|
|:--|:--|
|[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#face-detection)|公式チュートリアルの日本語訳|
|[サルでもわかる顔検出の原理](http://d.hatena.ne.jp/Cru/20100613/1276436975)|顔検出の原理についてわかりやすい解説あり|
|[顔の切り出しと認識](http://www.ime.info.hiroshima-cu.ac.jp/~hiura/lec/ime/04.pdf)|スライド形式の顔検出わかりやすい説明あり|
|[OBJECT DETECTION : FACE DETECTION USING HAAR CASCADE CLASSFIERS](http://www.bogotobogo.com/python/OpenCV_Python/python_opencv3_Image_Object_Detection_Face_Detection_Haar_Cascade_Classifiers.php)|コードを交えた物体検出の詳しい説明(英語)|
|[Heroku + OpenCVで簡易顔検出API](http://memo.sugyan.com/entry/20151203/1449137219)|顔を傾けて精度を高める方法あり|
|[Python+OpenCV で顔検出 - OpenCV に付属の評価器を試す](http://blog.mudatobunka.org/entry/2016/10/03/014520)|モデルとパラメータを変えた検証結果あり|
|[python+OpenCVで顔認識をやってみる](http://qiita.com/wwacky/items/98d8be2844fa1b778323)|プログラムのコピペ元|