【OpenCV-Python】matchTemplate(テンプレートマッチング)

OpenCVでテンプレートマッチングを行うにはmatchTemplate()関数を用います。

ただし、OpenCVのテンプレートマッチングは、回転やスケール変動に対応していないため、OpenCVの関数の中では使いずらい関数の一つでもあります。

 

テンプレートマッチングの構文

matchTemplate(image, templ, method[, result[, mask]]) ->result

引数

image テンプレートマッチングを行う、検索対象の画像
templ テンプレート画像
method 相関値(類似度)の計算方法
cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED, cv2.TM_CCORR, cv2.TM_CCORR_NORMED,
cv2.TM_CCOEFF, cv2.TM_CCOEFF_NORMED
(参考)TemplateMatchModes
mask (オプション)
テンプレート画像のうち、有効な領域をマスク画像で指定します。

methodの設定値のうち、TM_SQDIFFとTM_SQDIFF_NORMEDの値に関しては、検索対象の画像と、テンプレート画像の差分の2乗を用いた値なので、resultで得られた値が小さいほど、似ている場所となります。
それ以外の設定値は、resultで得られた値が大きいほど似ている場所となります。

戻り値

result テンプレートマッチングの結果を32bitのfloatのデータとして格納します。
検索対象の画像と、テンプレートの画像を重ね合わせた時に、テンプレート画像の一番、左上の座標の位置に相関値が格納されます。

 

テンプレートマッチングのサンプルプログラム

import cv2

# 画像を開く
img = cv2.imread("image.jpg", cv2.IMREAD_UNCHANGED)
# テンプレート画像を開く
template = cv2.imread("template.jpg", cv2.IMREAD_UNCHANGED)

# テンプレートマッチング
res = cv2.matchTemplate(img,template,cv2.TM_CCOEFF)
# 結果が最大、最小の位置を検出
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

# 検出位置を描画
cv2.rectangle(
    img,
    max_loc, 
    (max_loc[0] + template.shape[1], max_loc[1] + template.shape[0]), 
    (0, 0, 255), 
    3)

# 結果の表示
cv2.imshow("Template", template)
cv2.imshow("Result", img)

cv2.waitKey()

検索対象画像

テンプレート画像

テンプレートマッチングの結果

 

テンプレートマッチングの結果の画像について

テンプレートマッチング結果の画像は32bitのfloatの値が格納された画像となっています。

テンプレートマッチングの結果の画像を表示するサンプルは以下のようになります。

import cv2

# 画像を開く
img = cv2.imread("image.jpg", cv2.IMREAD_UNCHANGED)
# テンプレート画像を開く
template = cv2.imread("template.jpg", cv2.IMREAD_UNCHANGED)

# テンプレートマッチング
res = cv2.matchTemplate(img,template,cv2.TM_CCOEFF)
# 結果が最大、最小の位置を検出
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

# 検出位置を描画
cv2.rectangle(
    img,
    max_loc, 
    (max_loc[0] + template.shape[1], max_loc[1] + template.shape[0]), 
    (0, 0, 255), 
    3)

# 結果の表示
cv2.imshow("Template", template)
cv2.imshow("Result", img)
cv2.imshow("Template Result", res / max_val)

cv2.waitKey()

テンプレートマッチングの結果の画像

画像は、結果の画像の最大値で割って、0~1の範囲になるように調整しています。

今回のサンプルでは、相関値の計算にTM_CCOEFFを用いているので、値が大きい場所(画像が明るい場所)が似ている場所となります。

この位置は、テンプレート画像の左上の座標の位置となるので、実際のテンプレートの領域は、この明るい点を左上の座標にした、テンプレート画像の幅と高さの領域が検索結果の領域となります。

 

複数の位置のテンプレートマッチングの結果を取得する方法

これまで示したサンプルではテンプレートマッチングの結果の画像(result)の中から値が最大となる位置を探して、テンプレートマッチングの結果としていました。

しかしながら、この方法だと、値が最大となる1箇所しか結果を得られないため、複数の結果を得たい場合は、得られた結果の画像(result)の最大値となる周辺を値0(ゼロ)で塗りつぶしてから、残りの領域に関して値が最大となる場所を検索し、これを繰り返し行い、相関値がある程度、小さくなったら(大きくなったら)、検索を終了します。

 

複数の検索結果を取得するサンプルプログラム

import cv2

# 画像を開く
img = cv2.imread("image.jpg", cv2.IMREAD_UNCHANGED)
# テンプレート画像を開く
template = cv2.imread("template.jpg", cv2.IMREAD_UNCHANGED)

# テンプレートマッチング
res = cv2.matchTemplate(img,template,cv2.TM_CCOEFF_NORMED)

while True:

    # 結果が最大、最小の位置を検出
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
    print("MaxValue = ", max_val)

    if max_val < 0.3:
        break

    # 検出位置を描画
    cv2.rectangle(
        img,
        max_loc, 
        (max_loc[0] + template.shape[1], max_loc[1] + template.shape[0]), 
        (0, 0, 255), 
        3)
    
    # 検出した位置の近辺の値を0にする
    range = 10
    cv2.rectangle(
        res,
        (max_loc[0] - range, max_loc[1] - range), 
        (max_loc[0] + range, max_loc[1] + range), 
        0, 
        -1)
    
# 結果の表示
cv2.imshow("Template", template)
cv2.imshow("Result", img)
cv2.imshow("Template Result", res / max_val)

cv2.waitKey()

検出結果

0で塗りつぶされた結果の画像(result)

 

USBカメラを使ったテンプレートマッチングのサンプルプログラム

サンプルプログラム

import cv2

def TemplateMatching(img, template, threshold, range = 10):
    # テンプレートマッチング
    res = cv2.matchTemplate(img,template,cv2.TM_CCOEFF_NORMED)
    # 結果表示用の画像
    result_roi = img.copy()

    while True:

        # 結果が最大、最小の位置を検出
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        print("MaxValue = ", max_val)

        if max_val < threshold:
            break

        # 検出位置を描画
        cv2.rectangle(
            result_roi,
            max_loc, 
            (max_loc[0] + template.shape[1], max_loc[1] + template.shape[0]), 
            (0, 0, 255), 
            3)
        
        # 検出した位置の近辺の値を0にする
        cv2.rectangle(
            res,
            (max_loc[0] - range, max_loc[1] - range), 
            (max_loc[0] + range, max_loc[1] + range), 
            0, 
            -1)
    return result_roi
        
 # カメラを開く
cap = cv2.VideoCapture(0)

# 初期のテンプレート画像の切り出し
ret, frame = cap.read()
top, bottom, left, right = (190, 290, 270, 370)
template = frame[top : bottom, left : right]
cv2.imshow("Template", template)

while True:
    # 画像をキャプチャする
    ret, frame = cap.read()

    image_roi = frame.copy()
    cv2.rectangle(
            image_roi,
            (left, top), 
            (right, bottom), 
            (0, 255, 0), 
            3) 

    result_img = TemplateMatching(frame, template, 0.8)

    # 画像を表示する
    cv2.imshow("Image", image_roi)
    cv2.imshow("Result", result_img)
    
    key = cv2.waitKey(1)
    if key == ord('t'):
        # `t`キーを押すとテンプレート画像を登録する
        template = frame[top : bottom, left : right]
        cv2.imshow("Template", template)
    elif key > 0:
        break

カメラでの撮影画像

USBカメラで撮影中にキーボードの ‘t’ キーを入力すると、中央に表示した緑色の枠の範囲がテンプレート画像となるようにしています。

登録されたテンプレート画像

USBカメラで撮影中に検出されたテンプレート領域

 

まとめ

OpenCVのテンプレートマッチング(matchTemplate)は、回転やスケール変動に対応していないため、実際のカメラで撮影した画像に対して、テンプレートマッチングで何かを位置を検出しようとするのは、困難な場合が多いです。

ぜひ、ここにあるUSBカメラを使ったサンプルを用いて、カメラを遠くしたり、近づけたりして、画像を拡大/縮小し、カメラも回転しながら、いろいろと試してみてください。

さらに、相関値の計算も必ずしも正規化相互相関(TM_CCOEFF_NORMED)が一番、安定して検出するとは限りません。

 

他のマシンビジョン用(工業用の画像処理用)のライブラリでは、テンプレートマッチングは、普通に回転やスケール変動には対応していますし、さらに、被写体の輪郭をベースに、被写体の位置を検出する幾何学サーチなる検出方法も対応しているのがほとんどです。

さらにすごい物になると、ポテトチップスの袋のように、表面がデコボコしていて、画像が不規則に歪んでしまうような画像に対しても、サーチできてしまう物もあります。

 

そのため、OpenCVは一般的な画像処理を行うには、よく出来ているライブライだと思いますが、仕事でテンプレートマッチングを使う必要がある場合、OpenCVのテンプレートマッチングの処理(matchTemplate)を使って検出しようとするのには、十分に注意をしてください。

実際には、使い物にならない場合が多いです。

 

関連記事

テンプレートマッチング(template matching)

【OpenCV-Python】Webカメラから画像をキャプチャして保存する

https://docs.opencv.org/4.8.0/df/dfb/group__imgproc__object.html#ga586ebfb0a7fb604b35a23d85391329be

https://docs.opencv.org/4.8.0/df/dfb/group__imgproc__object.html#ga3a7850640f1fe1f58fe91a2d7583695d

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください