2019年版いろんなアイドルの平均顔を作ってみた(Python)

dlib

今更ながら、アイドルの平均顔を作ってみたくなったのでPythonで作ってみた。

先に結果を載せるとこんな感じになりました。(大量に作ったので、いい感じにできたやつを先に紹介します)
※各グループの公式サイトの画像を使用しているので、完成度にはばらつきがあります。

欅坂46

欅坂46の平均顔

乃木坂46

乃木坂46の平均顔

AKB48

AKB48の平均顔

日向坂や他の48グループ、ハロプロ、韓国アイドルなどいろんなグループの平均顔もつくったのでこちらの記事にまとめて載せておきました。

坂道シリーズの期生ごとの平均顔や、48のチームごとの平均顔も作っています。

この記事では、Pythonでのコーディングについて書いています。

目次

画像を集める

まずは、平均顔を作りたいアイドルグループの画像を探します。
平均顔を作るためにはある程度正面を向いた画像がよろしいので、公式サイトの宣材写真的なやつをとってくることにしました。

画像は自分で一個一個保存していってもいいですが、AKBとかは人数が多いのでスクレイピングしてしまったほうが楽だと思います。
私は、こんな感じで画像を一気に集めました。保存するファイル名とかは適当です。

import requests
from bs4 import BeautifulSoup
 
URL = 'https://www.akb48.co.jp/about/members/' #AKB48の公式サイト
 
images = []
soup = BeautifulSoup(requests.get(URL).content,'lxml')
 
for link in soup.find_all(img):
    if link.get(src).endswith(.jpg):
        images.append(link.get(src))
 
for i, target in enumerate(images):
    try:
        resp = requests.get(target)
    except:
        continue
 
    with open('./akb_{}.jpg'.format(i), 'wb') as f:
        f.write(resp.content)

スクレイピングはこちらのサイトが参考になりました。

Pythonで画像スクレイピングをしよう – Qiita

 

画像の左右を反転する

取得した画像は、右を向いている人や左を向いている人、正面を向いている人などバラバラです。これでは平均顔がきれいに作れないので、向きを合わせるために画像を反転させます。
今回は全員が向って左向きになるように合わせました。正面の人はそのままにします。

import cv2
 
img_path = ./image_path/filename.jpg
img = cv2.imread(img_path) #画像読み込み
img = cv2.flip(img, 1) #反転
cv2.imwrite(img_path, img) #上書き保存

  

画像を回転させ、顔がまっすぐになるようにする

すべての画像の顔の部分がしっかり重なった、きれいな平均顔を作るためには集めてきた画像を回転させて顔をまっすぐにしてやる必要があります。

画像を回転させて顔をまっすぐにする
画像を回転させて顔をまっすぐにする(乃木坂46齋藤飛鳥さん)

そのために、今回はdlibという機械学習ライブラリを使って顔器官(顔のパーツ)を検出し、左右の目の高さがそろうように画像を回転させようと思います。

参考
機械学習のライブラリ dlib – Qiita

dlibで顔を検出

まずは、dlibで顔検出を行います。
dlibのget_frontal_face_detector()により、顔の位置の矩形情報を得ることができます。

今回は、画像に写っている顔は1つだけという想定でやっていきます。

import cv2
import dlib
 
img_path = ./saito_asuka.jpg
 
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
 
detector = dlib.get_frontal_face_detector()
faces = detector(img)
 
for i, rect in enumerate(faces):
    cv2.rectangle(img, (rect.left(), rect.top()), (rect.right(), rect.bottom()), (255, 255, 0), thickness=2)
    #得られた顔の位置に矩形を描画

矩形を描画した画像を表示してみるとこんな感じになっています。

顔検出した画像

このようにして、画像の顔の位置を得ることができました。

dlibで顔のパーツを検出

次に、dlibのshape_predictor()で顔器官の検出をします。
shape_predictor()は人の顔の画像を入力すると、目や口や鼻などの顔の重要なランドマークの位置を特定してくれます。

顔器官の検出を行うには、学習済みデータが必要です。
http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2

こちらを解凍して使いました。

from imutils import face_utils
 
predictor_path = ./shape_predictor_68_face_landmarks.dat
predictor = dlib.shape_predictor(predictor_path)
 
faces = detector(img)
for i, rect in enumerate(faces):
    shape = predictor(img, rect)
    shape = face_utils.shape_to_np(shape)
    #顔のランドマークの位置に対応した68個の座標を得る
 
    for point in shape:
        cv2.circle(img, tuple(point), 1,  (255, 255, 0), -1)
        #得られたランドマークの座標を画像に描画

そうすると、こんな感じになります。

顔器官の検出

点の数は68個あり、それぞれの点の番号と顔のパーツが対応している。
例えば、向かって左目は36~41番目の点と対応しており、右目は42~47番目と対応している。

 左右の目の位置を求める

次に、左右の目の位置を求めていきます。
先ほど得た68個を使います。

left_eye_center = np.zeros(2)
right_eye_center = np.zeros(2)
     
for left_eye in shape[36:42]: #向かって左目
    left_eye_center += left_eye
for right_eye in shape[42:48]: #右目
    right_eye_center += right_eye
         
left_eye_center = left_eye_center / 6
right_eye_center = right_eye_center / 6
#6点の座標の平均をとり、それぞれの目の中心とする
 
cv2.circle(img, tuple(left_eye_center.astype(np.int)), 2, (255, 255, 0), -1)
cv2.circle(img, tuple(right_eye_center.astype(np.int)), 2, (255, 255, 0), -1)
#左右それぞれの目の中心に点を打つ

こうして得られた画像はこんな感じです。

目の中心座標

左右それぞれの目のほぼ中心を求めることができています。

画像を回転させ、左右の目の高さを合わせる

左右の目の座標がわかったので、顔がどれだけ傾いているかを計算することができます。

左目が原点の座標系

上図のように向かって左目の中心座標を原点とするXY座標系を考え、右目の中心点をP(x, y)とすると顔の傾き\theta

\theta = arctan\frac{y}{x}

で得られます。

そして、左目を中心にして画像を-\theta 回転させてやれば顔の傾きはゼロになります。

import math

row, col, ch = img.shape

tan = (left_eye_center[1] - right_eye_center[1]) / (right_eye_center[0] - left_eye_center[0])
deg = math.degrees(math.atan(tan))

M = cv2.getRotationMatrix2D(tuple(left_eye_center.astype(np.int)), -deg, 1)
#2次元回転を表すアフィン変換を求める
img = cv2.warpAffine(img, M, (col, row), borderValue=(255, 255, 255))
#borderValueは領域外の色を指定

参考
画像の幾何学変換 — opencv 2.2 documentation
OpenCV – 画像座標系における回転、拡大縮小について – Pynote

こうしてできた画像がこちら

回転を施した画像

画像を回転させたことにより、しっかりと顔の傾きをなくすことができています。

画像を重ねるときの基準点を計算する

画像と画像を重ねて平均顔を作るときには、1つ目の画像の顔の部分と2つ目の画像の顔の部分がしっかりと重なるようにしなければなりません。
今回は、画像と画像の目の位置を合わせて重ねていくことにします。
先ほど求めた左目の中心点と右目の中心点の線分の中心を顔の基準点とし、そこが重なるようにすることで目の位置を合わせます。

下の図の赤い点が基準点です。

顔の基準点

eye_center = (left_eye_center + right_eye_center) / 2
#eye_centerが基準点
#この基準点の座標は回転させる前の座標

いま、顔の傾きをなくすために画像を回転させているので、回転後の基準点の座標を計算してやる必要があります。

座標の回転

上図のように、点(x, y)を角度 \alpha 回転させた点(X, Y)は加法定理より

(1)    \begin{align*} X &= r{\rm cos}(\theta + \alpha) \\ &= r({\rm cos}\theta{\rm cos}\alpha - {\rm sin}\theta{\rm sin}\alpha)\\ &= r{\rm cos}\theta{\rm cos}\alpha - r{\rm sin}\theta{\rm sin}\alpha\\ &= x{\rm cos}\alpha - y{\rm sin}\alpha \end{align*} \begin{align*} Y &= r{\rm sin}(\theta + \alpha) \\ &= r({\rm sin}\theta{\rm cos}\alpha + {\rm cos}\theta{\rm sin}\alpha)\\ &= r{\rm sin}\theta{\rm cos}\alpha + r{\rm cos}\theta{\rm sin}\alpha\\ &= y{\rm cos}\alpha + x{\rm sin}\alpha \end{align*}

となります。これを用いて、回転後の基準点を計算します。

rad = math.radians(-deg) #これが α (弧度法)

x_eye_center_rotated = left_eye_center[0] + (eye_center[0] - left_eye_center[0]) * math.cos(rad) - (left_eye_center[1] - eye_center[1]) * math.sin(rad)
#回転後の基準点のx座標
y_eye_center_rotated = left_eye_center[1] - (eye_center[0] - left_eye_center[0]) * math.sin(rad) - (left_eye_center[1] - eye_center[1]) * math.cos(rad)
#回転後の基準点のy座標

cv2.circle(img, (int(x_eye_center_rotated), int(y_eye_center_rotated)), 2, (255, 0, 0), -1)

こうしてできた画像がこちら。

回転後の基準点

きちんと回転後の基準点の計算ができていますね。

複数の画像を重ね合わせ平均顔をつくる

いよいよ、画像を重ねて平均顔を作っていきます。
最終的なコードはこのようになりました。

import numpy as np
import cv2
import dlib
from imutils import face_utils
import math
import glob
from copy import copy

folder_path = ./nogizaka46/ #乃木坂の画像が詰まってるフォルダ
img_path_list = glob.glob(folder_path + *.jpg)

detector = dlib.get_frontal_face_detector()
predictor_path = ./shape_predictor_68_face_landmarks.dat
predictor = dlib.shape_predictor(predictor_path)

for img_num, img_path in enumerate(img_path_list):
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
row, col, ch = img.shape

faces = detector(img)
for face_num, rect in enumerate(faces):
shape = predictor(img, rect)
shape = face_utils.shape_to_np(shape)

left_eye_center = np.zeros(2)
right_eye_center = np.zeros(2)

for left_eye in shape[36:42]:
left_eye_center += left_eye
for right_eye in shape[42:48]:
right_eye_center += right_eye

left_eye_center = left_eye_center / 6
right_eye_center = right_eye_center / 6

#顔の大きさをそろえるときに使う
#left_right_eye_dist = math.sqrt((left_eye_center[0] - right_eye_center[0]) ** 2 + (left_eye_center[1] - right_eye_center[1]) ** 2)

tan = (left_eye_center[1] - right_eye_center[1]) / (right_eye_center[0] - left_eye_center[0])
deg = math.degrees(math.atan(tan))
rad = math.radians(-deg)
eye_center = (left_eye_center + right_eye_center) / 2

M = cv2.getRotationMatrix2D(tuple(left_eye_center.astype(np.int)), -deg, 1)
img = cv2.warpAffine(img, M, (col, row), borderValue=(255, 255, 255))

#回転中心の座標はint型になっているはずなので計算精度を上げる処理
left_eye_center = left_eye_center.astype(np.int)

x_eye_center_rotated = left_eye_center[0] + (eye_center[0] - left_eye_center[0]) * math.cos(rad) - (left_eye_center[1] - eye_center[1]) * math.sin(rad)
y_eye_center_rotated = left_eye_center[1] - (eye_center[0] - left_eye_center[0]) * math.sin(rad) - (left_eye_center[1] - eye_center[1]) * math.cos(rad)

#最初の画像を基準として、その上に次々重ねていくイメージ
if img_num == 0:
row_original = row
col_original = col
x_eye_center_rotated_original = x_eye_center_rotated
y_eye_center_rotated_original = y_eye_center_rotated
img_original = copy(img)

#顔の大きさをそろえるときに使う
#left_right_eye_dist_original = left_right_eye_dist

#2つ目以降の画像の処理
else:
   #顔の大きさをそろえるときに使う
#scale = left_right_eye_dist_original / left_right_eye_dist #originalとの左右の目の距離の比
#img = cv2.resize(img, (int(col * scale), int(row * scale))) #originalに合わせて画像をresize
#x_eye_center_rotated = x_eye_center_rotated * scale
#y_eye_center_rotated = y_eye_center_rotated * scale #imgのresizeに合わせて目の中心座標も変更

#1枚目の画像の基準点と2つ目以降の画像の基準点との距離
x_eye_center_dist_from_original = x_eye_center_rotated_original - x_eye_center_rotated
y_eye_center_dist_from_original = y_eye_center_rotated_original - y_eye_center_rotated

#基準点の距離分平行移動させて基準点を合わせる
M_shift = np.float32([[1, 0, x_eye_center_dist_from_original], [0, 1, y_eye_center_dist_from_original]])
img = cv2.warpAffine(img, M_shift, (col_original, row_original), borderValue=(255, 255, 255))

#すべての画像のweightが等しくなるように重ねて平均顔完成
img_original = cv2.addWeighted(img_original, 1 - 1 / (img_num + 1), img, 1 / (img_num + 1), 0)

cv2.imwrite(./average_face/nogizaka46.jpg, cv2.cvtColor(img_original, cv2.COLOR_RGB2BGR))

所々出てくる”#顔の大きさをそろえるときに使う”という部分は、顔の大きさが違う画像が含まれるときなどに使用します。
1枚目の基準となる画像の目と目の距離と、2枚目以降の目と目の距離の比から画像を拡大縮小して顔の大きさをそろえます。

今回は公式サイトの画像を使用しているので、同グループの顔の大きさや画像の大きさがほぼ一定なので使用しませんでした。
ただし、別グループどうしの平均顔を作る際には使うといいと思います。

そんなこんなで完成した画像がこちらです。

乃木坂46の平均顔

乃木坂46の平均顔はこんな感じらしいです。しっかりと目の位置があっているのでやりたかったことはできました。

最後に

ここまで読んでいただきありがとうございました。

こちらの記事では、今回作成したいろんなアイドルの平均顔を紹介しています。

私の趣味にだいぶ偏った内容になっています。
みたいグループがなかったら、上のコードで作ってみてください。

コメント

  1. […] 前回、2019年版いろんなアイドルの平均顔を作ってみた(Python)で紹介した平均顔の作り方をつかって様々なアイドルグループの平均顔を作成したので、紹介します。 […]

タイトルとURLをコピーしました