【顔分類その２】PythonとOpenCVを利用した画像ファイルからの顔の切り出し

## 目的

PythonとOpenCVを利用した画像ファイルからの顔の切り出しを試してみました。
64×64サイズの画像ファイルとして正面顔限定で顔を切り出します。

実装方法は https://blog.aidemy.net/entry/2017/12/17/214715 を参考にさせて頂きました。

## 参考

[【顔分類その１】PythonとGoogleカスタム検索APIを利用した画像ダウンロード](https://qiita.com/kerobot/items/f98b73cbd5f52c1fdac2)
[【顔分類その２】PythonとOpenCVを利用した画像ファイルからの顔の切り出し](https://qiita.com/kerobot/items/e3abe3f21808b4b584bd)
[【顔分類その３】PythonとOpenCVを利用した画像ファイルの水増し](https://qiita.com/kerobot/items/54bc1224424280150d1c)
[【顔分類その４】Pythonとkerasを利用した顔分類モデルの生成](https://qiita.com/kerobot/items/be4fd1a166073fbcff38)
[【顔分類その５】Pythonとkerasを利用して本田翼か佐倉綾音かを分類](https://qiita.com/kerobot/items/1ce75622d173d231d423)
[【顔分類その６】Djangoとkerasを利用した本田翼か佐倉綾音かを分類するWebアプリ](https://qiita.com/kerobot/items/65d4e1942a31c00317e2)

## 環境

* Windows 10 x64 1809
* Python 3.6.5 x64
* Power Shell 6 x64
* Visual Studio Code x64
* Git for Windows x64
* OpenCV 3.4.4

## 環境構築

* Windows 10 x64 に OpenCV 3.4.4 をインストールします。

> カスケードファイルのみ利用します。

* Windows 10 x64 に Python 3.6.x x64 をインストールします。

* 環境変数の PATH に、インストールしたPythonのフォルダとPython\Scriptsフォルダを設定しておきます。

* 下記手順で Python の仮想環境を構築し、有効化してpipをアップデートします。

```powershell
> python -m venv venv
> .\venv\Scripts\activate.ps1
(venv)> python -m pip install --upgrade pip
```

* pip でいろいろインストールします。

```powershell
(venv)> pip install pylint
(venv)> pip install python-dotenv
(venv)> pip install opencv-python
```

* インストールした OpenCV のカスケードファイルのパスを設定した.envファイルを作成します。

```python
CASCADE_FILE_PATH = "C:\opencv344\sources\data\haarcascades\haarcascade_frontalface_default.xml"
```

## プログラム作成

* 画像ファイルから顔を抜き出すプログラムを Python で書きます。

* ファイル名は「img_face_dt.py」とします。

```python
import os
import pathlib
import glob
import cv2
import settings

def load_name_images(image_path_pattern):
    name_images = []
    # 指定したパスパターンに一致するファイルの取得
    image_paths = glob.glob(image_path_pattern)
    # ファイルごとの読み込み
    for image_path in image_paths:
        path = pathlib.Path(image_path)
        # ファイルパス
        fullpath = str(path.resolve())
        print(f"画像ファイル（絶対パス）:{fullpath}")
        # ファイル名
        filename = path.name
        print(f"画像ファイル（名前）:{filename}")
        # 画像読み込み
        image = cv2.imread(fullpath)
        if image is None:
            print(f"画像ファイル[{fullpath}]を読み込めません")
            continue
        name_images.append((filename, image))
    return name_images

def detect_image_face(file_path, image, cascade_filepath):
    # 画像ファイルのグレースケール化
    image_gs = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # カスケードファイルの読み込み
    cascade = cv2.CascadeClassifier(cascade_filepath)
    # 顔認識
    faces = cascade.detectMultiScale(image_gs, scaleFactor=1.1, minNeighbors=15, minSize=(64, 64))
    if len(faces) == 0:
        print(f"顔認識失敗")
        return
    # 1つ以上の顔を認識
    face_count = 1
    for (xpos, ypos, width, height) in faces:
        face_image = image[ypos:ypos+height, xpos:xpos+width]
        if face_image.shape[0] > 64:
            face_image = cv2.resize(face_image, (64, 64))
        print(face_image.shape)
        # 保存
        path = pathlib.Path(file_path)
        directory = str(path.parent.resolve())
        filename = path.stem
        extension = path.suffix
        output_path = os.path.join(directory, f"{filename}_{face_count:03}{extension}")
        print(f"出力ファイル（絶対パス）:{output_path}")
        cv2.imwrite(output_path, face_image)
        face_count = face_count + 1

def delete_dir(dir_path, is_delete_top_dir=True):
    for root, dirs, files in os.walk(dir_path, topdown=False):
        for name in files:
            os.remove(os.path.join(root, name))
        for name in dirs:
            os.rmdir(os.path.join(root, name))
    if is_delete_top_dir:
        os.rmdir(dir_path)

RETURN_SUCCESS = 0
RETURN_FAILURE = -1
# Origin Image Pattern
IMAGE_PATH_PATTERN = "./origin_image/*"
# Output Directory
OUTPUT_IMAGE_DIR = "./face_image"

def main():
    print("===================================================================")
    print("イメージ顔認識 OpenCV 利用版")
    print("指定した画像ファイルの正面顔を認識して抜き出し、サイズ変更64x64を行います。")
    print("===================================================================")

    # ディレクトリの作成
    if not os.path.isdir(OUTPUT_IMAGE_DIR):
        os.mkdir(OUTPUT_IMAGE_DIR)
    # ディレクトリ内のファイル削除
    delete_dir(OUTPUT_IMAGE_DIR, False)

    # 画像ファイルの読み込み
    name_images = load_name_images(IMAGE_PATH_PATTERN)

    # 画像ごとの顔認識
    for name_image in name_images:
        file_path = os.path.join(OUTPUT_IMAGE_DIR, f"{name_image[0]}")
        image = name_image[1]
        cascade_filepath = settings.CASCADE_FILE_PATH
        detect_image_face(file_path, image, cascade_filepath)

    return RETURN_SUCCESS

if __name__ == "__main__":
    main()
```

* 設定ファイルを読み込むプログラムを Python で書きます。

* ファイル名は「settings.py」とします。

```python
import os
from os.path import join, dirname
from dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

CASCADE_FILE_PATH = os.environ.get("CASCADE_FILE_PATH")
```

## プログラム実行

* origin_imageフォルダに顔を切り出す画像ファイルを配置して実行します。

```powershell
(venv)> python img_face_dt.py
```

* face_imageフォルダに顔の画像が保存されます。

> 顔が認識できない場合や顔ではないものを切り出す場合もあります。

## 最後に

深層学習で画像の分類を行いたいという勢いから、いろいろな情報を参考にやってみました。
detectMultiScaleのパラメータであるminNeighborsを調整することで認識の精度が変化します。