OpenCV Haar Cascades で顔認識

# はじめに
- 最終的なWebアプリケーションに至る作業なので、資料を公開することにしました。
- なので、基本的な話のみになります。
- ソース一式は [ここ](https://github.com/maedamikio/public/tree/master/abe_or_ishihara/learning) です。
- 参考
    - [Face Detection using Haar Cascades](https://docs.opencv.org/3.4.6/d7/d8b/tutorial_py_face_detection.html)


# ライブラリのインストール
- `Ubuntu` なら `$ apt install python3-opencv` でインストールする場合もあると思います。
- また、最終的なWebアプリケーションの環境は、`Heroku` を想定しています。
- Heroku の場合は、Ubuntu を利用できますが、`python3-opencv` は、うまく動作しません。
- 下記の `pip` で `opencv-python` なら、最新バージョン、マルチコアの利用など、良い事が多いです。

```
$ pip install opencv-python
```


# 設定ファイル config.py
- ダウンロードした画像ファイルのフォルダ、顔認識した画像ファイルのフォルダを指定しています。
- 顔認識に利用する `Haar Cascades` `xml` ファイルの場所を指定します。
- `CLASSES` を利用して、該当フォルダの画像を順次処理します。

```
$ cat config.py

CLASSES = [
    '安倍乙',
    '石原さとみ',
    '大原優乃',
    '小芝風花',
    '川口春奈',
    '森七菜',
    '浜辺美波',
    '清原果耶',
    '福原遥',
    '黒島結菜'
]

BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_PATH = os.path.join(BASE_PATH, 'data')
LINK_PATH = os.path.join(DATA_PATH, 'link')
DOWNLOAD_PATH = os.path.join(DATA_PATH, 'download')
FACE_PATH = os.path.join(DATA_PATH, 'face')

HAARCASCADE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'haarcascade_frontalface_default.xml')
```


# haarcascade_frontalface_default.xml
- 以下から `xml` ファイルダウンロードします。

```
$ wget https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml
```

# 顔認識プログラム save_face_image.py

## ダウンロード画像の一覧の取得、Haar Cascade の読み込み等
- `query` には、`CLASSES` が順次与えられます。
- ダウンロード画像のパスの確認、顔画像のパスの作成をします。
- Haar Cascade を読み込みます。
- `glob` でダウンロード画像の　`*.jpeg` を検索し、一覧化します。

```
def detect(query):
    """画像の読み込み、顔画像の検出、顔画像の保存."""

    download_path = os.path.join(DOWNLOAD_PATH, query)
    if not os.path.isdir(download_path):
        print('no download path: {}'.format(download_path))
        return

    os.makedirs(os.path.join(FACE_PATH, query), exist_ok=True)

    face_cascade = cv2.CascadeClassifier(HAARCASCADE_PATH)

    download_list = glob.glob(os.path.join(download_path, '*.jpeg'))
    download_list.sort()
```

## 顔認識
- ダウンロード画像を順次処理します。
- 画像はカラーからグレースケールに変換し、Haar Cascade で顔認識を実施します。
- `scaleFactor` は、画像から顔認識をする時に、画像を異なるサイズに変更し、複数回顔認識をするための値です。
- `scaleFactor` が小さいと、顔以外を検知したり、時間がかかったりします。逆に、大きい値だと、検知出来なかったりします。
- 初期値は、`1.1` ですが、誤検知と非検知を鑑み、`1.2` にしています。
- `minNeighbors` は、上記の `scaleFactor` に関連があります。
- `scaleFactor` は、画像の同じ箇所を複数回確認し、検知することになります。何回検知した時、顔認識したことにするかを `minNeighbors` で指定しています。この値が大きいと、複数回検知しないと、顔認識したと見なされないので、見落としが発生します。
- 今回は、`3` にしました。この値は、デフォルト値です。忘れない様に、あえて指定しています。
- この値の調整は、状況に応じて調整が必要なので、難しいですね。。。

```
    for download in download_list:

        img = cv2.imread(download)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=3)

        if len(faces) < 1:
            print('query: {}, download: {}, face num: 0'.format(query, os.path.basename(download)))
            continue
```

## 顔画像の保存
- 顔画像の位置元に、画像を取り出し、顔画像のフォルダに保存します。
- ファイル名は、ダウンロード画像を元にしています。
- 例えば、ダウンロード画像が `0001.jpeg` なら 顔画像は `0001-0001.jpeg` になります。
- 複数人の顔画像が認識されるので、`0001-0002.jpeg` `0001-0003.jpeg` と言う感じで保存しています。

```
        for num, (x, y, w, h) in enumerate(faces, start=1):
            face = img[y:y+h, x:x+w]

            filename = os.path.join(FACE_PATH, query, os.path.basename(download).split('.')[0] + '-{:04d}.jpeg'.format(num))
            cv2.imwrite(filename, face)
            print('query: {}, download: {}, filename: {}'.format(query, os.path.basename(download), os.path.basename(filename)))
```


# 顔画像の例
- 以下の様な感じです。誤検知がありますね。この後が辛い。。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/acced4a0-a3c1-dd40-c7aa-24aa8e8e4f23.png)


# おわりに
- 顔認識を実施しました。
- 次回は、顔認識の簡易管理アプリと誤検知の削除をする予定です。