原文:Image Processing with Python — Template Matching with Scikit-Image - 2021.01.30

基于 Python Scikit-learn 的模板匹配算法,其用于识别图片中的相似物体.

实现如,

#!/usr/bin/python3
#!--*-- coding: utf-8 --*--
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Rectangle

from skimage.io import imread, imshow
from skimage import transform
from skimage.color import rgb2gray
from skimage.feature import match_template
from skimage.feature import peak_local_max

测试图片,如下,

leuven = imread('leuven_picture.PNG')
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(leuven);

图:Leuven Town Hall

#转灰度图
leuven_gray = rgb2gray(leuven)
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(leuven_gray);

图:Grayscale Leuven Town Hall

#截取其中一个窗口window,作为模板template
template = leuven_gray[310:390,240:270]
imshow(template);

图:A Slice of Leuven

#将模板送入 match_template 函数
resulting_image = match_template(leuven_gray, template)
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(resulting_image, cmap='magma');

图:match_template 函数输出结果

结果图像中越亮brighter的部分,其与模板匹配的越接近.

#最接近的一个
x, y = np.unravel_index(np.argmax(resulting_image), resulting_image.shape)
template_width, template_height = template.shape

rect = plt.Rectangle((y, x), template_height, template_width, 
                     color='r', fc='none')
plt.figure(num=None, figsize=(8, 6), dpi=80)
plt.gca().add_patch(rect)
imshow(leuven_gray);

图:最佳匹配

template_width, template_height = template.shape
plt.figure(num=None, figsize=(8, 6), dpi=80)

for x, y in peak_local_max(result, threshold_abs=0.5, 
                           exclude_border = 20):
    rect = plt.Rectangle((y, x), template_height, template_width,
                          color='r', fc='none')
    plt.gca().add_patch(rect)
imshow(leuven_gray);

图:多个模板匹配结果

#去除假阳样本false positives
#一种方式是,利用 homography matrix 
#参考:https://towardsdatascience.com/image-processing-with-python-applying-homography-for-image-warping-84cd87d2108f

points_of_interest =[[240, 130], 
                     [525, 255], 
                     [550, 545], 
                     [250, 545]]
projection = [[180, 150],
              [520, 150],
              [520, 550],
              [180, 550]]
color = 'red'
patches = []
fig, ax = plt.subplots(1,2, figsize=(15, 10), dpi = 80)
for coordinates in (points_of_interest + projection):
    patch = Circle((coordinates[0],coordinates[1]), 10, 
                    facecolor = color)
    patches.append(patch)
    
for p in patches[:4]:
    ax[0].add_patch(p)
ax[0].imshow(leuven_gray);

for p in patches[4:]:
    ax[1].add_patch(p)
ax[1].imshow(np.ones((leuven_gray.shape[0], leuven_gray.shape[1])));

图:原始角点和目标角点

points_of_interest = np.array(points_of_interest)
projection = np.array(projection)

tform = transform.estimate_transform('projective', points_of_interest, projection)
tf_img_warp = transform.warp(leuven, tform.inverse)

plt.figure(num=None, figsize=(8, 6), dpi=80)
fig, ax = plt.subplots(1,2, figsize=(15, 10), dpi = 80)
ax[0].set_title(f'Original', fontsize = 15)
ax[0].imshow(leuven)
ax[0].set_axis_off();
ax[1].set_title(f'Transformed', fontsize = 15)
ax[1].imshow(tf_img_warp)
ax[1].set_axis_off();

图:变换后图像

#变换后重新匹配
points_of_interest = np.array(points_of_interest)
projection = np.array(projection)

tform = transform.estimate_transform('projective', points_of_interest, projection)
tf_img_warp = transform.warp(leuven_gray, tform.inverse)

plt.figure(num=None, figsize=(8, 6), dpi=80)
fig, ax = plt.subplots(1,2, figsize=(15, 10), dpi = 80)
ax[0].set_title(f'Original', fontsize = 15)
ax[0].imshow(leuven_gray, cmap = 'gray')
ax[0].set_axis_off();
ax[1].set_title(f'Transformed', fontsize = 15)
ax[1].imshow(tf_img_warp, cmap = 'gray')
ax[1].set_axis_off();

图:变换后的灰度图

result = match_template(tf_img_warp, template)

plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(result, cmap=’magma’);

图:模板匹配结果

template_width, template_height = template.shape

plt.figure(num=None, figsize=(8, 6), dpi=80)
for x, y in peak_local_max(result, threshold_abs=0.5, 
                           exclude_border = 10):
    rect = plt.Rectangle((y, x), template_height, template_width, 
                          color='r', fc='none')
    plt.gca().add_patch(rect)
imshow(tf_img_warp);

图:变换后图像的多个模板匹配结果(可以看出,仍有一些假阳样本)

#采用 filter 模板匹配
template_width, template_height = template.shape

matched_list = []
for x, y in peak_local_max(result, threshold_abs=0.50, exclude_border = 10):
    rect = plt.Rectangle((y, x), template_height, template_width)
    coord = Rectangle.get_bbox(rect).get_points()
    matched_list.append(coord)
    
matched_patches = [tf_img_warp[int(match[0][1]):int(match[1][1]),
                               int(match[0][0]):int(match[1][0])] for match in matched_list]

difference = [abs(i.flatten() - template.flatten()) for i in matched_patches]
summed_diff = [array.sum() for array in difference]
final_patches =list(zip(matched_list,summed_diff))
statistics.mean(summed_diff)

#通过 mean diff、median diff、percentile diff 进行 filter 匹配结果
summed_diff = np.array(summed_diff)
filtered_list_mean =list(filter(lambda x: x[1] <= 
                         summed_diff.mean(), final_patches))
filtered_list_median =list(filter(lambda x: x[1] <=
                           np.percentile(summed_diff, 50), 
                           final_patches))
filtered_list_75 =list(filter(lambda x: x[1] <= 
                       np.percentile(summed_diff, 75),
                       final_patches))

#可视化结果
fig, ax = plt.subplots(1,3, figsize=(17, 10), dpi = 80)
template_width, template_height = template.shape

for box in filtered_list_mean:
    patch = Rectangle((box[0][0][0],box[0][0][1]), template_height, 
                       template_width, edgecolor='b',  
                       facecolor='none', linewidth = 3.0)
    ax[0].add_patch(patch)
ax[0].imshow(tf_img_warp, cmap = 'gray');
ax[0].set_axis_off()

for box in filtered_list_median:
    patch = Rectangle((box[0][0][0],box[0][0][1]), template_height, 
                       template_width, edgecolor='b', 
                       facecolor='none', linewidth = 3.0)
    ax[1].add_patch(patch)
ax[1].imshow(tf_img_warp, cmap = 'gray');
ax[1].set_axis_off()

for box in filtered_list_75:
    patch = Rectangle((box[0][0][0],box[0][0][1]), template_height, 
                       template_width, 
                       edgecolor='b', facecolor='none', 
                       linewidth = 3.0)
    ax[2].add_patch(patch)
ax[2].imshow(tf_img_warp, cmap = 'gray');
ax[2].set_axis_off()
fig.tight_layout()

图:模板过滤后的结果

可以看出,结果看起来好了一些. 然而,注意到,Mean 和 Median 虽然具有更少的假正样本(less false positives),但真正样本(true positives)也更少;75 Percent filter 则保持了几乎所有的真正样本(true positives).

Last modification:July 5th, 2022 at 11:14 pm