Python 備忘録

Python-ver: 3.6.3

Pythonで類似度検出③ 特徴点マッチング

久しぶりの更新です。
最近仕事が忙しいからかなかなか時間が取れません。


余談ですが、自分は月に一冊ぐらいのペースで技術書を買って読むのですが、「達人プログラマー
という本が非常に面白かったです。プログラマなら読んで損はない本でした。

新装版 達人プログラマー 職人から名匠への道

新装版 達人プログラマー 職人から名匠への道

今回は特徴点を使って類似度を検出したいと思います。

画像ファイル

今回、テストに使うのはこちらの画像です。
f:id:sh0122:20180204162245j:plain:h300:w400
アインシュタイン einstein.jpg
f:id:sh0122:20180204162249j:plain:h300:w400
女性 girl.jpg
f:id:sh0122:20180204162252j:plain:h300:w400
男性 man.jpg
f:id:sh0122:20180204162255j:plain:h300:w400
紳士 man2.jpg
f:id:sh0122:20180204162256j:plain:h300:w400
おばあさん old_woman.jpg

アインシュタインを比較画像にして、アインシュタインに似ている画像をプログラムで見つけます。
この中では年齢と外見からおばあさんの画像が一番近いかと思います。

ソース

# -*- coding: UTF-8 -*-

"""
	pattern_matching.py

	-version: 3.6.3
	-library: opencv
"""

import cv2
import os

def main():
	TARGET_FILE = 'einstein.jpg'
	IMG_DIR = '../images/'

	bf = cv2.BFMatcher(cv2.NORM_HAMMING)
	# 特徴点算出のアルゴリズムを決定(コメントアウトで調整して切り替え)
	# detector = cv2.ORB_create()
	detector = cv2.AKAZE_create()
	(target_kp, target_des) = calc_kp_and_des(IMG_DIR + TARGET_FILE, detector)

	print('TARGET_FILE: %s' % (TARGET_FILE))

	files = os.listdir(IMG_DIR)
	for file in files:
	    if file == '.DS_Store' or file == TARGET_FILE:
	        continue

	    comparing_img_path = IMG_DIR + file
	    try:
	        (comparing_kp, comparing_des) = calc_kp_and_des(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 = 100000

	    print(file, ret)

def calc_kp_and_des(img_path, detector):
	"""
		特徴点と識別子を計算する
		:param str img_path: イメージのディレクトリパス
		:param detector: 算出の際のアルゴリズム
		:return: keypoints
		:return: descriptor
	"""
	IMG_SIZE = (200, 200)
	img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
	img = cv2.resize(img, IMG_SIZE)
	return detector.detectAndCompute(img, None)

if __name__ == '__main__':
	main()

ソースはほぼまんまこちらを参考にさせていただきました。ありがとうございます。
Python + OpenCVで画像の類似度を求める - Qiita

今回のエラー

コードを試しているときに以下のエラーが出ました。

error: (-215) ssize.width > 0 && ssize.height > 0 in function cv::resize

これはどうやらイメージファイルのパスの指定が間違っていたため、読み込みがうまくできなかったエラーでした。
パスを修正してなんとか回避。

結果

出力は以下のようになりました。
AKAZEの場合

TARGET_FILE: einstein.jpg
girl.jpg 137.53623188405797
man.jpg 132.2753623188406
man2.jpg 154.91304347826087
old_woman.jpg 126.91304347826087

ORBの場合

TARGET_FILE: einstein.jpg
girl.jpg 71.87186629526462
man.jpg 70.72980501392757
man2.jpg 75.0
old_woman.jpg 70.76044568245125

ここで注意したいのは数字が低い方が類似度が高いということになります。
AKAZEの場合はうまいこといっているようです。
ORBの場合は、若干男性のほうが似ているというように出ています。
AKAZEのほうが精度がよいのでしょうか。

特徴点を結びつけたものがこちら
f:id:sh0122:20180204171201p:plain

表示させるコードはこちらです。表示させるだけなので大分雑です。
使う場合はリファクタリングしてください。

def show_imgs_match():
	img1 = cv2.imread('../images2/einstein.jpg')
	img2 = cv2.imread('../images2/man3.jpg')
	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()