OpenCV&Pythonで画像の類似度を計算させる〜イケメンの顔比較

・動機
・やりたいこと
・準備
・類似度の計算
・実行コード
・実行結果
・おまけ

動機

画像系の技術にあまり関心が無かったのですが、とある知人が福士蒼汰のような雰囲気の男性が好みであると発言されたことを発端に、福士蒼汰に最も顔の近い知人を見つけるというプライベートなミッションを仰せつかりました。
そこで、まずは最も楽だろうと思われる、画像間の類似度を計算する方法について調べてみました。顔のパーツを検知して、パーツ同士で比較するなどのレベルでは無いことをご了承下さい。ちなみに、比較画像は国民的アイドルである嵐のメンバーの画像としました。今回の実践で、与えられた画像の中で、福士蒼汰に最も近そうな嵐のメンバーの写真がわかることになります。

やりたいこと

画像間の類似度の計算

準備

・Python2.7
・OpenCV

OSX環境における準備にあたっては以下の情報が参考になりました。
PythonでOpenCVを使う
Mac OS X で OpenCV 3 + Python 2/3 の開発環境を整備する方法

類似度の計算

こちらのブログにある計算手法とコードを使いました。
How-To: Python Compare Two Images

紹介されているコードで以下の評価指標が計算できます。
・Mean Squared Error (MSE)
・Structural Similarity Index (SSIM)・・・0〜1の値を取ります。

指標について、詳しくはこちらの論文に書かれています。
Image Quality Assessment: From Error Visibility to Structural Similarity

実行コード

紹介されていたコードは画像サイズが同じでないと計算ができなかったので、まずは画像のサイズを整えるためのコードが以下のようになります。コードはこちらのものを使いました。
Python – アンチエイリアスで写真をキレイに縮小

これだけではサイズが同一にならなかったので、追加で以下のコードを実行しました。

以下は、類似度計算の実行用コードとなります。

実行結果

福士蒼汰との比較をしており、画像上部にMSEとSSIMが出ています。嵐の大野くんが一番近いようです。

original

aiba

oono

sakurai

ninomiya

matsumoto

おまけ

イケメンではないですが、出川哲朗でも計算してみました。

degawa

今回最も高い値が出てしまいました。やはり、顔のパーツを識別して、そのパーツ間の類似度の計算ができないといけないような気がします。悔しいので今後も画像認識系の技術について向き合ってみようと思います。

References   [ + ]

1. x, y), Image.ANTIALIAS) else: # アンチエイリアスなしで縮小 resize_img = resize_img.resize((x, y
2. x, y # リサイズ後の画像を保存 resize_img.save(after, 'jpeg', quality=100) print "RESIZED!:{}[{}x{}] --> {}x{}".format(filename, before_x, before_y, x, y) # 実行 main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# -*- coding: utf-8 -*-
import commands
from PIL import Image
import re
 
# 縮小する際の画像の高さピクセル
PHOTO_HEIGHT = 400
 
# 画像があるフォルダのフルパス
BASE_DIR = "/任意のフォルダのパス/"
 
# 画像の正規表現名
PHOTO_REGEX = r"pic00.*.[jpg|jpeg]"
 
# リサイズ後の画像の接頭語
PHOTO_RESIZE_PREFIX = "r_"
 
def main():
    # 画像フルパスを取得
    _cmd = "cd {} && ls".format(BASE_DIR)
    l = commands.getoutput(_cmd)
    l = l.split("\n")
    l = [_l for _l in l if re.match(PHOTO_REGEX, _l)]
 
    # 出力用のフォルダを生成
    commands.getoutput("mkdir {}/output".format(BASE_DIR))
 
    # 既存ファイルを readモードで読み込み
    for _l in l:
        before_path = '{}/{}'.format(BASE_DIR, _l)
        filename = '{}{}'.format(PHOTO_RESIZE_PREFIX, _l)
        after_path = '{}/output/{}'.format(BASE_DIR, filename)
        resize(before_path, after_path, filename=_l)  # 縮小
 
 
def resize(before, after, height=PHOTO_HEIGHT, filename="", aa_enable=True):
    """
    画像をリサイズする
    :param str before: 元画像ファイルパス
    :param str after: リサイズ後の画像ファイルパス
    :param int height: リサイズ後の画像の高さ
    :param bool aa_enable: アンチエイリアスを有効にするか
    :return:
    """
    # 画像をreadonlyで開く
    img = Image.open(before, 'r')
    # リサイズ後の画像ピクセルを計算
    before_x, before_y = img.size[0], img.size[1]
    x = int(round(float(height / float(before_y) * float(before_x))))
    y = height
    resize_img = img
    if aa_enable:
        # アンチエイリアスありで縮小
        resize_img.thumbnail((x, y), Image.ANTIALIAS)
    else:
        # アンチエイリアスなしで縮小
        resize_img = resize_img.resize((x, y))
 
    # リサイズ後の画像を保存
    resize_img.save(after, 'jpeg', quality=100)
    print "RESIZED!:{}[{}x{}] --> {}x{}".format(filename, before_x, before_y, x, y)
 
 
# 実行
main()

これだけではサイズが同一にならなかったので、追加で以下のコードを実行しました。

以下は、類似度計算の実行用コードとなります。

以下は、類似度計算の実行用コードとなります。