LoginSignup
1
2

More than 3 years have passed since last update.

画像から顔を切り取ろう

Last updated at Posted at 2020-05-11

概要

OpenCVが用意しているHaar-Like特徴量Cascadeを使って顔の検出を行い、自動で切り取るプログラムを作りました。

更新

このプログラムの改良版を作成しました。
以前は1つの画像に複数個の顔が含まれていても1個しか切り取ることができなかったですが、改良版では全て切り取るようにしました。
その他、修正や機能追加をしましたので、こちらを利用することをおすすめします。

環境

-Software-
Windows 10 Home
Anaconda3 64-bit(Python3.7)
Spyder
-Library-
opencv-python 4.1.2.30
natsort 7.0.0
-Hardware-
CPU: Intel core i9 9900K
RAM: 16GB 3200MHz

参考

書籍
Pythonで始めるOpenCV4プログラミング 北山 直洋 (著)
(Amazonページ)

プログラム

Githubに上げておきます。

https://github.com/himazin331/Face-Cropping
リポジトリにはデータ加工プログラム、Haar-Cascadeが含まれています

前提

本プログラムの動作にはHaar-Like特徴量のCascadeファイルが必須です。
今回はOpenCVのHaar-Cascadeを使用します。
なお、Cascadeはリポジトリに含まれるので別途用意する必要はありません。

ソースコード

コードが汚いのはご了承ください...

face_cut.py
import cv2
import os
import argparse as arg
import sys
from natsort import natsorted


# 画像加工
def face_cut(imgs_dir, result_out, img_size, label, HAAR_FILE):
    # Haar-Like特徴量Cascade型分類器の読み込み
    cascade = cv2.CascadeClassifier(HAAR_FILE)

    # データ加工
    for img_name in natsorted(os.listdir(imgs_dir)):
        print("画像データ:{}".format(img_name))

        # jpg形式のみ
        _, ext = os.path.splitext(img_name)
        if ext.lower() == '.jpg':
            img_path = os.path.join(imgs_dir, img_name)  # ファイルパスの結合
            img = cv2.imread(img_path)  # データ読み込み

            img_g = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)  # グレースケールに変換
            face = cascade.detectMultiScale(img_g)  # 顔を検出

            # 顔が検出されなかったら
            if len(face) == 0:
                print("Face not found.")
            else:
                for x, y, w, h in face:
                    # 顔の切り取り
                    face_cut = img_g[y:y + h, x:x + w]
                    # リサイズ
                    face_img = cv2.resize(face_cut, (img_size, img_size))

                    # 保存
                    result_img_name = r'\data' + str(label) + '.jpg'
                    cv2.imwrite(os.path.join(result_out + result_img_name),
                                face_img)
                    label += 1

                print("Processing success!!")
        else:
            print("Unsupported file extension")


def main():
    # コマンドラインオプション作成
    parser = arg.ArgumentParser(description='Face image cropping')
    parser.add_argument('--imgs_dir', '-d', type=str, default=None,
                        help='画像フォルダパス(未指定ならエラー)')
    parser.add_argument('--out', '-o', type=str,
                        default=os.path.dirname(os.path.abspath(__file__)) + '/result_crop'.replace('/', os.sep),
                        help='加工後データの保存先(デフォルト値=./reslut_crop)')
    parser.add_argument('--img_size', '-s', type=int, default=32,
                        help='リサイズ(NxNならN,デフォルト値=32)')
    parser.add_argument('--label', '-l', type=int, default=1,
                        help='dataN.jpgのNの初期値(デフォルト値=1)')
    parser.add_argument('--haar_file', '-c', type=str,
                        default=os.path.dirname(os.path.abspath(__file__)) + '/haar_cascade.xml'.replace('/', os.sep),
                        help='haar-Cascadeのパス指定(デフォルト値=./haar_cascade.xml)')
    args = parser.parse_args()

    # 画像フォルダ未指定時->例外
    if args.imgs_dir is None:
        print("\nException: Cropping target is not specified.\n")
        sys.exit()
    # 存在しない画像フォルダ指定時->例外
    if os.path.exists(args.imgs_dir) is False:
        print("\nException: {} does not exist.\n".format(args.imgs_dir))
        sys.exit()
    # 存在しないCascade指定時->例外
    if os.path.exists(args.haar_file) is False:
        print("\nException: {} does not exist.\n".format(args.haar_file))
        sys.exit()

    # 設定情報出力
    print("=== Setting information ===")
    print("# Images folder: {}".format(os.path.abspath(args.imgs_dir)))
    print("# Output folder: {}".format(args.out))
    print("# Images size: {}".format(args.img_size))
    print("# Start index: {}".format(args.label))
    print("# Haar-cascade: {}".format(args.haar_file))
    print("===========================\n")

    # 出力フォルダの作成(フォルダが存在する場合は作成しない)
    os.makedirs(args.out, exist_ok=True)

    # 加工
    face_cut(args.imgs_dir, args.out, args.img_size, args.label, args.haar_file)
    print("")


if __name__ == '__main__':
    main()

実行結果

用意した画像は以下の通り。
大川竜弥さんですね。
image.png

image.png

「Face not found.」と出力されたものは、顔を検出できなかった、言い換えれば顔として認識できなかったことになります。
この例では、下の2つが顔として認識できませんでした。
image.png

どちらも顔が傾いています。ある程度の傾きなら問題ないのですが、画像のような角度で傾いているとダメみたいですね。どうしても顔を切り取りたい場合は、画像に対しアフィン変換をする必要があります。

この2つ以外はこのように、
image.png

グレースケール化され、顔のみ切り取られてリサイズされています。
眼鏡をかけていても問題ありません。(サングラス、マスク着用は厳しいかも...)

コマンド
python face_cut.py -d <画像フォルダ> (-o <保存先> -s <リサイズ> -l <インデックス> -c <cascade>)

加工された画像データの保存先はデフォルトで./result_cropになっています。
Haar-cascadeの指定はデフォルトで./haar_cascade.xmlとなっています。
その他、リサイズは32×32px、インデックスは1がデフォルトで指定されています。

説明

face_cut関数で、グレースケール化、顔の切り取り、リサイズをします。

まずは、顔を検出し、切り取るのに使うHaar-cascadeを読み込みます。

    # Haar-Like特徴量Cascade型分類器の読み込み
    cascade = cv2.CascadeClassifier(HAAR_FILE)

HAAR_FILEはコマンドオプションより指定したHaar-cascadeのパスです。

下にある処理では画像の読み込み、グレースケール化、顔の検出を行っています。

    # データ加工
    for img_name in natsorted(os.listdir(imgs_dir)):
        print("画像データ:{}".format(img_name))

        # jpg形式のみ
        _, ext = os.path.splitext(img_name)
        if ext.lower() == '.jpg':
            img_path = os.path.join(imgs_dir, img_name)  # ファイルパスの結合
            img = cv2.imread(img_path)  # データ読み込み

            img_g = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)  # グレースケールに変換
            face = cascade.detectMultiScale(img_g)  # 顔を検出

今回はJPEGファイルのみ対象としていますが、
if ext.lower() == '.jpg':'.png''.bmp'にすれば、そのファイルを対象に加工できます。

cv2.cvtColor()でRGB画像からグレースケール画像に変換します。
その後、cascade.detectMultiScale()でHaar-cascadeを用いた顔の検出を行います。

            img_g = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)  # グレースケールに変換
            face = cascade.detectMultiScale(img_g)  # 顔を検出

cascade.detectMultiScale()は顔が検出されたとき、検出箇所のx座標とy座標、幅と高さを返却します。
顔が検出されなければなにも返却されません。

            # 顔が検出されなかったら
            if len(face) == 0:
                print("Face not found.")
            else:
                for x, y, w, h in face:
                    # 顔の切り取り
                    face_cut = img_g[y:y + h, x:x + w]
                    # リサイズ
                    face_img = cv2.resize(face_cut, (img_size, img_size))

                    # 保存
                    result_img_name = r'\data' + str(label) + '.jpg'
                    cv2.imwrite(os.path.join(result_out + result_img_name),
                                face_img)
                    label += 1

                print("Processing success!!")
        else:
            print("Unsupported file extension")

下の処理では返される情報をもとに、顔部分の切り取りを行っています。

                    # 顔の切り取り
                    face_cut = img_g[y:y + h, x:x + w]

下の図のような具合になっています。(見づらいですが...)
image.png

切り取りができたら、cv2.resize()で指定のサイズにリサイズを行い、保存を行います。

以上です。
main関数については説明不要だと思うので割愛します。

おわりに

私はデータスクレイピングとこのプログラムを使って、学習データの収集をしてました。
特段こだわりがなければ、十分使えると思います。

ただ、実行結果でも触れたとおり限界はあります。横顔の場合は横顔専用のHaar-cascadeも同梱されているので、そちらを使うといいかもしれません。
また、誤検出も多少あるのでデータクレンジング(顔ではない部分が切り取られた画像の排除)が必要です。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2