顔検出 + 年齢・性別予測

# はじめに

今回の内容は、画像から顔を検出し、検出された顔の性別と年齢を推定するソースコードを作成することです。ただし、Pythonの勉強も兼ねているため、完全自作ではなく、複数のサイトを参考にソースコードを作成しました。

ソースコードは、[こちら](https://github.com/oHjm/face_and_gender)です。

# 顔検出
顔検出を行うライブラリとして、OpenCVのHaar-like特徴分類器やDlibなどがありますが、FacenetのMTCNNを採用。[「davidsandberg/facenet/src/compare.py」](https://github.com/davidsandberg/facenet)中の関数load_and_align_dataを基に書き換えました。

```ruby:MTCNNを用いた顔検出
def Face_detection(Img, image_size):
    minsize = 20
    threshold = [ 0.6, 0.7, 0.7 ]  
    factor = 0.709 
    margin = 44
    gpu_memory_fraction = 1.0
    
    print('Creating networks and loading parameters')
    with tf.Graph().as_default():
        gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=gpu_memory_fraction)
        sess = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options, log_device_placement=False))
        with sess.as_default():
            pnet, rnet, onet = align.detect_face.create_mtcnn(sess, None)
    
            Img_size = np.asarray(Img.shape)[0:2]
            bounding_boxes, _ = align.detect_face.detect_face(Img, minsize, pnet, rnet, onet, threshold, factor)
            faces = np.zeros((len(bounding_boxes), image_size, image_size, 3), dtype = "uint8")
            bb = np.zeros((len(bounding_boxes), 4), dtype=np.int32)
            for i in range(len(bounding_boxes)):            
                det = np.squeeze(bounding_boxes[i,0:4])
                bb[i, 0] = np.maximum(det[0]-margin/2, 0)
                bb[i, 1] = np.maximum(det[1]-margin/2, 0)
                bb[i, 2] = np.minimum(det[2]+margin/2, Img_size[1])
                bb[i, 3] = np.minimum(det[3]+margin/2, Img_size[0])
                cropped = Img[bb[i, 1]:bb[i, 3],bb[i, 0]:bb[i, 2],:]
                aligned = misc.imresize(cropped, (image_size, image_size), interp='bilinear')
                faces[i, :, :, :] = cv2.cvtColor(aligned, cv2.COLOR_BGR2RGB)
    return faces, bb
```
- 引数
 - Img：入力画像
 - image_size：リサイズする顔画像の大きさ
- 戻り値
 - faces：顔画像が格納された4次元配列
 - bb：入力画像から検出された顔の位置


# 年齢・性別予測
年齢・性別を予測するために、[「Kerasでウェブカメラから顔領域を検出し、年齢・性別を推定する」](https://qiita.com/yu4u/items/8cc71c645afe8cbb1f42)を参考にさせていただきながら、[demo.py](https://github.com/yu4u/age-gender-estimation/blob/master/demo.py)を基に書き換えました。また、学習済みモデルも使わせていただいております。

```ruby:性別・年齢予測
def age_gender_predict(faces):    
    if len(faces) > 0:   
        # モデルの設定
        if os.isdir("model") == False:
            pre_model = "https://github.com/yu4u/age-gender-estimation/releases/download/v0.5/weights.28-3.73.hdf5"
            modhash = 'fbe63257a054c1c5466cfd7bf14646d6'
            weight_file = get_file("weights.28-3.73.hdf5", pre_model, cache_subdir="model",
                                   file_hash=modhash, cache_dir=str(Path(__file__).resolve().parent))
        else:
            weight_file = "model/weights.28-3.73.hdf5"            

        img_size = np.asarray(faces.shape)[1]
        model = WideResNet(img_size, depth=16, k=8)()
        model.load_weights(weight_file)
        
        # 予測
        results = model.predict(faces)
        Genders = results[0]
        ages = np.arange(0, 101).reshape(101, 1)
        Ages = results[1].dot(ages).flatten()

    return Ages, Genders

```
- 引数
 - faces：顔画像が格納された配列
- 戻り値
 - Ages：予測された各年齢（0～100歳）の確率
 - Genders：予測された各性別（Male、Female）の確率

# メインプログラム
```ruby:main

import cv2
import numpy as np
from wide_resnet import WideResNet
from pathlib import Path
import align.detect_face
import tensorflow as tf
from scipy import misc
from keras.utils.data_utils import get_file
import os.path as os

img = cv2.imread("input.jpg") #入力画像
img_size = 64

# 性別・年齢を表記する関数
def draw_label(image, point, label, font=cv2.FONT_HERSHEY_SIMPLEX,
               font_scale=1.0, thickness=1):
    size = cv2.getTextSize(label, font, font_scale, thickness)[0]
    x, y = point
    cv2.rectangle(image, (x, y - size[1]), (x + size[0], y), (0,255,255), cv2.FILLED)
    cv2.putText(image, label, point, font, font_scale, (0, 0, 0), thickness, lineType=cv2.LINE_AA)

faces, bb = Face_detection(img, img_size) # 顔検出  
Ages, Genders = age_gender_predict(faces) # 性別・年齢予測
        
for face in range(len(faces)):        
    cv2.rectangle(img,(bb[face, 0], bb[face, 1]),(bb[face, 2], bb[face, 3]),(0,255,255),10)
    label = "{}, {}".format(int(Ages[face]), "Male" if Genders[face][0] < 0.5 else "Female")
    draw_label(img, (bb[face, 0], bb[face, 1]), label)

# 出力画像の保存        
cv2.imwrite('output.jpg', img)
```

# 結果
検証画像として、[「商用無料の写真検索さん」](https://www.nairegift.com/freephoto/)を利用しました。

上が入力画像、下が出力結果となります。

- 参照：[Business Woman by thisisedinburgh](http://www.igosso.net/se.cgi?q=Business+Woman+&sa=%E6%A4%9C%E7%B4%A2&lid=1&lia=1&lib=1&lic=1)

![Business Woman by thisisedinburgh](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499475/1096f4b5-2a96-e22d-335a-739fc204cefa.jpeg) ![output.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499475/501c643f-e4c2-1529-744e-7fe2ad5dc528.jpeg)
性別：Female　年齢：40



- 参照：[Small Business Summit 2011 Pre Event Photo 9 by Grant Wickes](http://www.igosso.net/se.cgi?q=Small+Business+Summit+2011+Pre+Event+Photo+9+by+Grant+Wickes&sa=%E6%A4%9C%E7%B4%A2&lid=1&lia=1&lib=1&lic=1)

![Small Business Summit 2011 Pre Event Photo 9 by Grant Wickes](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499475/68f22297-5cc0-0585-82aa-86f3e0034f71.jpeg) ![output.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499475/4e46dc17-f733-146f-eda9-d6251d2a6dfe.jpeg)
性別：（左から）Female、Female、male　年齢：（左から）27、44、26



出力結果において、性別は合っており、年齢もそれっぽくなりました。

# 最後に
今回の技術は、下図のように、小売業などにおける来店者の性別・年齢層に関するデータを抽出することができるため、（既に商品・サービスが販売されているが）来店客分析に用いることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499475/424081b1-a56b-4266-7cc3-6b43ddc51998.png)