画像の中から対象物を識別する技術として用いられるのが「画像認識」です。例えば、写真を撮る際に目や鼻の位置関係や特徴から顔の位置を認識し、そこにピントが合うように自動ピント調整を行う機能などで利用されています。今回は、OpenCVで利用可能なマッチング手法での画像認識について紹介させていただきます。

マッチング手法の種類

マッチングの手法には、大まかに「領域ベースマッチング」と「特徴ベースマッチング」があります。最近では「機械学習」や「ディープラーニング」といった手法を用いた画像認識も注目を浴びていますが、こちらについては別の機会に説明いたします。

1)領域ベースマッチング

一般にテンプレートマッチングと呼ばれる手法で、対象画像とテンプレート画像をピクセル単位で順次比較して行き、差分が少ない領域を探していく方法です。差分や類似性などの相関を計算するアルゴリズムが複数用意されているので、比較対象の画像によって使い分けを行います。

領域ベースマッチングのイメージ

対象画像
01_base.png
テンプレート画像
01_temp.png
テンプレートマッチングの走査イメージ
01_image.png
評価結果
01_result.png

1)180°回転:類似度=0.475
2)85%縮小:類似度=0.795
3)同一画像:類似度=1.0(完全一致)
4)一部をカット:類似度=0.946
5)縦方向60%縮小:類似度=0.436
6)横方向60%縮小:類似度=0.456

使用した関数

cv2.matchTemplate(img_src, temp_src, cv2.TM_CCOEFF_NORMED)
※img_srcは、対象画像(グレースケール)の入力配列
※temp_srcは、テンプレート画像(グレースケール)の入力配列

テンプレートマッチングは非常に精細に一致度合いを認識できる一方、対象とテンプレートの画像とにサイズの違いがあったり、微妙でも角度に違いがあった場合には類似度が大きく下がってしまうというデメリットがあります(変形に弱い)。また、差分比較の方法がピクセル単位であること、対象画像の全領域を随時走査しながら比較していくため、大きな画像ほど処理時間がかかるという点考慮しなくてはなりません。このような欠点を補うためには、前処理で対象画像から不要なノイズを除去したり、サイズや角度の補正などを行うことで精度を向上させる工夫が必要となります。また、明らかに対象外の領域であることがはっきりしている場合、その領域を走査対象から除外することで処理時間の短縮が可能です。

2)特徴ベースマッチング

特徴量マッチングなどと呼ばれるのがこの手法です。まず比較する2枚の画像のそれぞれに対して「特徴点の抽出」を行い、その特徴点から特徴量を計算し、類似性を比較する方法です。「特徴点の抽出」方法においても、また特徴量の計算方法においても複数のアルゴリズムが存在しています。

テンプレートマッチングと異なり、「特徴点の抽出」には計算が掛かるものの、比較作業は高速に行えるため、複数の画像同士のマッチングなどに適しています。また、画像の変形や回転の影響も受けづらいのが特長です。

特徴ベースマッチングのイメージ

まず、異なる6種類の図形の中に同一の図形を1つ配置し、特徴ベースマッチングにより実際に抽出される様子をご覧ください。

対象画像
02_base01.png
テンプレート画像
02_temp.png

注)特徴点を検出させたいオブジェクトが画像の際(きわ)にあると上手く特徴量が計算できないようです。マッチングの結果が表示されない場合や、それによりプログラムの実行中にエラーが出るような場合は、画像の周囲にある程度の余白を付け足してからお試しください。

1)同一図形とのマッチング結果
02_result01.png

上図では、類似した特徴量を持つキーポイント(特徴点)同士をラインで結ぶことでマッチングの結果を表示しています。ほとんどのラインが目的の図形に結びついている事から、正しく認識されたことがわかります。

次に、領域ベースマッチングでは、変形・回転・ノイズなどが抽出結果にどのように影響するのか検証した結果を紹介いたします。

2)180°回転した図形とのマッチング結果
02_result02.png
3)85%縮小した図形とのマッチング結果
02_result03.png
4)一部をカットした図形とのマッチング結果
02_result04.png
5)縦方向60%縮小した図形とのマッチング結果
02_result05.png
6)横方向60%縮小した図形とのマッチング結果
02_result06.png
7)ノイズを追加した図形とのマッチング結果
02_result07.png
8)輪郭をぼかした図形とのマッチング結果
02_result08.png

これらの結果から、特徴ベースマッチングが対象画像への「回転」や「変形」といった影響を受けづらいマッチング手法であることが分かるのではないでしょうか。

注)今回は互いに似たような特徴点を持つ単純な図形同士の組み合わせで検証したため、ある程度の誤検出が発生しているものと思われます。

使用した関数

【特徴点の検出】
detector = cv2.AKAZE_create()
kp1, des1 = detector.detectAndCompute(gray1_src, None)
kp2, des2 = detector.detectAndCompute(gray2_src, None)
※gray1_srcは、テンプレート画像(グレースケール)の入力配列
※gray2_srcは、検出対象画像(グレースケール)の入力配列

【マッチング処理】
bf = cv2.BFMatcher(cv2.NORM_HAMMING)
matches = bf.match(des1, des2)
matches = sorted(matches, key = lambda x:x.distance)
mutch = cv2.drawMatches(color1_src, kp1, color2_src, kp2, matches[:30], None, flags=2)
※color1_srcは、対象画像(カラー)の入力配列
※color2_srcは、テンプレート画像(カラー)の入力配列

サンプルコード

必要に応じ、入力画像のファイル名や使用する関数を差し替えてください。

サンプルプログラムの動作確認環境

  1. OS: ubuntu16.04LTS(64bit)
  2. Python: 3.4.1

領域ベースマッチング

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import numpy as np
import random
import sys

if __name__ == '__main__':

    # 対象画像を指定
    base_image_path = '<file path>/<file name>'
    temp_image_path = '<file path>/<file name>'

    # 画像をグレースケールで読み込み
    gray_base_src = cv2.imread(base_image_path, 0)
    gray_temp_src= cv2.imread(temp_image_path, 0)

    # ラベリング処理
    label = cv2.connectedComponentsWithStats(gray_base_src)
    n = label[0] - 1
    data = np.delete(label[2], 0, 0)

    # マッチング結果書き出し準備
    color_src = cv2.cvtColor(gray_base_src, cv2.COLOR_GRAY2BGR)
    height, width = gray_temp_src.shape[:2]

    # ラベリング情報を利用して各オブジェクトごとのマッチング結果を画面に表示
    for i in range(n):
 
        # 各オブジェクトの外接矩形を赤枠で表示
        x0 = data[i][0]
        y0 = data[i][1]
        x1 = data[i][0] + data[i][2]
        y1 = data[i][1] + data[i][3]
        cv2.rectangle(color_src, (x0, y0), (x1, y1), (0, 0, 255))

        # 各オブジェクトごとの類似度を求める
        x2 = x0 - 5
        y2 = y0 - 5
        x3 = x0 + width + 5
        y3 = y0 + height + 5

        crop_src = gray_base_src[y2:y3, x2:x3]
        c_height, c_width = crop_src.shape[:2]

        res = cv2.matchTemplate(crop_src, gray_temp_src, cv2.TM_CCOEFF_NORMED)
        res_num = cv2.minMaxLoc(res)[1]
        cv2.putText(color_src, str(i + 1) + ") " +str(round(res_num, 3)), (x0, y1 + 15), cv2.FONT_HERSHEY_PLAIN, 1, (0, 255, 255))

    # 結果の表示
    cv2.imshow("color_src", color_src)

    cv2.waitKey(0)
    cv2.destroyAllWindows()

特徴ベースマッチング

<
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import numpy as np
import random
import sys

if __name__ == '__main__':

    # 対象画像を指定
    base_image_path = '<file path>/<file name>'
    temp_image_path = '<file path>/<file name>'

    # 画像をグレースケールで読み込み
    gray_base_src = cv2.imread(base_image_path, 0)
    gray_temp_src = cv2.imread(temp_image_path, 0)

    # マッチング結果書き出し準備
    # 画像をBGRカラーで読み込み
    color_base_src = cv2.imread(base_image_path, 1)
    color_temp_src = cv2.imread(temp_image_path, 1)

    # 特徴点の検出
    type = cv2.AKAZE_create()
    kp_01, des_01 = type.detectAndCompute(gray_base_src, None)
    kp_02, des_02 = type.detectAndCompute(gray_temp_src, None)

    # マッチング処理
    bf = cv2.BFMatcher(cv2.NORM_HAMMING)
    matches = bf.match(des_01, des_02)
    matches = sorted(matches, key = lambda x:x.distance)
    mutch_image_src = cv2.drawMatches(color_base_src, kp_01, color_temp_src, kp_02, matches[:10], None, flags=2)

    # 結果の表示
    cv2.imshow("mutch_image_src", mutch_image_src)
    cv2.imshow("02_result08", mutch_image_src)

    cv2.waitKey(0)
    cv2.destroyAllWindows()