やること
名探偵がひらめくときに頭に電撃が走りますが、このピキーンを顔の特徴点検出で付けられないかと思って試してみました。
参考文献
顔の特徴点を検出するパッケージ「dlib」は多くのサイトで解説されています。顔の検出とランドマークの検出を明確に分けて説明されているこちらが参考になりました。
[OpenCV+dlib] 顔認識の実験 - Qiita
#1.はじめにOpenCVとdlibで顔認識を実験してみました。#2. Face Detector()まず、顔を検出します。顔の検出というのは、「画像の中から、人の顔を認識し、その位置を特定…
環境
WinPython3.6をおすすめしています。
WinPython - Browse /WinPython_3.6/3.6.7.0 at SourceForge.net
Portable Scientific Python 2/3 32/64bit Distribution for Windows
Google Colaboratoryが利用可能です。
Google Colab
用いた画像
「MetaHuman Creator」のKeijiさんの画像を使用しました。ほんのり阿笠博士。
実装
dlibが必要なのでpipします。
pip install dlib
必要なパッケージをインポートします。あらかじめ「shape_predictor_68_face_landmarks.dat」という100MBくらいある検出モデルファイルをダウンロードしておく必要があります(ダウンロードリンクは参考文献サイトにもあります)。
import cv2
import numpy as np
import matplotlib.pyplot as plt
import dlib
#顔検出のインスタンス
detector = dlib.get_frontal_face_detector()
#ランドマーク検出のインスタンス
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
画像を読み込んでランドマークを検出、プロットしてみます。
#表示
def show(img, landmarks):
#画像
plt.imshow(img[:, :, [2, 1, 0]], vmin=0, vmax=255) #BGRをRGBで表示
#ランドマーク
for (x, y) in landmarks:
plt.plot(x, y, '+w')
plt.show()
#読み込み
img = cv2.imread('14-27_sample.png')
#顔を1つだけ検出
face = detector(img, 1)[0]
#顔のランドマークを検出
landmarks = np.array([[p.x, p.y] for p in predictor(img, face).parts()])
print(landmarks.shape)
#表示
show(img, landmarks)
(68, 2)
landmarksの添字は0~67番まであります。ここでは1番から16番を貫通させてみましょう。2点の傾き(=顔のロール角)を次のように計算します。
ロール角をラジアンで求めて、顔に棒をぶっ刺します。三角関数よりも金融経済を学ぶべきではないか?いま役に立ちましたよ!
#ピキーン付きの表示
def show2(img, landmarks, radian_roll):
#画像
plt.imshow(img[:, :, [2, 1, 0]], vmin=0, vmax=255) #BGRをRGBで表示
#画像左側のピキーン
barlen = 500
barh = barlen * np.sin(radian_roll)
barw = barlen * np.cos(radian_roll)
plt.plot([landmarks[1, 0], landmarks[1, 0]-barw], [landmarks[1, 1], landmarks[1, 1]-barh], '-w')
#画像右側のピキーン
plt.plot([landmarks[16, 0], landmarks[16, 0]+barw], [landmarks[16, 1], landmarks[16, 1]+barh], '-w')
#はみ出すのでトリミング
plt.xlim(0, img.shape[1])
plt.ylim(img.shape[0], 0)
plt.show()
#ロール角度の計算
left = landmarks[1, 0] #1番のx座標
bottom = landmarks[1, 1] #1番のy座標
right = landmarks[16, 0] #16番のx座標
top = landmarks[16, 1] #16番のy座標
tan = (top - bottom) / (right - left)
radian_roll = np.arctan(tan) #y軸が逆さまのため符号違いであることに注意
print(radian_roll)
#ピキーン付きの表示
show2(img, landmarks, radian_roll)
-0.09065988720074511
注意点として、画像はy軸が逆さまなのでラジアンが見た目と逆の符号で得られます。でもそのまま使ってやれば大丈夫です。
動画を処理すると
こちらの動画から
ランドマークを検出して
ピキーンできました。
おわりに
本当は目からビームを出したかったのですがロール・ピッチ・ヨーが大変で諦めました。。