自作データセットを作るためにOpenCVで顔認証をする

##1. はじめに
前回の記事(<a href='https://qiita.com/DancingEnginee1/items/6c00cac61633dc51521b'>自作データセットを作るためにSeleniumで画像スクレイピングをしてみた(Python)</a>)では、人の顔画像の自作データセットを作るためにSeleniumを使用して、Yahoo画像検索のページから画像を取得する方法を紹介しました。本記事では、その続きとして取得した画像をOpecCVで顔認証し、画像処理してデータセットにする部分を紹介したいと思います。


##2. 環境
OS : macOS Mojave ver. 10.14.6
言語 : Python 3.7.2

##3. 今回やりたいことの流れ
①スクレイピングで画像を取得。←前回はここまでやった。

②OpenCVで画像の顔認証をして、顔の部分を取り出し、トリミングする。

③顔画像をnumpy配列にして、ラベル付けする。

④画像データをデータセットとして保存する。

前回は①の部分を説明しました。
今回は②〜④の部分を解説します。

##4. OpenCVで顔認証する
顔認証については<a>こちら</a href='https://qiita.com/FukuharaYohei/items/ec6dce7cc5ea21a51a82'>の記事を参考にしています。
顔認証の部分でやりたいことは、
![img41.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/473300/8db1674e-d676-fa54-0ca4-56a9ed0316ae.jpeg)
人物画像から、
![jim.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/473300/01370017-fe32-fd62-5808-ee7114a94b9d.jpeg)
このように、顔の部分だけを取り出すという処理です。
今は分かりやすいように赤線をつけていますが、実際は赤線の内側部分だけをトリミングしています。

まず、OpenCVで顔認証をする準備として、顔検出の学習済みモデルファイル(カスケードファイル)を取得します。
モデルファイルは<a>こちら</a href='https://github.com/opencv/opencv/tree/master/data/haarcascades'>のGitHubから取得します。
今回はfrontal_face_alt2.xmlというファイルを使用しています。
これは、自分の画像ファイルを試して一番顔を検出する精度が良さそうだったからです。

それでは、顔検出をするコードを書いていきます。

```Python:face_detection.py
import cv2
import os

# カスケードファイルの読み込み
os.chdir('カスケードファイルを保存したpath')
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_alt2.xml')

# 入出力ファイルのpathを確認
image_file = 'file_name.jpg'
input_path = './inputs/' + image_file
output_path = './outputs/' + image_file

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

# グレースケールの画像に変換
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# カスケードファイルで顔認証
face_rect = face_cascade.detectMultiScale(gray, minSize=(10, 10), maxSize=(200, 200))

if len(face_rect) >= 1:
    for (x, y, w, h) in face_rect:
        trimmed_image = image[y:y+h, x:x+w]
        cv2.imwrite(output_path, trimmed_image)
```

正しく顔認証ができればface_rectには４つの戻り値がかえってきます。
(顔が写っていない、顔と認証されなかった場合にはNoneがかえってきます。)
先ほど、顔認証の例で示した赤い四角形の座標である
(四角形のx座標, 四角形のy座標, 四角形のx方向の幅, 四角形のy方向の高さ)がかえってきます。
なので、画像から顔の部分だけを取り出したい場合には、元の画像からコードのようにトリミングをします。
また、この時にトリミングした画像と元画像の変数名を同じにするとエラーが発生する可能性があります。
face_rectでかえってくる戻り値は１つの組に限らず、複数かえってくることがあります。
(画像内に複数の顔が写っている場合、または顔でない部分が顔と誤認識された場合)
その時に、トリミングした画像と元画像の変数名が同じだと、２つ目以降の四角形をトリミングする時に、すでにトリミングされた画像から、画像を切り出そうとするためエラーが発生します。

##5. 画像データにラベル付けして、データセットとして保存する
先ほど使用したコードに少し付け足して、画像データをデータセットとして保存できるようにします。

```Python:make_dataset.py
import cv2
import os
import glob
import numpy as np

# カスケードファイルの読み込み
os.chdir('カスケードファイルを保存したpath')
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_alt2.xml')

X_data = []
Y_data = []
X_train = []
Y_train = []
X_test = []
Y_test = []

# 画像を保存しているファイルの１つ上の階層に移動する
os.chdir('PATH')

# 画像を保存しているファイルのパスをリスト形式で取得する
path_list = glob.glob('*')

for file_path in path_list:
    # 画像を保存しているファイルに移動する
    os.chdir('PATH')
    os.chdir('./{}'.format(file_path))

    # ファイルの名前によってラベルを変更する(今回はJapanese=0, Foreigner=1)
    if file_path == 'Japanese':
        label = 0
    elif file_path == 'Foreigner':
        label = 1

    # データセットにいれる画像のファイル名をリスト形式で取得する
    image_list = glob.glob('*.jpg')

    for image_name in image_list:

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

        # グレースケールの画像に変換
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

        # カスケードファイルで顔認証
        face_rect = face_cascade.detectMultiScale(gray, minSize=(10, 10), maxSize=(200, 200))

        if len(face_rect) >= 1:
            for (x, y, w, h) in face_rect:
                trimmed_image = image[y:y+h, x:x+w]
                trimmed_image = cv2.resize(trimmed_image, (64, 64))
                X_data.append(trimmed_image)
                Y_data.append(label)

        else:
            continue


X_data = np.array(X_data)
Y_data = np.array(Y_data)

# データをシャッフル
np.random.seed(seed=32)
np.random.shuffle(X_data)

np.random.seed(seed=32)
np.random.shuffle(Y_data)

# データを訓練用とテスト用に分ける。
for i in range(len(X_data)):
    if i <= len(X_data) * 0.8:
        X_train.append(X_data[i])
        Y_train.append(Y_data[i])
    else:
        X_test.append(X_data[i])
        Y_test.append(Y_data[i])

xy = (X_train, Y_train, X_test, Y_test)

# データセットを保存する
np.save('データセットを保存する場所', xy)
```

上のコードを実行する時には、ファイル構造は、画像のクラス毎にフォルダを分けておいてください。

フォルダからファイルを取得する際には、globモジュールを使用しています。
globモジュールを使用することで、現在のフォルダ内にあるファイル名を取り出すことができます。
ファイル名は正規表現で指定することで特定の名前のファイルだけを取り出すことができます。
例えば、上のコードのように('*.jpg')として取り出すと、後ろに.jpgとつくファイル全てを取り出すことができます。
→globモジュール、pythonの正規表現については<a href='https://note.nkmk.me/python-glob-usage/'>こちら</a>を参照してください。

上のコードのように画像のクラス毎に画像を取り込んでいると、同じクラスの画像が偏ってしまうため、データをシャッフルする必要があります。
この時、乱数のseed値を指定しないと画像データをラベルが一致しなくなるので注意してください。
あとは、画像データとラベルデータを訓練データをテストデータに分けて、保存して完成です。

##6. まとめ
今回は、前回の続きで、スクレイピングで取得した画像を顔認証、ラベル付けしてデータセットにするところを説明しました。分かりにくい箇所、改善した方が良いところなどがあれば、指摘お願いします。