Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

皆さんこんにちは。@best_not_bestです。
現在のお仕事は、Google Analytics、Adobe Analytics、Google Cloud Platformを使って、サイトの分析のお手伝いをしています。

Deep Learningでの画像解析は精度は良いですが、学習データを収集することがネックとなります。今回は、OpenCVで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 (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

以下のライブラリをインストールします。

requirements.txt
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全てで比較しそれらの平均を算出しています。

hist_matching.py
#!/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_pathcomparing_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画像の特徴点を抽出し、それらの距離を比較します。以下を参考にさせていただきました。

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

feature_detection.py
#!/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/
実行結果(AKAZE)
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.
実行結果(ORB)
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を使わなくてもそれなりの精度は出せる
  • 手法に応じてグレースケール変換、サイズ変換を行う
  • 今回の場合、ヒストグラム比較では上手くいかなかったので手法の再検討が必要
  • 特徴点抽出の場合、向きが異なる画像の比較は難しいので、同じ向きの画像を比較させる
    • 例えば、必ず正面を向いている社員証の画像どうしは比較し易い気がする
  • いぬかわいい
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした