openCVで複数画像ファイルから顔検出をして切り出し保存

openCVを使ってフォルダにある複数の画像ファイルから顔検出をして、顔部分を切り出して画像保存するプログラムを作ったので、その紹介です。
以下は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/116932920c99a5b73b32)
- [openCVで効率的に大量画像を顔検出するためのtips](https://qiita.com/FukuharaYohei/items/1e15d79c87f1de513eac)

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

#Pythonコード
記事[「openCVの顔検出でパラメータを使って素早く検出精度を高める」](http://qiita.com/FukuharaYohei/items/116932920c99a5b73b32)で解説したコードを流用して、画像ファイル名、画像数、使用学習済モデル、detelcMultiScaleのパラメータを実行時指定できるようにしています。
どの元ファイルと出力ファイルを出力ファイル名から識別できるようにしようかとも思ったのですが、この後でtensorFlowで学習するときに邪魔になりそうだったのでやめました。

```py3
import cv2, os, argparse, shutil

# 切り抜いた画像の保存先ディレクトリ
SAVE_PATH = "./outputs/"

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

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

# 直接実行されている場合に通る(importされて実行時は通らない)
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--cascade",
        type=str,
        default="alt",
        choices=CASCADE,
        help="cascade 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=80,
        help="minSize value of detectMultiScale."
  )
    parser.add_argument(
        "--input_dir",
        type=str,
        default="./input/",
        help="The path of input directory."
  )
    parser.add_argument(
        "--move_dir",
        type=str,
        default="/done/",
        help="The path of moving detected files."
  )

# パラメータ取得と実行
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"

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

# 顔検知に成功した数(デフォルトで0を指定)
face_detect_count = 0

# 顔検知に失敗した数(デフォルトで0を指定)
face_undetected_count = 0

# フォルダ内ファイルを変数に格納(ディレクトリも格納)
files =  os.listdir(FLAGS.input_dir)

# 成功ファイルを移動いない場合は、出力用のディレクトリが存在する場合、削除して再作成
if FLAGS.move_dir == "":
    if os.path.exists(SAVE_PATH):
        shutil.rmtree(SAVE_PATH)
    os.mkdir(SAVE_PATH)

print(FLAGS)

# 集めた画像データから顔が検知されたら、切り取り、保存する。
for file_name in files:

    # ファイルの場合(ディレクトリではない場合)
    if os.path.isfile(FLAGS.input_dir + file_name):

        # 画像ファイル読込
        img = cv2.imread(FLAGS.input_dir + file_name)
        
        # 大量に画像があると稀に失敗するファイルがあるのでログ出力してスキップ(原因不明)
        if img is None:
            print(file_name + ':Cannot read image file')
            continue

        # カラーからグレースケールへ変換(カラーで顔検出しないため)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        # 顔検出
        face = faceCascade.detectMultiScale(gray, scaleFactor=FLAGS.scale, minNeighbors=FLAGS.neighbors, minSize=(FLAGS.min, FLAGS.min))

        if len(face) > 0:
            for rect in face:
                # 切り取った画像出力
                cv2.imwrite(SAVE_PATH + str(face_detect_count) + file_name, img[rect[1]:rect[1] + rect[3], rect[0]:rect[0] + rect[2]])
                face_detect_count = face_detect_count + 1
                
            # 検出できたファイルは移動
            if FLAGS.move_dir != "":
                shutil.move(FLAGS.input_dir + file_name, FLAGS.input_dir + FLAGS.move_dir)
        else:
            print(file_name + ':No Face')
            face_undetected_count = face_undetected_count + 1
            
print('Undetected Image Files:%d' % face_undetected_count)
```

実行前にinputsとoutputsフォルダを直下に作って、inputsフォルダに画像を入れておいてください。
コマンドラインで実行します。初期値を与えているので、何もパラメータを指定しなくても大丈夫です。

```none
python openCVCutAndSave01_20170804.py
```

パラメータを与えて実行も当然できます。

```none
python openCVCutAndSave01_20170804.py --cascade "alt" --image_file "cut_source" --image_count 4 --scale 1.1 --neigbors 3 --min 50
```
inputsフォルダの画像を切り出してoutputsフォルダに出力し、顔検出できた画像はdoneフォルダに移動しています。大量画像処理をすると検出有無によってフォルダをわけると便利なので、こんな仕様にしています。
![10.openCVResult.JPG](https://qiita-image-store.s3.amazonaws.com/0/134273/2e93a476-e9db-da2f-7318-d8e3215793c4.jpeg)
詳しくは記事[「openCVで効率的に大量画像を顔検出するためのtips」](https://qiita.com/FukuharaYohei/items/1e15d79c87f1de513eac)に書き留めています。