Pythonで人物画像から顔だけ切り出して保存したい ~face_recognitionで顔検出してトリミング~

# やること
### これを
![Zinedine_Zidane_0001](https://user-images.githubusercontent.com/26497221/80873060-f04f7400-8cf0-11ea-895b-c312a64d9f29.jpg)

### こう
![Zinedine_Zidane_0001 jpg 03-36-34-700](https://user-images.githubusercontent.com/26497221/80873035-cf871e80-8cf0-11ea-9f77-5f9edb49cae8.jpg)

# 環境
```
MacOS Catalina 10.15.4
Python 3.7.6
```

# face_recognitionとは？
学習済みモデルをつかい、顔検出などいくつかの画像処理を行えるライブラリ。
dlibが使われているとのこと。
https://github.com/ageitgey/

# リポジトリ
face_recognitionをつかったコードです。
https://github.com/komiyakomiyakomiya/face_trim

```sh:terminal
$ git https://github.com/komiyakomiyakomiya/face_trim.git
```

# ライブラリ
```
opencv-python==4.2.0.34
face-recognition==1.3.0
```

# インストール
```sh:terminal
$ pip install opencv-python==4.2.0.34 face-recognition==1.3.0
```

# コード
```py:face_trim.py
import os
from pathlib import Path
import subprocess
import sys

import cv2
import face_recognition
from IPython.display import display
from IPython.display import Image


cwd = Path().resolve()


def exec_cmd(cmd):
    """ コマンド実行 """
    # cmd文字列の前後にスペースが入っていたら削除 -> スペースで分割しlist化
    cmd_split = cmd.strip().split()
    # stdoutの設定で標準出力を取得
    cp = subprocess.run(cmd_split, stdout=subprocess.PIPE)
    # cp = subprocess.check_output(cmd_split)
    if cp.returncode != 0:
        print(f'{cmd_split[0]} faild.', file=sys.stderr)
        sys.exit(1)
    # 標準出力があれば返す
    if cp.stdout is not None:
        # bytes -> strへデコード
        return cp.stdout.decode('utf-8')


def get_face_location(img_path):
    """ 顔の座標を取得 """
    img = face_recognition.load_image_file(img_path)
    # location = face_recognition.face_locations(img, model='cnn')
    location = face_recognition.face_locations(img, model='hog')
    print(location)
    # [(82, 175, 180, 76)]
    top = location[0][0]
    right = location[0][1]
    bottom = location[0][2]
    left = location[0][3]
    return top, right, bottom, left


def get_face_location_cli(img_path):
    """ CLIツールのface_detectionを実行し顔の座標を取得
    load_image_file()メソッドがバグで使えない場合こっちを利用 """
    # 顔を検出し、Top, Right, Bottom, Leftの座標を標準出力するコマンド
    cmd_face_detection = f'face_detection {img_path}'
    # cmd_face_detection = f'face_detection --model cnn {img_path}'
    # 標準出力を受け取る
    stdout = exec_cmd(cmd_face_detection)
    print(stdout)
    # /Users/USER_NAME/path/to/dir/input/Zinedine_Zidane_0001.jpg,89,181,192,77
    # カンマ区切りでリスト化
    stdout_list = stdout.strip().split(',')
    top = int(stdout_list[1])
    right = int(stdout_list[2])
    bottom = int(stdout_list[3])
    left = int(stdout_list[4])
    return top, right, bottom, left


def display_image(img_path):
    """ 画像を表示 """
    # 画像の読み込み
    img = cv2.imread(img_path)
    # 拡張子を取得
    format = os.path.splitext(img_path)[1]
    # 渡した拡張子(format)の形式にエンコード
    decoded_bytes = cv2.imencode(format, img)[1].tobytes()
    print(cv2.imencode(format, img)[1])
    # [[255]
    # [216]
    # [255]
    # ...
    # [103]
    # [255]
    # [217]]
    # print(decoded_bytes)
    # b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x0......
    display(Image(data=decoded_bytes))


if __name__ == '__main__':
    # お好きな画像に置き換えて下さい
    file_name = f'Zinedine_Zidane_0001.jpg'
    # 元画像の置き場所
    input_path = f'{cwd}/../input'
    # トリミング画像の出力先
    output_path = f'{cwd}/../output'
    # 出力先ディレクトリがなければ作成
    os.makedirs(output_path, exist_ok=True)

    # 顔座標を取得
    top, right, bottom, left = get_face_location(f'{input_path}/{file_name}')

    # 元ファイルの読み込み
    img = cv2.imread(f'{input_path}/{file_name}')
    # 表示
    display_image(f'{input_path}/{file_name}')
    # トリミング
    img_face = img[top:bottom, left:right]
    # outputディレクトリに保存
    cv2.imwrite(f'{output_path}/{file_name}', img_face)
    # 表示
    display_image(f'{output_path}/{file_name}')
```

# ディレクトリ内の画像まとめてトリミング

```py
if __name__ == '__main__':
    # 元画像の置き場所
    input_path = f'{cwd}/../input'
    # トリミング画像の出力先
    output_path = f'{cwd}/../output'
    # 出力先ディレクトリがなければ作成
    os.makedirs(output_path, exist_ok=True)

    # パスオブジェクトを生成
    path_obj = Path(input_path)
    # globでパターンマッチング
    files_path = path_obj.glob('*')
    # posix変換
    files_path_posix = [file_path.as_posix() for file_path in files_path]
    print(files_path_posix)

    for file_path in files_path_posix:
        # ファイル名取得
        file_name = file_path.split('/')[-1]
        # 顔座標を取得
        top, right, bottom, left = get_face_location(file_path)
        # 元ファイルの読み込み
        img = cv2.imread(file_path)
        # 表示
        display_image(file_path)
        # トリミング
        img_face = img[top:bottom, left:right]
        # outputディレクトリに保存
        cv2.imwrite(f'{output_path}/{file_name}', img_face)
        # 表示
        display_image(f'{output_path}/{file_name}')
```

# エラー
こんなエラーが出ることがありました。

```sh
AttributeError: module 'face_recognition' has no attribute 'load_image_file'
```

イシューも立っていたのですが、ここで書かれていることを試しても解決せず。
https://github.com/ageitgey/face_recognition/issues/318

あきらめてCLIツールの標準出力から顔座標を取得するという強引なやり方を試していたら、いつの間にかなおってました...。


# トリミング画像をクラスタリング
こちらに記事を書きました。
もしよろしければ

  - [正解ラベルの無い複数人の画像データをFaceNetでクラスタリングして別々のディレクトリへ出力](https://qiita.com/drafts/da677ff982acb679f122/)


# おしまい
最後まで読んでいただきありがとうございました！
ジダンに意味はありません。
たまたま、そばにいたので。