Pythonでテンプレートマッチング、OpenCVサンプルコードと解説

準備

Pythonでテンプレートマッチングする場合、画像処理ライブラリOpenCVが必要です。入れてない人は次の記事を参考にしてください。

https://www.tech-tech.xyz/python-opencv-pip-install/

OpenCVで物体認識する方法

OpenCVで静止画から物体認識をする場合、つぎの3つの方法があります。

  1. テンプレートマッチング法 今回は、テンプレートマッチング法で物体認識を行います。

  2. 特徴点の抽出し、比較することで検出
    https://www.tech-tech.xyz/sift-surf-akaze/

  3. カスケード型分類器を用いて検出
    https://www.tech-tech.xyz/haar-cascade/

これらは、お手軽順で並べてます。つまり、1.が最もお手軽な方法で、3.は判別機の学習のためにデータを集めて成形するのが面倒です。ただし、3.の方法を使った顔認識は優秀な判別機が公式から配布されているのでお手軽にできます。今回紹介するテンプレートマッチング法は最も単純な方法です。

テンプレートマッチングとは?

テンプレート画像と一致する部分を検出

テンプレートマッチングの説明図

画像からテンプレート画像と一致する部分を検出するのがテンプレートマッチングです。テンプレートマッチングは、テンプレート画像を被検出画像上でスライドさせ、テンプレート画像と被検出画像の領域を比較し、類似度の高い領域を検出します。

メリットとデメリット

他の画像認識アルゴリズムと比べるとテンプレートマッチングは次のようなメリットとデメリットがあります。

  • メリット:お手軽
  • デメリット:柔軟性がない(拡大や縮小程度の違いでも認識できなくなる。)

OpenCVで使用する関数

類似度を表すグレースケール画像を出力

result = cv2.matchTemplate(image, template, method)

OpenCvではcv2.matchTemplate関数でテンプレートマッチングを行います。入力画像、テンプレート画像、比較方法を入力します。出力のresultは各画素が入力画像とテンプレート画像の類似度を表すグレースケール画像になります。

minMaxLoc関数を使って、最も明るい領域と暗い領域を検出

min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(gray_image)

cv2.minMaxLoc関数は入力画像(グレースケール)から最も明るい画素と暗い画素の位置を出力します。出力のmin_val, max_valは画素の値、min_loc, max_locは画素の位置です。cv2.matchTemplateの結果を入力することで最もマッチする領域を検出するのに用います。

テンプレートマッチングでダイヤの4を探す

準備

トランプの画像からダイヤの4をテンプレートマッチングで検出します。テンプレート画像は被検出画像から切り取った画像です。

  • 被検出画像(“img.png”で保存)
    被検出画像

  • テンプレート画像(“temp.png”で保存)
    テンプレート画像

サンプルコード

#coding:utf-8
import cv2

#画像をグレースケールで読み込む
img = cv2.imread("img.png", 0)
temp = cv2.imread("temp.png", 0)

#マッチングテンプレートを実行
#比較方法はcv2.TM\_CCOEFF\_NORMEDを選択
result = cv2.matchTemplate(img, temp, cv2.TM\_CCOEFF\_NORMED)

#検出結果から検出領域の位置を取得
min\_val, max\_val, min\_loc, max\_loc = cv2.minMaxLoc(result)
top\_left = max\_loc
w, h = temp.shape\[:: -1\]
bottom\_right = (top\_left\[0\] + w, top_left\[1\] + h)

#検出領域を四角で囲んで保存
result = cv2.imread("img.png")
cv2.rectangle(result,top\_left, bottom\_right, (255, 0, 0), 2)
cv2.imwrite("result.png", result)

サンプルコードの解説

#画像をグレースケールで読み込む
img = cv2.imread("img.png", 0)
temp = cv2.imread("temp.png", 0)

入力画像とテンプレート画像はグレースケールで読み込んでます。色情報を使う場合は特定の色を抽出してグレースケールへ変換する場合が多いようです。

# テンプレートマッチングを実行
# 比較方法はcv2.TM\_CCOEFF\_NORMEDを選択
result = cv2.matchTemplate(img, temp, cv2.TM\_CCOEFF\_NORMED)

比較方法はcv2.TM_CCOEFF_NORMEDにしました。私の実感ではこの比較方法が最も精度が良いです。

#検出結果から検出領域の位置を取得
min\_val, max\_val, min\_loc, max\_loc = cv2.minMaxLoc(result)

cv2.minMaxLocは入力としてグレースケール画像をとります。そして、最も明るい位置(max_loc)とその色の値(max_val)、最も暗い位置(min_loc)とその色の値(min_val)を返します。resultにはcv2.matchTemplateの結果が入ってます。この結果画像(result)は類似度が高いほど明るくなるので最も明るいところ(max_loc)が類似度の高い領域の左上の位置を指しています。ただし、比較方法がcv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMEDの場合は類似度が高いほど黒くなるのでcv2.minMaxLocでテンプレートマッチングする場合はmin_locが類似度の高い領域になります。

結果

ダイヤの4が無事に囲まれました。

テンプレートマッチング法による物体検出の結果

複数の物体を検出する

次はテンプレートマッチングでダイヤマークをすべて検出したいと思います。ダイヤマークを用意します。(mark.pngで保存)

テンプレート画像

サンプルコード

#coding:utf-8
import cv2
import numpy as np

#画像をグレースケールで読み込む
img = cv2.imread("img.png", 0)
temp = cv2.imread("mark.png", 0)

#マッチングテンプレートを実行
result = cv2.matchTemplate(img, temp, cv2.TM\_CCOEFF\_NORMED)

#類似度の設定(0~1)
threshold = 0.9

#検出結果から検出領域の位置を取得
loc = np.where(result >= threshold)

#検出領域を四角で囲んで保存
result = cv2.imread("img.png")
w, h = temp.shape\[:: -1\]
for top_left in zip(*loc\[:: -1\]):
    bottom\_right = (top\_left\[0\] + w, top_left\[1\] + h)
    cv2.rectangle(result,top\_left, bottom\_right, (255, 0, 0), 2)
cv2.imwrite("result2.png", result)

サンプルコードの解説

#類似度の設定(0~1)
threshold = 0.9

#検出結果から検出領域の位置を取得
loc = np.where(result >= threshold)

複数の物体を検出する場合は閾値が必要になります。今回は類似度を0.9に設定します。

結果

ほとんどのダイヤマークが検出できました。検出に失敗した箇所は四角い領域内に余計な物体が入ったからだと考えられれます。ダイヤのQの上部を拡大すると次のようになっています。

fail_case

まとめ

テンプレートマッチングは単純な物体検出方法なので向きが変わったり、四角い領域に余計な物が入り込むと検出が失敗してしまうことがわかりました。もう少し高度なマッチング技術をためしたい場合、下記の記事が参考になると思います。

コメント

タイトルとURLをコピーしました