Warning: Undefined variable $position in /home/pystyles/pystyle.info/public_html/wp/wp-content/themes/lionblog/functions.php on line 4897

OpenCV – cv2.matchTemplate でテンプレートマッチングを行う方法

OpenCV – cv2.matchTemplate でテンプレートマッチングを行う方法

概要

OpenCV でテンプレートマッチングを行う方法について解説します。

Advertisement

テンプレートマッチング

検出対象の物体が映るテンプレート画像を用意します。

入力画像に対して、テンプレート画像と同じ大きさの検索窓を左上から右下にかけてスライドさせながら動かしていきます。 移動する検索窓の各位置において、「入力画像の検索窓の範囲」と「テンプレート画像」の類似度を計算します。 検索窓のすべての位置における類似度が最終的な出力結果になります。

cv2.matchTemplate

result = cv2.matchTemplate(image, templ, method[, result[, mask]])
引数
名前 デフォルト値
image ndarray
入力画像
templ ndarray
テンプレート画像
method TemplateMatchModes
類似度の計算方法
返り値
名前 説明
result 検索窓の各位置での類似度

サンプルコード

入力画像からテンプレート画像の❓ボックスを探します。

sample1.jpg

入力画像

template.jpg

テンプレート画像

In [1]:
import cv2
import numpy as np
from IPython.display import Image, display
from matplotlib import pyplot as plt


def imshow(img):
    """ndarray 配列をインラインで Notebook 上に表示する。
    """
    ret, encoded = cv2.imencode(".jpg", img)
    display(Image(encoded))


# 入力画像、テンプレート画像を読み込む。
img = cv2.imread("sample1.jpg")  # 入力画像
templ = cv2.imread("template.jpg")  # テンプレート画像

# テンプレートマッチングを行う。
result = cv2.matchTemplate(img, templ, cv2.TM_CCOEFF_NORMED)
Advertisement

result について

返り値 result は、検索窓の各位置でのテンプレート画像との類似度を表す2次元配列です。 入力画像の大きさを $(W, H)$、テンプレート画像の大きさを $(w, h)$、検索窓の左上の点を $(x, y)$ としたとき、 検索窓は $(x, y) \in (W − w + 1, H − h + 1)$ の範囲でスライドします。

result[y, x] は検索窓が位置 (x, y), (x + w, y + h) にいるときの類似度の値になります。

result の解釈

In [2]:
print("img.shape", img.shape)  # (380, 694, 3)
print("template.shape", templ.shape)  # (30, 31, 3)
print("result.shape", result.shape)  # (380 - 30 + 1, 694 - 31 + 1) = (351, 664)
img.shape (380, 694, 3)
template.shape (29, 33, 3)
result.shape (352, 662)

matplotlib で類似度のヒートマップを描画します。黄色のところは類似度が高いことを意味します。

In [3]:
fig, ax = plt.subplots(figsize=(10, 5))
im = ax.imshow(result, cmap="jet")
fig.colorbar(im)

plt.show()

類似度が最も高い場所を探す

テンプレート画像が最もマッチする位置を探すには以下のようにします。

In [4]:
# 最も類似度が高い位置を取得する。
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(result)
print(f"max value: {maxVal}, position: {maxLoc}")
# max value: 0.9999998211860657, position: (392, 124)

# 描画する。
tl = maxLoc[0], maxLoc[1]
br = maxLoc[0] + templ.shape[1], maxLoc[1] + templ.shape[0]

dst = img.copy()
cv2.rectangle(dst, tl, br, color=(0, 255, 0), thickness=2)
imshow(dst)
max value: 0.9971528649330139, position: (390, 124)

類似度が閾値以上の場所を探す

類似度が最も高い場所の場合、2つある❓ブロックのうち1つしか検出されませんでした。 類似度が一定以上の場所はすべて検出するように変更するには、以下のようにします。

In [5]:
# 最も類似度が 0.9 以上の位置を取得する。
ys, xs = np.where(result >= 0.9)

# 描画する。
dst = img.copy()
for x, y in zip(xs, ys):
    cv2.rectangle(
        dst,
        (x, y),
        (x + templ.shape[1], y + templ.shape[0]),
        color=(0, 255, 0),
        thickness=2,
    )
imshow(dst)
Advertisement

類似度の計算方法

method 引数で指定できる類似度の計算方法には以下があります。とくに拘りがなければ、cv2.CCOEFF_NORMED にしておくとよいでしょう。


入力画像を $I$、テンプレート画像を $T$、出力結果を $\text{result}$ とします。 入力画像の検索窓の領域及びテンプレート画像の画素を1次元配列としたものを $\bm{u}, \bm{v}$ としたとき、類似度の計算はこの2つのベクトルの類似度を計算することに他なりません。

cv2.TM_SQDIFF

類似度を二乗差 (sum of square difference, SQDIFF) で計算します。

$$ \begin{aligned} \text{result}(x, y) &= \sum_{i, j} {(T(i, j) – I(x + i, y + j))^2} \\ &= \|\bm{u} – \bm{v}\|^2 \end{aligned} $$

cv2.TM_SQDIFF_NORMED

類似度を正規化二乗差 (normalized square difference) で計算します。

$$ \begin{aligned} \text{result}(x, y) &= \frac {\sum_{i, j} (T(i, j) – I(x + i, y + j))^2} {\sqrt{\sum_{i, j} T(i, j)^2} \sqrt{\sum_{i, j} I(x + i, y + j)^2}} \\ &= \frac{\|\bm{u} – \bm{v}\|^2}{\|\bm{u}\| \|\bm{v}\|} \end{aligned} $$
Advertisement

cv2.TM_CCORR

類似度を相互相関 (cross correlation, CCORR) で計算します。

$$ \begin{aligned} \text{result}(x, y) &= \sum_{i, j} T(i, j) I(x + i, y + j) \\ &= \bm{u} \cdot \bm{v} \end{aligned} $$

cv2.TM_CCORR_NORMED

類似度を正規化相互相関 (normalized cross correlation) で計算します。 これはベクトル $\bm{u}, \bm{v}$ のなす角を $\theta$ としたときの $\cos \theta$ であり、コサイン類似度と呼ばれます。 出力値は $[-1, 1]$ の範囲になります。

$$ \begin{aligned} \text{result}(x, y) &= \frac{\sum_{i, j} T(i, j) I(x + i,y + j)} {\sqrt{\sum_{i, j} T(i, j)^2} \sqrt{\sum_{i, j} I(x + i, y + j)^2}}] \\ &= \frac{\bm{u} \cdot \bm{v}}{\|\bm{u}\| \|\bm{v}\|} \end{aligned} $$

cv2.TM_CCOEFF

類似度を相関係数 (correlation coefficient, COOEFF) で計算します。

$$ \begin{aligned} \text{result}(x, y) &= \sum_{i, j} (T'(i, j) \cdot I'(x + i, y + j)) \\ &= (\bm{u} – \bar{\bm{u}}) \cdot (\bm{v} – \bar{\bm{v}}) \end{aligned} $$

ただし、

$$ \begin{aligned} T'(i, j) &= T(i, j) – \frac{1}{wh} \sum_{i’, j’} T(i’, j’) \\ I'(x + i, y + j) &= I(x + i, y + j) – \frac{1}{wh} \sum_{i’, j’} I(x + i’, y + j’) \end{aligned} $$
Advertisement

cv2.TM_CCOEFF_NORMED

類似度を正規化相関係数 (normalized correlation coefficient) で計算します。 出力値は $[-1, 1]$ の範囲になります。

$$ \begin{aligned} result(x, y) &= \frac {\sum_{i, j} (T'(i, j) \cdot I'(x + i, y + j))} {\sqrt{\sum_{i, j} T'(i, j)^2 \cdot \sum_{i, j} I'(x + i, y + j)^2}} \\ &= \frac{(\bm{u} – \bar{\bm{u}}) \cdot (\bm{v} – \bar{\bm{v}})} {\|\bm{u} – \bar{\bm{u}}\| \|\bm{v} – \bar{\bm{v}}\|} \end{aligned} $$

各 method のよる結果の違い

In [6]:
params = [
    "TM_SQDIFF",
    "TM_SQDIFF_NORMED",
    "TM_CCORR",
    "TM_CCORR_NORMED",
    "TM_CCOEFF",
    "TM_CCOEFF_NORMED",
]

fig = plt.figure(figsize=(10, 10))

for i, param in enumerate(params, 1):
    result = cv2.matchTemplate(img, templ, getattr(cv2, param))
    
    ax = fig.add_subplot(3, 2, i)
    ax.set_title(param)
    ax.imshow(result, cmap="jet")

plt.show()