CNNで顔検出やってみた

3 min read読了の目安(約3500字

目標

今回の目標は画像から顔を検出してバウンディングボックスを描画することです。
気軽にお試ししたいので今回は学習済みモデルを使用します。

実行環境

tensorflow==1.14.0

手順

リポジトリのclone

$ git clone https://github.com/davidsandberg/facenet

facenet/src/以下に以下のface_detection.pyを配置する

face_detection.py
import align.detect_face
import tensorflow as tf
import numpy as np
import glob
import os

def face_detection(path, margin):
    minsize = 20 # minimum size of face
    threshold = [ 0.5, 0.6, 0.6 ]  # three steps's threshold
    factor = 0.709 # scale factor
    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)
  
    # read image
    original_img = Image.open(path)
    # image width & height
    width, height = original_img.size
    # pillow -> numpy
    img = np.array(original_img, np.float32)
    # inference
    results, _ = align.detect_face.detect_face(img, minsize, pnet, rnet, onet, threshold, factor)
    if len(results) < 1:
      print("can't detect face")
    else:
        for result in results:
            print(result)
            x1, y1, x2, y2, score = result
            x1 = np.maximum(x1-margin/2, 0)
            y1 = np.maximum(y1-margin/2, 0)
            x2 = np.minimum(x2+margin/2, width)
            y2  = np.minimum(y2+margin/2, height)

            # draw bounding box
            img = ImageDraw.Draw(original_img)
            img.rectangle((x1, y1, x2, y2), outline=(0,255,0), width=3)

        return original_img


if __name__ == '__main__':
    img_path = '../data/images/test.jpg'
    output_path = './result.png'
    margin = 0

    detect_img = face_detection(
                            path=img_path,
                            margin=margin
                            )    
    detect_img.save(output_path)

face_detection.pyのimg_pathに入力画像のパス、output_pathに出力画像のパスを指定する

facenet/src/でface_detection.pyを実行する

$ python face_detection.py

入力画像

出力画像

つまずいたところ(エラー)

ValueError: Object arrays cannot be loaded when allow_pickle=False

numpy==1.16.3 より、numpy.load()関数の挙動が変更されたらしく、デフォルトでnumpy.load()の引数allow_pickleがFalseになっているのが原因らしい

解決法

facenet/src/align/detect_face.pyのload()関数内のnp.load()を修正する

detect_face.py
def load(self, data_path, session, ignore_missing=False):
        """Load network weights.
        data_path: The path to the numpy-serialized network weights
        session: The current TensorFlow session
        ignore_missing: If true, serialized weights for missing layers are ignored.
        """
        data_dict = np.load(data_path, encoding='latin1', allow_pickle=True).item() #ここを修正

        for op_name in data_dict:
            with tf.variable_scope(op_name, reuse=True):
                for param_name, data in iteritems(data_dict[op_name]):
                    try:
                        var = tf.get_variable(param_name)
                        session.run(var.assign(data))
                    except ValueError:
                        if not ignore_missing:
                            raise

まとめ

学習済みのマルチタスクCNNを動かして顔検出のお試ししてみました。
思ったより精度高そうでだいぶ使えそうです。
色々応用できそうですね!