OpenCVのTemplate Matchingサンプルを詳しく調べる

2020年1月28日火曜日

Python

t f B! P L
OpenCVを使ったテンプレートマッチングをPythonで試す時、まず参考にするのがTemplate Matching in OpenCVまたはTemplate Matching with Multiple Objectsというサンプルコードだと思います。これらのサンプルコードのおおまかな内容は理解が簡単なのですが、テンプレートマッチングの関数cv.matchTemplate()の戻り値をどのように扱えばよいのかはっきりしなかったので、詳しく確かめてみました。

解析したサンプルコード

Template Matching with Multiple Objectsというサンプルコードについて、cv.matchTemplate()の戻り値の構造を確かめました。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img_rgb = cv.imread('target.png')
img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
template = cv.imread('template.png',0)
w, h = template.shape[::-1]

res = cv.matchTemplate(img_gray,template,cv.TM_CCOEFF_NORMED)

threshold = 0.8
loc = np.where( res >= threshold)
for pt in zip(*loc[::-1]):
    cv.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
cv.imwrite('res.png',img_rgb)
オリジナルのサンプルコードでは、テンプレートマッチング対象画像としてmario.pngが使われていますが、target.pngに差し替えています。また、オリジナルのサンプルコードでは、テンプレート画像としてmario_coin.pngが使われていますが、template.pngに差し替えています。
使用した画像

サンプルの解析

なぜcv.TM_CCOEFF_NORMEDなのか

このサンプルは、ある画像中の複数のマッチング部分を検出します。_NORMEDのモードを指定すると、マッチング結果が-1.0から1.0の間に正規化されるようです。_NORMED以外のモードを指定すると、マッチング結果から最大値、最小値を求めることはできますが、「ある値以上はマッチしたと考える」といった処理を行うときに、その境界値を決めることができません。このため、画像中のマッチング部分を特定することができません。マッチング結果が正規化されていると、「0.8以上はマッチングしたとみなす」といった境界値を設定することができるようになります。

threshold = 0.8の意味

マッチングの境界値を決めています。cv.TM_CCOEFF_NORMEDは、マッチング率が高いほど値が大きくなります。この値は、状況に応じて調整が必要になります。正規化されているので、最大値は1.0です。

cv.matchTemplate(img_gray,template,cv.TM_CCOEFF_NORMED)の戻り値の構造

cv.matchTemplate(img_gray,template,cv.TM_CCOEFF_NORMED)の戻り値resは、2次元の配列になります。配列の大きさは、マッチング対象画像の大きさが(W,H)、テンプレート画像の大きさが(w,h)の時、(W-w+1)x(H-h+1)です。この例で使用した画像の場合、(W,H)=(640,480)、(w,h)=(89,305)なので、戻り値resの大きさは、(640-89+1,480-305+1)=(552,176)です。print(res)を実行すると、次の様に表示されます。具体的には、len(res)=176であり、len(res[0])=552です。1次元側がw(x方向)、2次元側がh(y方向)に対応します。

res[[ 0.6472297   0.64573884  0.6484621  ...  0.3446521   0.34587955
   0.34714523]
 [ 0.64773846  0.64624274  0.6487364  ...  0.3454256   0.3465633
   0.34753427]
 [ 0.64942294  0.6476835   0.64918137 ...  0.3461068   0.3472825
   0.34812936]
 ...
 [ 0.3363744   0.3467935   0.35590363 ... -0.04257199 -0.05481663
  -0.06681015]
 [ 0.32571808  0.33642587  0.34692764 ... -0.05835321 -0.06999504
  -0.08208057]
 [ 0.31470156  0.32549503  0.33650377 ... -0.07385409 -0.08541072
  -0.09782302]]
この結果を見るとわかるように、値は-1.0から1.0の間に正規化されています。

loc = np.where( res >= threshold)の意味

この部分は、resの中で0.8以上の要素を探し、その配列のインデックスがlocとして返されます。locの構造は、2次元配列になります。locがなぜ2次元配列になるかというと、resが2次元配列だからです。locの2次元配列は、resを行列としてみた場合の列と行(またはx,y座標)になります。print(loc)を実行すると、次の様に表示されます。

loc(array([122, 123, 127]), array([94, 94, 95]))
上の結果の場合、loc[0][0]とloc[1][0]がペアで行列の0.8以上の要素の行列インデックス(x,y座標)を表します(2次元配列の[0]がy座標、[1]がx座標)。同様に、loc[0][1]とloc[1][1]とloc[0][2]とloc[1][2]のペアが行列の0.8以上の要素の行列インデックス(y,x座標)を表します。

for pt in zip(*loc[::-1]):

この部分は、locの結果から、検出部分のボックスを描画するループです。zip(*loc[::-1])は、まずloc[::-1]でloc[0]=y座標、loc[1]=x座標であったのを、loc[0]=x座標、loc[1]=y座標と逆にしています。次にzip(*loc)でlocをpt(y,x)配列としてループさせています。

cv.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)

ここで、ptには長方形の1点の(x,y)、 (pt[0] + w, pt[1] + h)には反対側の頂点の(x,y)が入ります。pt[0]がx座標、pt[1]がy座標です。ptには、0.8以上でマッチした領域の左上の座標が入っているので、その値のx(pt[0])にテンプレート画像のwを足し、y(pt[1])にテンプレート画像のhを足すと、テンプレートマッチング対象画像上にマッチした領域を重ねて描画できます。

このブログを検索

ブログアーカイブ

QooQ