###概要
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はリポジトリに含まれるので別途用意する必要はありません。
###ソースコード
コードが汚いのはご了承ください...
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()
###実行結果
用意した画像は以下の通り。
大川竜弥さんですね。
「Face not found.」と出力されたものは、顔を検出できなかった、言い換えれば顔として認識できなかったことになります。
この例では、下の2つが顔として認識できませんでした。
どちらも顔が傾いています。ある程度の傾きなら問題ないのですが、画像のような角度で傾いているとダメみたいですね。どうしても顔を切り取りたい場合は、画像に対しアフィン変換をする必要があります。
グレースケール化され、顔のみ切り取られてリサイズされています。
眼鏡をかけていても問題ありません。(サングラス、マスク着用は厳しいかも...)
コマンド
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]
切り取りができたら、cv2.resize()
で指定のサイズにリサイズを行い、保存を行います。
以上です。
main関数については説明不要だと思うので割愛します。
###おわりに
私はデータスクレイピングとこのプログラムを使って、学習データの収集をしてました。
特段こだわりがなければ、十分使えると思います。
ただ、実行結果でも触れたとおり限界はあります。横顔の場合は横顔専用のHaar-cascadeも同梱されているので、そちらを使うといいかもしれません。
また、誤検出も多少あるのでデータクレンジング(顔ではない部分が切り取られた画像の排除)が必要です。