openCVの顔検出でパラメータを指定して手っ取り早く検出精度を高める

openCVを使って顔検出する時に、**「1.学習済モデル(cascadeファイル)をどれにするか？」、「2.detectMultiScaleメソッドのパラメータをどうするか？」**は必ず通る迷いどころかと思います。検出精度を高めるには上記の2つは必須で、いちいちプログラムを書き換えて実行しているのは効率が悪いです。手っ取り早く検出精度を高めるための(プログラム変更せずに)パラメータ指定して実行する方法の紹介です。
以下はopenCVに関する他参考記事です。
> - [OpenCV3.3とPython3.6をAnacondaでWindows10へインストール](http://qiita.com/FukuharaYohei/items/5d49938ffd33d198f0c0)
- [【入門者向け解説】openCV顔検出の仕組と実践(detectMultiScale)](http://qiita.com/FukuharaYohei/items/ec6dce7cc5ea21a51a82)
- [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)

#パラメータのライブライリargparse
argparseはPython 標準ライブラリの一部で、プログラムの引数使用を助けてくれます。
詳しく知りたい方は[公式ヘルプ](https://docs.python.jp/3/library/argparse.html)と[公式チュートリアル](https://docs.python.jp/3/howto/argparse.html#)を参照ください。

##基本的なargparseの使い方
シンプルなargparseの使い方です。こんなコードを書いて、"argparse_test01.py"という名前で保存します([チュートリアルの「位置指定引数の導入」](https://docs.python.jp/3/howto/argparse.html#introducing-positional-arguments)から引用)。

```py3
import argparse

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", 
                    help="display a square of a given number",
                    type=int)
args = parser.parse_args()
print(args.square**2)
```

まずは、コマンドラインでヘルプを見ます。

```none
python argparse_test01.py --h
```

すると定義しているパラメータの説明を返してくれます。

```none
usage: argparse_test01.py [-h] square

positional arguments:
  square      display a square of a given number

optional arguments:
  -h, --help  show this help message and exit
```

では、コマンドラインでプログラム実行します。

```none
python argparse_test01.py 4
```

するとパラメータとして指定した"4"を2乗した値"16"を返してくれます。

##パラメータを配列に入れる
[tensorflowのチュートリアル](https://github.com/tensorflow/tensorflow/blob/r1.2/tensorflow/examples/tutorials/mnist/fully_connected_feed.py)でも使われている形で、パラメータを配列に入れました。

```py3:argparse_test02.py
import argparse

# 基本モデル
FLAGS = None

def main():
    print(FLAGS.parameter1)
    print(FLAGS.parameter2)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--parameter1',
        type=float,
        default=0.01,
        help='Parameter1.'
  )
    parser.add_argument(
        '--parameter2',
        type=int,
        default=2000,
        help='Parameter2.'
  )

FLAGS, unparsed = parser.parse_known_args()
main()
```

下記のコマンドで実行するとそのままパラメータの値を出力してくれます。

```none
python argparse_test02.py --parameter1 3.23 --parameter2 112
```

ちなみに2つ指定することで省略形も定義できます。この場合は"-p"でパラメータ指定もできます。

```python
    parser.add_argument(
        '-p',
        '--parameter',
        type=int,
        default=2000,
        help='Parameter2.'
  )
```

#顔検出プログラム
冒頭で書きましたが、「1.学習済モデル(cascadeファイル)」と「2.detectMultiScaleメソッドのパラメータ」、あとついでに画像ファイル名をパラメータ化します。
コードはこんな感じ。

```py3:openCVWithParameter01.py
import cv2
import argparse

# 基本的なモデルパラメータ
FLAGS = None

# 学習済モデルの種類
cascade = ["default","alt","alt2","tree","profile","nose"]

# 直接実行されている場合に通る(importされて実行時は通らない)
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--cascade",
        type=str,
        default="default",
        choices=cascade,
        help="cascade file."
  )
    parser.add_argument(
        "--image_file",
        type=str,
        default="cut_source0.jpg",
        help="image file."
  )
    parser.add_argument(
        "--scale",
        type=float,
        default=1.3,
        help="scaleFactor value of detectMultiScale."
  )
    parser.add_argument(
        "--neighbors",
        type=int,
        default=2,
        help="minNeighbors value of detectMultiScale."
  )
    parser.add_argument(
        "--min",
        type=int,
        default=30,
        help="minSize value of detectMultiScale."
  )

# パラメータ取得と実行
FLAGS, unparsed = parser.parse_known_args()        

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

if   FLAGS.cascade == cascade[0]:#"default":
    cascade_path = "./models/haarcascade_frontalface_default.xml"
elif FLAGS.cascade == cascade[1]:#"alt":
    cascade_path = "./models/haarcascade_frontalface_alt.xml"
elif FLAGS.cascade == cascade[2]:#"alt2":
    cascade_path = "./models/haarcascade_frontalface_alt2.xml"
elif FLAGS.cascade == cascade[3]:#"tree":
    cascade_path = "./models/haarcascade_frontalface_alt_tree.xml"
elif FLAGS.cascade == cascade[4]:#"profile":
    cascade_path = "./models/haarcascade_profileface.xml"
elif FLAGS.cascade == cascade[5]:#"nose":
    cascade_path = "./models/haarcascade_mcs_nose.xml"

# 使用ファイルと入出力ディレクトリ
image_path  = "./inputs/"  + FLAGS.image_file
output_path = "./outputs/" + FLAGS.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=FLAGS.scale, minNeighbors=FLAGS.neighbors, minSize=(FLAGS.min, FLAGS.min))

#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)
```

あとは実行するだけです。これで、**いちいちプログラムを書き換えながら実行する手間が減って、効率化できるはず！**

```none
python openCVWithParameter01.py --scale 1.3 --neighbors 1 --image_file "cut_source1.jpg" --cascade "nose" --min 50
```

もちろん、パラメータ指定しないでデフォルト値実行も可能です。

```none
python openCVWithParameter01.py
```