皆さんこんにちは。@best_not_bestです。
現在のお仕事は、Google Analytics、Adobe Analytics、Google Cloud Platformを使って、サイトの分析のお手伝いをしています。
Deep Learningでの画像解析は精度は良いですが、学習データを収集することがネックとなります。今回は、OpenCVで2枚の画像を比較して画像の類似度を求めてみたいと思います。
やること
以下の犬画像の類似度を比較します。画像はGoogle先生から拾ってきています。
精度を高めるため、正面を向いている画像を選びました。
比較画像
ファイル名 | 画像 | 説明 |
---|---|---|
05.png | 柴犬 |
比較対象
ファイル名 | 画像 | 説明 |
---|---|---|
01.png | ダックスフント | |
02.png | コーギー | |
03.png | ゴールデン・レトリバー | |
04.png | 柴犬 | |
06.png | ラブラドール・レトリーバー |
同じ犬種である、05.pngと04.pngの類似度が高くなれば(いちおう)成功です。
環境
マシン/OS
- MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports)
- OS 10.12.6
- pyenv: 1.2.8
- pyenv-virtualenv: 1.1.3
Python
pyenv、pyenv-virtualenvで 3.7.1 の環境を構築します。
(いつの間にかAnaconda使わなくてもOpenCVが入るようになってた!)
$ mkdir hogehoge
$ cd hogehoge
$ pyenv local 3.7.1
$ pyenv virtualenv 3.7.1 hogehoge
$ pyenv local hogehoge
$ pip install -U pip
以下のライブラリをインストールします。
docopt==0.6.2
flake8==3.6.0
flake8-docstrings==1.1.0
flake8-polyfill==1.0.1
mccabe==0.6.1
numpy==1.15.4
opencv-python==3.4.4.19
pbr==5.1.1
pycodestyle==2.4.0
pydocstyle==3.0.0
pyflakes==2.0.0
six==1.12.0
snowballstemmer==1.2.1
ディレクトリ構成
scripts
├─ hist_matching.py
└─ feature_detection.py
comparing
├─ 01.png
├─ 02.png
├─ 03.png
├─ 04.png
├─ 05.png
└─ 06.png
検証1: ヒストグラム比較
説明
色の分布や位置等から比較する手法です。詳細は以下を参照ください。
画像サイズは一律200px × 200pxに変換して比較しています。
また、calcHist
の第2引数はB、G、Rの色相に対応する[0]、[1]、[2]のどれかの値を指定するのですが、今回はB、G、R全てで比較しそれらの平均を算出しています。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""hist matching.
Usage:
hist_matching.py
--target_file_path=<target_file_path>
--comparing_dir_path=<comparing_dir_path>
hist_matching.py -h | --help
Options:
-h --help show this screen and exit.
"""
import cv2
from docopt import docopt
import glob
import logging
import os
import sys
from statistics import mean
if __name__ == '__main__':
# logging config
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s %(levelname)s: %(message)s',
)
logging.info('%s start.' % (__file__))
# get parameters
args = docopt(__doc__)
target_file_path = args['--target_file_path']
comparing_dir_path = args['--comparing_dir_path']
# setting
img_size = (200, 200)
channels = (0, 1, 2)
mask = None
hist_size = 256
ranges = (0, 256)
ret = {}
# get comparing files
pattern = '%s/*.png'
comparing_files = glob.glob(pattern % (comparing_dir_path))
if len(comparing_files) == 0:
logging.error('no files.')
sys.exit(1)
# read target image
target_file_name = os.path.basename(target_file_path)
target_img = cv2.imread(target_file_path)
target_img = cv2.resize(target_img, img_size)
for comparing_file in comparing_files:
comparing_file_name = os.path.basename(comparing_file)
if comparing_file_name == target_file_name:
continue
tmp = []
for channel in channels:
# calc hist of target image
target_hist = cv2.calcHist([target_img], [channel], mask, [hist_size], ranges)
# read comparing image
comparing_img_path = os.path.join(
os.path.abspath(os.path.dirname(__file__)) + '/../',
comparing_file,
)
comparing_img = cv2.imread(comparing_img_path)
comparing_img = cv2.resize(comparing_img, img_size)
# calc hist of comparing image
comparing_hist = cv2.calcHist([comparing_img], [channel], mask, [hist_size], ranges)
# compare hist
tmp.append(cv2.compareHist(target_hist, comparing_hist, 0))
# mean hist
ret[comparing_file] = mean(tmp)
# sort
for k, v in sorted(ret.items(), reverse=True, key=lambda x: x[1]):
logging.info('%s: %f.' % (k, v))
logging.info('%s end.' % (__file__))
sys.exit(0)
実行方法
target_file_path
、comparing_dir_path
がコマンド引数となります。それぞれ<対象の画像パス>
、<比較対象の画像があるディレクトリパス>
を指定ください。
$ python scripts/hist_matching.py \
--target_file_path=./comparing/05.png \
--comparing_dir_path=./comparing/
2018-12-16 23:09:02,826 INFO: ./comparing/03.png: 0.510273.
2018-12-16 23:09:02,826 INFO: ./comparing/04.png: 0.500878.
2018-12-16 23:09:02,826 INFO: ./comparing/06.png: 0.310258.
2018-12-16 23:09:02,826 INFO: ./comparing/02.png: 0.259239.
2018-12-16 23:09:02,826 INFO: ./comparing/01.png: 0.005728.
類似度は-1〜1の範囲を取り、全く同じ画像の場合に1となります。
04.pngとの類似度が高くなって欲しかったのですが、03.png(ゴールデン・レトリバー)との類似度が1番高いという結果になってしまいました・・・。
検証2: 特徴点のマッチング
説明
2画像の特徴点を抽出し、それらの距離を比較します。以下を参考にさせていただきました。
- 3日で作る高速特定物体認識システム (4) 特徴点のマッチング - 人工知能に関する断創録
- [OpenCV] いまさら局所特徴量で物体検出!? - Qiita
- 特徴点のマッチング - OpenCV-Python Tutorials 1 documentation
抽出精度を高めるため、グレースケール変換を行っています。前項と同様に、画像サイズは一律200px × 200pxに変換して比較しています。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""feature detection.
Usage:
feature_detection.py
--target_file_path=<target_file_path>
--comparing_dir_path=<comparing_dir_path>
feature_detection.py -h | --help
Options:
-h --help show this screen and exit.
"""
import cv2
from docopt import docopt
import glob
import logging
import os
import sys
if __name__ == '__main__':
# logging config
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s %(levelname)s: %(message)s',
)
logging.info('%s start.' % (__file__))
# get parameters
args = docopt(__doc__)
target_file_path = args['--target_file_path']
comparing_dir_path = args['--comparing_dir_path']
# setting
img_size = (200, 200)
bf = cv2.BFMatcher(cv2.NORM_HAMMING)
# detector = cv2.ORB_create()
detector = cv2.AKAZE_create()
ret = {}
# get comparing files
pattern = '%s/*.png'
comparing_files = glob.glob(pattern % (comparing_dir_path))
if len(comparing_files) == 0:
logging.error('no files.')
sys.exit(1)
# read target image
target_file_name = os.path.basename(target_file_path)
target_img = cv2.imread(target_file_path, cv2.IMREAD_GRAYSCALE)
target_img = cv2.resize(target_img, img_size)
(target_kp, target_des) = detector.detectAndCompute(target_img, None)
for comparing_file in comparing_files:
comparing_file_name = os.path.basename(comparing_file)
if comparing_file_name == target_file_name:
continue
# read comparing image
comparing_img_path = os.path.join(
os.path.abspath(os.path.dirname(__file__)) + '/../',
comparing_file,
)
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)
# detect
matches = bf.match(target_des, comparing_des)
dist = [m.distance for m in matches]
ret[comparing_file] = sum(dist) / len(dist)
# sort
for k, v in sorted(ret.items(), reverse=False, key=lambda x: x[1]):
logging.info('%s: %f.' % (k, v))
logging.info('%s end.' % (__file__))
sys.exit(0)
実行方法
コマンド引数は前項と同様です。
$ python scripts/feature_detection.py \
--target_file_path=./comparing/05.png \
--comparing_dir_path=./comparing/
2018-12-16 23:45:35,315 INFO: ./comparing/04.png: 124.100000.
2018-12-16 23:45:35,316 INFO: ./comparing/02.png: 131.225000.
2018-12-16 23:45:35,316 INFO: ./comparing/03.png: 135.725000.
2018-12-16 23:45:35,316 INFO: ./comparing/01.png: 143.700000.
2018-12-16 23:45:35,316 INFO: ./comparing/06.png: 143.850000.
2018-12-16 23:49:55,066 INFO: ./comparing/04.png: 53.519713.
2018-12-16 23:49:55,066 INFO: ./comparing/02.png: 58.659498.
2018-12-16 23:49:55,066 INFO: ./comparing/03.png: 59.534050.
2018-12-16 23:49:55,066 INFO: ./comparing/06.png: 63.512545.
2018-12-16 23:49:55,066 INFO: ./comparing/01.png: 67.397849.
類似度は距離を算出しているので、全く同じ画像の場合の値は0となり、値が小さいほど類似度が高くなります。
実施したどちらの手法(AKAZE、ORB)でも、04.pngとの類似度が高くなっています。
まとめ
- Deep Learningを使わなくてもそれなりの精度は出せる
- 手法に応じてグレースケール変換、サイズ変換を行う
- 今回の場合、ヒストグラム比較では上手くいかなかったので手法の再検討が必要
- 特徴点抽出の場合、向きが異なる画像の比較は難しいので、同じ向きの画像を比較させる
- 例えば、必ず正面を向いている社員証の画像どうしは比較し易い気がする
- いぬかわいい