Edited at

Python + OpenCVで画像の類似度を求める

https://qiita.com/best_not_best/items/669aaa9e1b8de647d29d にこの記事の修正版を投稿しました!

皆さんこんにちは。@best_not_bestです。

現在のお仕事は、求人サイトの案件レコメンド機能の実装や、Google AnalyticsやAdobe Analyticsを使って、そのサイトの分析をしています。(あとは動物画像を社内SlackにひたすらPostしています。)

前回のAdvent Calendarでは、Deep Learningを使った画像類似度判定を行いました。精度は良かったのですが、やはり学習データを収集することがネックとなります・・・。今回は、単純に2枚の画像を比較して画像の類似度を求めてみたいと思います。


やること

以下のおいぬ様の画像の類似度を比較します。画像はGoogle先生から拾ってきています。

精度を高めるため、正面を向いている画像を選びました。

比較画像

ファイル名
画像
説明

05.png
05
柴犬(かわいい)

比較対象

ファイル名
画像
説明

01.png
01
ダックスフント(かわいい)

02.png
02
コーギー(かわいい)

03.png
03
ゴールデン・レトリバー(かわいい)

04.png
04
柴犬(かわいい)

06.png
06
ラブラドール・レトリーバー(かわいい)

同じ犬種である、05.pngと04.pngの類似度が高くなれば(いちおう)成功です。


環境


  • マシン/OS


    • MacBook Pro (Retina, 15-inch, Mid 2014)

    • OS X Yosemite 10.10.5



  • Python


    • Python 3.5.2 :: Anaconda 4.1.1 (x86_64)



  • Pythonパッケージ


    • opencv3 3.1.0(condaでインストールします。)




ディレクトリ構成

hist_matching.py

feature_detection.py
images
├─ 01.png
├─ 02.png
├─ 03.png
├─ 04.png
├─ 05.png
└─ 06.png


検証1: ヒストグラム比較

ざっくり言うと、色合いで比較する手法です。詳細は以下を参照ください。

色合いで比較するのでグレースケール変換は行いません。また、画像サイズは一律200px × 200pxに変換して比較しています。


hist_matching.py

#!/usr/bin/env python

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

"""hist matching."""

import cv2
import os

TARGET_FILE = '05.png'
IMG_DIR = os.path.abspath(os.path.dirname(__file__)) + '/images/'
IMG_SIZE = (200, 200)

target_img_path = IMG_DIR + TARGET_FILE
target_img = cv2.imread(target_img_path)
target_img = cv2.resize(target_img, IMG_SIZE)
target_hist = cv2.calcHist([target_img], [0], None, [256], [0, 256])

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
comparing_img = cv2.imread(comparing_img_path)
comparing_img = cv2.resize(comparing_img, IMG_SIZE)
comparing_hist = cv2.calcHist([comparing_img], [0], None, [256], [0, 256])

ret = cv2.compareHist(target_hist, comparing_hist, 0)
print(file, ret)


実行すると、メモリを消費するのかSegmentation fault: 11python(18114,0x7fff7a45b000) malloc: *** error for object 0x102000e00: incorrect checksum for freed object - object was probably modified after being freed.といったエラーがまれに表示されます。解決策は見つかりませんでした(すいません)が、大量の画像を扱う場合は、比較画像の数を絞って複数回処理を行ったほうが良さそうです。


実行結果

TARGET_FILE: 05.png

01.png 0.3064316801821619
02.png -0.09702013809004943
03.png 0.5273343981076624
04.png 0.5453261576844468
06.png 0.1256772923432995

全く同じ画像の場合、類似度は1となります。05.pngと04.pngの類似度が高くなっているのが分かります。01.pngとの類似度が高いのは意外でした。


検証2: 特徴点のマッチング

2画像の特徴点を抽出し、それらの距離を比較します。以下を参考にさせていただきました。

抽出精度を高めるため、グレースケール変換を行っています。前項と同様に、画像サイズは一律200px × 200pxに変換して比較しています。


feature_detection.py

#!/usr/bin/env python

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

"""feature detection."""

import cv2
import os

TARGET_FILE = '05.png'
IMG_DIR = os.path.abspath(os.path.dirname(__file__)) + '/images/'
IMG_SIZE = (200, 200)

target_img_path = IMG_DIR + TARGET_FILE
target_img = cv2.imread(target_img_path, cv2.IMREAD_GRAYSCALE)
target_img = cv2.resize(target_img, IMG_SIZE)

bf = cv2.BFMatcher(cv2.NORM_HAMMING)
# detector = cv2.ORB_create()
detector = cv2.AKAZE_create()
(target_kp, target_des) = detector.detectAndCompute(target_img, None)

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_img = cv2.imread(comparing_img_path, cv2.IMREAD_GRAYSCALE)
comparing_img = cv2.resize(comparing_img, IMG_SIZE)
(comparing_kp, comparing_des) = detector.detectAndCompute(comparing_img, None)
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)


まれにcv2.errorを吐くため、try exceptを使っています。抽出手法はAKAZEとORBで試しました。


実行結果(AKAZE)

TARGET_FILE: 05.png

01.png 143.925
02.png 134.05
03.png 140.775
04.png 127.8
06.png 148.725


実行結果(ORB)

TARGET_FILE: 05.png

01.png 67.59139784946237
02.png 58.60931899641577
03.png 59.354838709677416
04.png 53.59498207885304
06.png 63.55913978494624

距離を算出しているので、全く同じ画像の場合の値は0となり、値が小さいほど類似度が高くなります。実施したどちらの手法でも、前項と同様に05.pngと04.pngの類似度が高くなっています。


まとめ


  • Deep Learningを使わなくてもそれなりの精度は出せる

  • 手法に応じてグレースケール変換、サイズ変換を行う

  • 特徴点抽出の場合、向きが異なる画像の比較は難しいので、同じ向きの画像を比較させる

  • 例えば、必ず正面を向いている社員証の画像どうしは比較し易い気がする

  • おいぬ様 is GOD.