あけましておめでとうございます。堤 真聖です。
この間ショッピングモールに出かけたらみんなポケモンGOをやっていて驚きました。
ポケモンGO、1週間だけやって辞めたけど、まだ結構流行っているんですね。
せっかくだしポケモンの懐かしさに浸りつつ
「自分の顔に似てるポケモンを抽出してくれる解析プログラム」
でも作ってみようかな!
- openCVで画像を取得
- openCVで画像を調整
- openCVで特微量計算
- openCVで類似度判定
はい。思いっきりopenCV
の力を借りて実装して行きます!
今回810種類のポケモンと闘わせる画像はコチラ。

の、つもりだったんですが、本来顔同士を比較したかったものの、ポケモンの顔を検出するのは至極困難なので、全身同士を比較することにします!(ごめんね俺の変顔)

まずは特微量を算出。
コードはGitHubにアップしているので要所をかいつまんで行きます。
import cv2
import os
def calc_feature(img_path: str, detector: cv2.ORB_create()):
IMG_SIZE = (100, 100)
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
img = cv2.resize(img, IMG_SIZE)
return detector.detectAndCompute(img, None)
次に、810もある画像一つ一つを比較し特微量の差を計算します
target_image = 'me.jpg'
image_directory = '../../blog/material_images/'
# マッチング
bf = cv2.BFMatcher(cv2.NORM_HAMMING)
# 特微量算出
# detector = cv2.ORB_create()
detector = cv2.AKAZE_create()
(target_kp, target_des) = calc_feature(image_directory + target_image, detector)
min_value = 1000
min_path = ''
files = os.listdir(image_directory)
for file in files:
if file == '.DS_Store' or file == target_image:
continue
comparing_img_path = image_directory + file
try:
(comparing_kp, comparing_des) = calc_feature(
comparing_img_path, detector)
#画像同士をマッチング
matches = bf.match(target_des, comparing_des)
dist = [m.distance for m in matches]
#類似度計算
ret = sum(dist) / len(dist)
except cv2.error:
ret = 1000
if min_value > ret:
min_value = ret
min_path = file
こうなります。
結果
target: me.jpg
parasect.png 165.36363636363637
lumineon.png 157.27272727272728
.
.
.
latios.png 151.63636363636363
mew.png 170.36363636363637
ekans.png 173.9090909090909
今回計算しているのは「特微量の差」なので数値が低いほど類似度が高いということになります。
どれも少し値が大きすぎますね。もしかして俺はポケモンには似てないってことかな(笑)。
もう少し詰めて行きます。
アルゴリズムをORBに変えてみます。
出力結果
target: me.jpg
parasect.png 87.61702127659575
lumineon.png 81.08510638297872
.
.
.
latios.png 71.34042553191489
mew.png 86.91489361702128
ekans.png 86.68085106382979
AKAZEの半分ほどの値になりましたね。
今思ったけど自分からポケモンに似せていくってなんかシュール。
それでは結果を可視化して行きます。
def show_imgs_match(file):
img1 = cv2.imread('../../blog/material_images/me.jpg')
img2 = cv2.imread('../../blog/material_images/' + file)
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
akaze = cv2.ORB_create()
kp1, des1 = akaze.detectAndCompute(gray1, None)
kp2, des2 = akaze.detectAndCompute(gray2, None)
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
matches = sorted(matches, key = lambda x:x.distance)
img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None, flags=2)
plt.imshow(cv2.cvtColor(img3, cv2.COLOR_BGR2RGB))
plt.show()
さて、ここで問題です。俺はどのポケモンに似ているでしょうか?(810択クイズ)
正解は、、、

確かになんか似ている。。

値が62程度まで落ち着きました。
ちなみにAKAZEでは


あ〜〜。。
ちなみにこちらの場合、値は100前後になりました。
AKAZEとは、要は画素の応答値を閾値と比較し最大値であればと特微点とする方法。なので出っ張った鼻やズボンの角などのエッジ部分が特に紐づいています。
詳しい非線形拡散方程式
の計算方法は以下より。
https://qiita.com/nct_nct/items/c9bf6e502248a8939a0d
対してORBは、輝度値やエッジを特微点として判定するため、同じような色合いを紐付けます。
つまり、ポケモンは「絵」なので、凹凸よりも輝度やエッジによる特徴が特微点として検出されやすいということ。
なので今回はAKAZEよりもORB方が類似度が高かったんですね。
要するに、AKAZEの場合、同じような形のポケモンが、ORBの場合、同じような色彩のポケモンが高い類似度だと判定されるということなんですね。
ここで、ポケモンたちがどんな特徴によって分類されるのかクラスタリングしてみたいと思います。
import numpy as np
from PIL import Image
from skimage import data
from sklearn.cluster import KMeans
datasets = []
X = []
for path in os.listdir(image_directory):
X.append(path)
img = Image.open(image_directory + path)
img = img.convert('P')
img_resize = img.resize((100, 100))
img_array = np.array(img)
img_array = img_array.flatten()
datasets.append(img_array)
model = KMeans(n_clusters=15).fit(datasets)
labels = model.labels_
さて、今回はクラスタ数を15に設定し任意のクラスターを抽出してみました。
表示:
fig = plt.figure(figsize=(10, 60))
count = 0;
for cluster, path in zip(labels, os.listdir(image_directory)):
img = Image.open(image_directory + path)
img = img.convert('RGB')
img = img.resize((100, 100))
img_array = np.array(img)
if cluster == 1:
count += 1
ax = fig.add_subplot(20, 4, count)
ax.set_title(path)
ax.imshow(img_array)
plt.show()
すると、
クラスター1

クラスター2

今後の展望
- 画像の処理の精度を高める
- 特微量算出アルゴリズムの工夫
さらに精度を高めてデプロイするので楽しみにしていてください!
コメントを残す