機械学習で用いる顔画像データセットの一作り方（３：候補画像から顔画像生成その１）

[前回](http://qiita.com/ligerbolt/items/fdfe836e71a2c0247a79)、[前々回](http://qiita.com/ligerbolt/items/aeaa88980030fba940b6)まで対象となる顔が写った候補画像を生成・取得する方法について述べましたが、今回はそれらから顔領域部分を抽出して顔画像を生成する方法を紹介します。

#今回用いた開発環境
+ MacOS X El Capitan 10.11.4 
+ Python 3.5
+ OpenCV 3.1
+ dlib 18.18

#候補画像から顔画像を生成するフロー
顔画像を生成するには候補画像中より顔が映った領域のみをトリミングし、そのトリミングした領域を新たな画像=顔画像として生成します。
しかしこの作業を手動で行うには膨大な手間と時間を要しますので、それを少しでも解決するために今回は以下の顔認識API（ライブラリ）を用います。

+ dlib
+ OpenCV
+ Face API（Microsoft Cognitive Services）

これらのAPIを使えば候補画像から顔領域のみを自動的に抽出することが可能となりますが、今回はOpenCVとdlibを用いた手法について説明します。
具体的な顔画像生成のフローは基本的に同じで、以下の様になります。

1. 顔検出APIに候補画像を入力することで、顔領域と思われる座標情報を取得する
2. 候補画像から1で取得した座標情報を元に、座標領域をトリミングする
3. トリミングした領域を新たな画像として生成し、顔画像とする

なお上記処理は下記コードにあるface_detectメソッドにて定義するものとします。


```python
# -*- coding: utf-8 -*-
import glob

#候補画像を格納したディレクトリパス
IMG_PATH = "/home/hoge/image/"

def face_detect(img_list):
	# 本メソッドで顔画像生成処理を定義
	return


if __name__ == '__main__':
	
	#指定ディレクトリより候補画像を取得、API等を用いて顔画像生成
	images = glob.glob(IMG_PATH + '*.jpg')	
	face_detect(images)
	
```


##dlib
[dlib](http://dlib.net/)はC++用に開発された画像処理や機械学習の機能を纏めたライブラリですが、pythonでも使用できます。
（Mac環境におけるdlibインストール方法に関しては、[こちらの記事](http://qiita.com/bohemian916/items/2de6b72151e2bbbf0389)等を参照してください。）

dlibでは顔検出用に`get_frontal_face_detector`メソッド用意されているので、それを利用します。
他にも識別精度の高い非線形識別器の一種であるSVM関連のメソッドを使えば、より高度な検出も可能ですが今回は手軽さを優先してこちらを利用します。
実際に`get_frontal_face_detector`メソッドを利用した顔画像生成コードは、下記のようになります。

※ なお画像処理部に関してはOpenCVを使用しています。

```python
import dlib
import cv2

def face_detect(img_list):

	# 顔領域を検出する識別器呼び出し
	detector = dlib.get_frontal_face_detector()
	
	# 候補画像を識別器にかけて座標情報を取得
	for img_path in img_list:
	
		# トリミング用候補画像（カラーモード）
		img = cv2.imread(img_path, cv2.IMREAD_COLOR)
		
		# 画像配列を組み直して識別器に入力、顔領域と思われる座標情報取得
		cv_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
		faces = detector(cv_img, 1)

		# 顔領域を検出すれば顔画像生成
		img_count = 1
        for face in faces:
        	
        	# 候補画像サイズ取得
        	height, width = img.shape[:2]
        	
        	# 顔領域の座標点取得
        	top = face.top()
        	bottom = face.bottom()
        	left = face.left()
        	right = face.right()
        	
        	# イレギュラーな顔領域は無視
        	if not top < 0 and left < 0 and bottom > height and right > width:
            	break
            else:
            	# 顔領域トリミング
            	dst_img = img[top:bottom, left:right]
            	
            	# 顔画像サイズ正規化して保存
            	face_img = cv2.resize(dst_img, (64,64))
            	new_img_name = str(img_count) + '.jpg'
            	cv2.imwrite(new_img_name, face_img)
            	img_count += 1

```
　
OpenCVとdlibでは、画像のカラー情報であるRGB（Red/Green/Blue）の各画素値の扱い方が違います。
OpenCVではRGB（Red/Green/Blue）の画素値をBGRの順に配列に保持していますが、dlibではRGBの順に保持しているため`cvtColor`メソッドを用いて、配列構造を変換させる必要があります。

またget_frontal_face_detectorでは顔領域の座標情報として、画像領域外の座標値を稀に返します。これは画像端に顔が映っているがために顔の一部分が欠けてるケースが多く、学習データとしてもそのまま顔画像として生成しても使いにくいものになるため、イレギュラーな顔領域として無視しています。
## OpenCV
[OpenCV](http://opencv.org/)はC++やPython等で使用できる、本記事シリーズでもお馴染みの画像処理ライブラリですが、ここではカスケード識別器クラスとして用意されている`CascadeClassifier`クラスを利用します。
このクラスを用いれば識別器を構築する際に読み込む学習結果を格納したxmlファイルの内容次第で、様々なオブジェクトに対応した識別器を構築できます。
今回はOpenCVで提供されている`haarcascade_frontalface_default.xml`[(提供サイト)](https://github.com/Itseez/opencv/tree/master/data/haarcascades)を参照することで、顔検出器として利用します。

```python
import cv2

# HaarCascade識別器が参照するXMLファイルパス
XML_PATH = "./haarcascade_frontalface_default.xml"
    
def face_detect(img_list):   
    
    # 顔領域識別器をセット
    classifier = cv2.CascadeClassifier(xml_path)

	# 顔領域を検出すれば顔画像生成
    img_count = 1
    for img_path in img_list:
    
		# トリミング用カラー画像
		org_img = cv2.imread(img_path, cv2.IMREAD_COLOR)

		# 識別器入力用グレースケール画像
        gray_img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
        
        #候補画像（グレースケール画像）を識別器に入力、顔領域と思われる座標情報取得
        face_points = classifier.detectMultiScale(gray_img, \
                scaleFactor=1.2, minNeighbors=2, minSize=(1,1))
        
        #識別結果（矩形座標情報）よりカラー画像をトリミングして顔画像生成　
        for points in face_points:
            
            # 顔領域の座標点取得
            x, y, width, height =  points
            
            # 顔領域トリミング
            dst_img = org_img[y:y+height, x:x+width]
            
            # 顔画像サイズ正規化して保存
            face_img = cv2.resize(dst_img, (64,64))
            new_img_name = str(img_count) + '.jpg'
            cv2.imwrite(new_img_name, face_img)
            img_count += 1
            
```

OpenCVを用いた方法ではトリミング用のカラー画像と識別器入力用のグレースケール画像、2種類の候補画像を用意します。これは`detectMultiScale`メソッドがカラー画像の入力に対応してないためです。

なお`detectMultiScale`メソッドでは、以下のパラメータ引数を取ります。

+ scaleFactor - 画像ピラミッド生成時の縮小量。これを1.01に近づけるほど細かい顔領域探索が可能
+ minNeighbors - 顔領域が取りうる最低限の近傍矩形数
+ minSize - 検出する顔領域の最低サイズ
+ maxSize（今回未指定） - 検出する顔領域の最大サイズ

※上記一部パラメータについては、@workpilesさんの下記サイトが理解に役立つと思います。

[物体検出（detectMultiScale）をパラメータを変えて試してみる（minNeighbors編）](http://workpiles.com/2015/04/opencv-detectmultiscale-minneighbors/)
[物体検出（detectMultiScale）をパラメータを変えて試してみる（scaleFactor編）](http://workpiles.com/2015/04/opencv-detectmultiscale-scalefactor/)

#上記2つの手法を使ってみた大雑把な印象
###dlib
+ 検出精度はかなり高い。特に非顔領域（顔の写っていない領域）を顔領域と誤認識することは殆ど無い。
+ 正面顔だけではなく、やや横や上下に顔が傾いた画像にもそこそこロバストで検出可能ある。

###OpenCV
+ 処理速度はdlibより優れてる。
+ `detectMultiScale`メソッドのパラメータ設定によって検出精度が大きく変動する。
+ 非顔領域を顔領域と誤認識するケースがやや目立つ。
+ 正面以外を向いた顔には検出精度低い（横顔用の識別器が必要）

　
以上簡単ではありますが、今回は候補画像より顔画像を生成する手法についての説明でした。