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

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

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

欅坂46

欅坂46の平均顔



乃木坂46

乃木坂46の平均顔



AKB48

AKB48の平均顔



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

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


目次

画像を集める

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

画像は自分で一個一個保存していってもいいですが、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)は加法定理より

\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の平均顔はこんな感じらしいです。しっかりと目の位置があっているのでやりたかったことはできました。


最後に

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

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

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


アイドル平均顔ギャラリー

TWICE

TWICEの平均顔



IZ*ONE

IZ*ONEの平均顔



坂道グループ

乃木坂46

1期生

乃木坂46 1期生の平均顔






2期生

乃木坂46 2期生の平均顔






3期生

乃木坂46 3期生の平均顔






4期生

乃木坂46 4期生の平均顔







乃木坂46の平均顔

乃木坂46の平均顔









欅坂46


1期生

欅坂46 1期生の平均顔







2期生

欅坂46 2期生の平均顔







欅坂46の平均顔

欅坂46の平均顔







日向坂46

1期生

日向坂46 1期生の平均顔






2期生

日向坂46 2期生の平均顔







日向坂46の平均顔

日向坂46の平均顔








坂道グループの平均顔

坂道グループの平均顔








48グループ

AKB48


チームA

AKB48チームAの平均顔







チームK

AKB48チームKの平均顔






チームB

AKB48チームBの平均顔






チーム4

AKB48チーム4の平均顔






チーム8


北海道・東北エリア

AKB48チーム8北海道・東北エリアの平均顔






関東エリア

AKB48チーム8関東エリアの平均顔






中部エリア

AKB48チーム8中部エリアの平均顔






関西エリア

AKB48チーム8関西エリアの平均顔







中国・四国エリア

AKB48チーム8中国・四国エリアの平均顔







九州・沖縄エリア

AKB48チーム8九州・沖縄エリアの平均顔







チーム8の平均顔

AKB48チーム8の平均顔







AKB48研究生

AKB48研究生の平均顔







AKB48の平均顔

AKB48の平均顔







SKE48


チームS

SKE48チームSの平均顔







チームK

SKE48チームKⅡの平均顔







チームE

SKE48チームEの平均顔








SKE48研究生

SKE48研究生の平均顔







SKE48の平均顔

SKE48の平均顔








NMB48


チームN

NMB48チームNの平均顔







チームM

NMB48チームMの平均顔






チームBⅡ

NMB48チームBⅡの平均顔







NMB48研究生

NMB48研究生の平均顔







NMB48の平均顔

NMB48の平均顔









HKT48


チームH

HKT48チームHの平均顔







チームK

HKT48チームKⅣの平均顔







チームTⅡ

HKT48チームTⅡの平均顔







HKT48研究生

HKT48研究生の平均顔







HKT48の平均顔

HKT48の平均顔







NGT48


1期生

NGT48 1期生の平均顔






NGT48研究生

NGT48研究生の平均顔







NGT48の平均顔

NGT48の平均顔








STU48


1期生

STU48 1期生の平均顔







STU48研究生

STU48研究生の平均顔







STU48の平均顔

STU48の平均顔








48グループの平均顔

48グループの平均顔










今回紹介したすべてのアイドルの平均顔

全体の平均顔