Python と OpenCV を使ってテンプレートマッチングの処理を行ってみます。
以下で表記の「図1: 探索対象の画像」内から「図2: 探索画像」の場所をテンプレートマッチングという画像認識アルゴリズムを使って探索します。
図1: 探索対象の画像

図2: 探索画像

| 言語 : | Python, | 3.10.7 |
| OS : | Windows11 home, | 23H2 |
見つけた場所を赤枠で表記しています。
図: 実行結果例

[プログラムソース "template_matching.py"]
import cv2 as cv
import numpy as np
import copy
from matplotlib import pyplot as plt
def template_matching(img, template, method, search_area_top_left = None, search_area_bottom_right = None):
"""
テンプレートマッチング処理
Args:
img (array): 評価対象画像
template (array): テンプレート画像
method (int): 評価方式
(cv.TM_CCOEFF, cv.TM_CCOEFF_NORMED, cv.TM_CCORR, cv.TM_CCORR_NORMED, cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED)
search_area_top_left:
探索範囲 左上座標 (ex: (0, 0))
無指定時は (0, 0)
search_area_bottom_right:
探索範囲 右下座標 (ex: (1920, 1080))
無指定時は img 全範囲
Returns:
top_left: 最も一致した左上座標
rotation_result: 最も一致した回転角度 [degree]
res: matchTemplate 処理結果
"""
val = None
res_result = None
rotation_result = None
rotation_range = 10.0
rotation_step = 0.5
img_h, img_w = img.shape[:2]
template_h, template_w = template.shape[:2]
if search_area_top_left is None:
search_area_top_left = (0, 0)
if search_area_bottom_right is None:
search_area_bottom_right = (img_w - template_w + 1, img_h - template_h + 1)
assert search_area_top_left[0] >= 0, "Invalid argument"
assert search_area_top_left[1] >= 0, "Invalid argument"
assert search_area_top_left[0] <= img_w - template_w + 1, "Invalid argument"
assert search_area_top_left[1] <= img_h - template_h + 1, "Invalid argument"
assert search_area_bottom_right[0] >= 0, "Invalid argument"
assert search_area_bottom_right[1] >= 0, "Invalid argument"
assert search_area_bottom_right[0] <= img_w - template_w + 1, "Invalid argument"
assert search_area_bottom_right[1] <= img_h - template_h + 1, "Invalid argument"
assert search_area_top_left[0] < search_area_bottom_right[0], "Invalid argument"
assert search_area_top_left[1] < search_area_bottom_right[1], "Invalid argument"
# 探索範囲を制限するマスク画像を作成
template_mask = np.full((img_h - template_h + 1, img_w - template_w + 1), 0, dtype=np.uint8)
cv.rectangle(template_mask, search_area_top_left, search_area_bottom_right, 255, -1)
ret, template_mask = cv.threshold(template_mask, 128, 255, cv.THRESH_BINARY)
for rotation_degree in np.arange(-rotation_range, rotation_range + rotation_step, rotation_step).tolist():
# テンプレート画像を少しずつ回転してテンプレートマッチング処理を行います。
# 最もスコアの良いものを結果として採用します。
affine = cv.getRotationMatrix2D((template_w/2.0, template_h/2.0), rotation_degree, 1.0)
#template_rotated = cv.warpAffine(template, affine, (w, h), flags=cv.INTER_LINEAR)
#template_rotated = cv.warpAffine(template, affine, (w, h), flags=cv.INTER_CUBIC)
template_rotated = cv.warpAffine(template, affine, (template_w, template_h), flags=cv.INTER_LANCZOS4)
# テンプレートマッチング処理を行う
res = cv.matchTemplate(img, template_rotated, method)
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res, template_mask)
# If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
if method in [cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED]:
if val != None and min_val < val:
rotation_result = rotation_degree
val = min_val
top_left = min_loc
res_result = copy.deepcopy(res)
elif val is None:
rotation_result = rotation_degree
val = min_val
top_left = min_loc
res_result = copy.deepcopy(res)
else:
if val != None and max_val > val:
rotation_result = rotation_degree
val = max_val
top_left = max_loc
res_result = copy.deepcopy(res)
elif val is None:
rotation_result = rotation_degree
val = max_val
top_left = max_loc
res_result = copy.deepcopy(res)
return top_left, rotation_result, res_result
# 処理対象画像 を読み込み
img_org = cv.imread('ISO_12233-reschart.bmp', cv.IMREAD_GRAYSCALE)
assert img_org is not None, "file could not be read, check with os.path.exists()"
# テンプレート画像 を読み込み
#template = cv.imread('template_img_1.bmp', cv.IMREAD_GRAYSCALE)
template = cv.imread('template_img_1_rot10.bmp', cv.IMREAD_GRAYSCALE)
#template = cv.imread('template_img_2.bmp', cv.IMREAD_GRAYSCALE)
assert template is not None, "file could not be read, check with os.path.exists()"
w, h = template.shape[::-1]
# All the 6 methods for comparison in a list
methods = ['cv.TM_CCOEFF', 'cv.TM_CCOEFF_NORMED', 'cv.TM_CCORR_NORMED', 'cv.TM_SQDIFF', 'cv.TM_SQDIFF_NORMED']
for meth in methods:
img = img_org.copy()
method = eval(meth)
# テンプレートマッチング処理による画像探索
top_left, rotation, res = template_matching(img, template, method) # 全範囲探索
#top_left, rotation, res = template_matching(img, template, method, (160, 190), (200, 240)) # 探索領域を指定
# 画像探索結果を表示
print(meth, " : ", top_left, ", ", rotation)
bottom_right = (top_left[0] + w, top_left[1] + h)
img_color = cv.cvtColor(img, cv.COLOR_GRAY2RGB)
cv.rectangle(img_color, top_left, bottom_right, color=(255, 0, 0), thickness=10)
plt.subplot(121),plt.imshow(res, cmap = 'gray')
plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_color, cmap = 'gray')
plt.title(f'Detected Point - {top_left}, {rotation}[deg]'), plt.xticks([]), plt.yticks([])
plt.suptitle(meth)
plt.show()
本ページの情報は、特記無い限り下記 MIT ライセンスで提供されます。
| 2024-05-09 | - | 新規作成 |