元々は人力で公式を作ろうとしてみましたが、数学の基礎知識が乏しい私には難かったため、これを機に以前からずっと気になっていた機械学習を導入することにしました。
そして、結果としては満足できる精度になりそうになったのでここにその作業の手順を残しておこうと思います。
追記: joblib は使わず pickle を使うようコードを変更しました。 (2017.03.24)
【おおまかな手順】
- 角度データのある顔画像を用意
- 顔データを加工して機械学習&結果を保存
- 学習結果を使って未知の画像から顔の角度を取得
【実際の手順】
まず角度データつきの顔画像を探しました。
機械学習で一番骨が折れるのはデータ収集です。
ある程度の数がないと精度を高めることができないためです。
ただ、今回はテストということで数は少ないですが以下のページからダウンロードして試してみました。
http://www-prima.inrialpes.fr/perso/Gourier/Faces/HPDatabase.html
次にこの画像から学習データとラベルを取得して学習させます。
(今回は顔が左右どちらにどのくらいの角度向いているかを学習させます。)
学習データ
データは、以下のような顔の右側と左側の比率を使うことにしました。
(左のこめかみから鼻までの距離 + 左のほほから鼻までの距離) / (右のこめかみから鼻までの距離 + 右のほほから鼻までの距離)
※2本ずつの距離を使ったのは縦方向の回転(pitch)の影響が少なくなるのではないかと思ったからです。
ラベル
ラベル(学習後、テストデータを入れると取得できる答え)はダウンロードしたファイル名に顔の角度が「+30」や「-45」などの形で含まれているのでこれを正規表現で切り出してそのまま利用します。
※つまり、ある顔データを入力すると「この顔の角度は+15(左に15度)です」などという形で答えを教えてくれるという形になります。
【実際のコード】
実際の機械学習コードは以下になります。
※このコードを使うには 「顔の入れ替えを試してみた」 で紹介した dlib の顔検出環境が必要になりますので事前に準備しておく必要があります。
(後で知りましたが、dlib は python のパッケージマネージャーの pip や anaconda で楽に準備できるようですね^^;)
※ path/to/***** となっている部分は自分の環境に合わせてください。テストした環境は python 2.7.12 です。
# -*- coding: utf-8 -*-
import sys
import cv2
import dlib
import glob
import re
import pickle
import numpy as np
from sklearn import svm
def get_distance(landmarks, ids):
id1 = ids[0]
id2 = ids[1]
point1 = landmarks[id1]
point2 = landmarks[id2]
return np.linalg.norm(np.array(point2)-np.array(point1))
def get_face_h_ratio(landmarks):
length1 = get_distance(landmarks, [0,30])
length2 = get_distance(landmarks, [16,30])
length3 = get_distance(landmarks, [4,30])
length4 = get_distance(landmarks, [12,30])
return (length1+length3)/(length2+length4)
predictor = dlib.shape_predictor('/path/to/shape_predictor_68_face_landmarks.dat') # Predictorファイル
detector = dlib.get_frontal_face_detector()
train_angles = []
train_labels = []
for i in range(1,16):
dir_path = '/path/to/head_pose_images/Person%02d/*' % i # ダウンロードした顔画像フォルダ
print dir_path
paths = glob.glob(dir_path)
for path in paths:
m = re.search(r'([\+\-]+([0-9]{1,2}))([\+\-]+([0-9]{1,2}))\.jpg$', path)
if m:
im = cv2.imread(path, cv2.IMREAD_COLOR)
rects = detector(im, 1)
if len(rects) == 0:
continue
landmarks = np.matrix([[p.x, p.y] for p in predictor(im, rects[0]).parts()]) # 顔ポイントを取得
if landmarks is not None:
label = m.group(1) # ファイル名から顔の左右角度を切り出す(yaw)
ratio = get_face_h_ratio(landmarks) # 顔の左右距離から比率を取得
train_angles.append([ratio])
train_labels.append(label)
clf = svm.SVC()
clf.fit(train_angles, train_labels) # 取得したデータで機械学習する
pickle.dump(clf, open('yaw_angles.pkl', 'wb')) # 学習した結果を後でも使えるようにファイル保存
このコードを実行すると「yaw_angle.pkl」というファイルが作成されます。
これが学習結果になりますので後で顔角度を取得したい場合は、以下のようにこのファイルをロードして使うことになります。
path = 'checking_image.jpg'
im = cv2.imread(path, cv2.IMREAD_COLOR)
rects = detector(im, 1)
if len(rects) == 0:
sys.exit()
landmarks = np.matrix([[p.x, p.y] for p in predictor(im, rects[0]).parts()]) # 顔ポイントを取得
if landmarks is not None:
with open('yaw_angles.pkl', 'rb') as f:
clf = pickle.load(f)
ratio = get_face_h_ratio(landmarks) # 顔の左右距離から比率を取得
pre = clf.predict([ratio])
print pre # 配列で答え(ここでは角度)が返ってきます。
※ 注: ここでは import は省略しています。
以上が今回の機械学習の手順になります。
ただし、途中でも書きましたがこれだけでは学習データが少ないと思いますので他のデータも必要になってくるかと思います。
それにしても機械学習はすごいですね。
今回は左右の顔の向き(yaw)でしたが、同じようにすることで上下(pitch)も学習できるでしょうし、顔だけでなくインターネット上に無限にあるデータをうまく学習させればいろいろと面白いことができるのではないでしょうか。
今後は tensorflow の方にもチャレンジしてみたいと思います。
ではまた(^^)