1. 簡述
爲了讓大家不至於看到後面的公式就退卻,我先簡單描述一下模板匹配的原理:
- 簡單來說,模板匹配就是你拿一個模板(圖片)在目標圖片上依次滑動,每次計算模板與模板下方的子圖的相似度,最後我們就計算出了非常多的相似度;
- 如果你只是單個目標的匹配,那你只需要取相似度最大值所在的位置就可以得出匹配位置;
- 如果你要匹配多個目標,那就設定一個閾值,就是說,只要咋倆的相似度大於比如0.8,我就認爲你是我要匹配的目標。
2. 原理
2.1 計算步驟
- 你有一張模板圖像和一張較大的待搜索圖像,模板匹配是一種用於在較大圖像中搜索和查找模板圖像位置的方法。
- 具體就是將模板圖像滑動到輸入圖像上(就像在卷積操作一樣),然後在模板圖像下比較模板和輸入圖像的子圖的相似度(以下會介紹幾種不同的相似度度量方法)。
- 它返回一個灰度圖像,其中每個像素表示該像素的鄰域與模板匹配的相似度。如果輸入圖像的大小
(WxH)
和模板圖像的大小(wxh)
,則輸出圖像的大小將爲(W-w + 1,H-h + 1)
。 - 獲得相似度圖像之後,在其上查找最大相似度所在的像素。將其作爲匹配區域矩形的左上角,並以
(w,h)
作爲矩形的寬度和高度。該矩形是與模板匹配的區域。 - 下面放da張圖,免得大家看得太枯燥。大概用省略號的方式表示滑動操作:
2.2 相似度度量指標
- 差值平方和匹配
CV_TM_SQDIFF
- 標準化差值平方和匹配
CV_TM_SQDIFF_NORMED
- 相關匹配
CV_TM_CCORR
- 標準相關匹配
CV_TM_CCORR_NORMED
- 相關匹配
CV_TM_CCOEFF
- 標準相關匹配
CV_TM_CCOEFF_NORMED
下面的描述多數是公式,其中表示模板圖像,表示目標圖像$, 表示相似度矩陣
爲了簡化表示,下述公式僅表示模板與圖像中某個位置的相似度函數
2.2.1 差值平方和匹配 CV_TM_SQDIFF
- 原理: 計算模板與某個子圖的對應像素的差值平方和。
- 越相似該值越小
其中,是相對於目標大圖I左上角的座標,是相對於模板或者當前子圖的左上角的座標,那麼就是當前子圖上的某個點相對於目標大圖的絕對座標。
2.2.2 標準化差值平方和匹配 CV_TM_SQDIFF_NORMED
- 標準化的差值平方和
- 越相似該值越小
這種標準化操作可以保證當模板和圖像各個像素的亮度都乘上了同一個係數時,相關度不發生變化。也就是說當和變爲和時,不發生變化。
2.2.3 相關匹配 CV_TM_CCORR
- 模板與子圖對應位置相乘,可以將其看作是差值平方和平方項展開之後中間的那個項
- 越相似該值越大
2.2.4 標準相關匹配 CV_TM_CCORR_NORMED
- 標準化的相關匹配,
- 和標準化差值平方和匹配類似,都是去除了亮度線性變化對相似度計算的影響。可以保證圖像和模板同時變亮或變暗k倍時結果不變。
- 越相似該值越大
2.2.5 相關匹配 CV_TM_CCOEFF
- 這種方法也叫做相關匹配,但是,這裏是把圖像和模板都減去了各自的平均值,使得這兩幅圖像都沒有直流分量。
- 越相似該值越大
2.2.6 標準相關匹配 CV_TM_CCOEFF_NORMED
- 具體的說,就是在減去了各自的平均值之外,還要各自除以各自的方差。
- 經過減去平均值和除以方差這麼兩步操作之後,無論是我們的目標圖像還是模板都被標準化了,這樣可以保證圖像和模板分別改變光照不影響計算結果。
- 越相似該值越大
- 計算出的相關係數被限制在了 -1 到 1 之間,1 表示完全相同,-1 表示兩幅圖像的亮度正好相反,0 表示兩幅圖像之間沒有線性關係。
3. python實戰
3.1 單模板匹配單個目標(同時測試不同的相似度度量指標)
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('image3.png', 0)
img2 = img.copy()
template = cv2.imread('roi3.png', 0)
w, h = template.shape[::-1]
# All the 6 methods for comparison in a list
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
plt.subplot(4, 2, 1)
plt.imshow(template, cmap='gray')
plt.title('Template Image'), plt.xticks([]), plt.yticks([])
plt.subplot(4, 2, 2)
plt.imshow(img, cmap='gray')
plt.title('Target Image'), plt.xticks([]), plt.yticks([])
for i, meth in enumerate(methods):
img = img2.copy()
method = eval(meth)
# Apply template Matching
res = cv2.matchTemplate(img, template, method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv2.rectangle(img, top_left, bottom_right, 255, 2)
plt.subplot(4, 2, i+3)
plt.imshow(img, cmap='gray')
plt.title('Matching Result by {}'.format(meth)), plt.xticks([]), plt.yticks([])
plt.show()
3.2 單模板匹配多個目標
import cv2
import numpy as np
from matplotlib import pyplot as plt
img_rgb = cv2.imread('image1.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('roi1.png', 0)
w, h = template.shape[::-1]
plt.subplot(131)
plt.imshow(template)
plt.subplot(132)
plt.imshow(img_rgb)
res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):
cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (255, 255, 255), 2)
plt.subplot(133)
plt.imshow(img_rgb)
plt.show()
3.3 多模板匹配多個目標
import cv2
import numpy as np
from matplotlib import pyplot as plt
img_rgb = cv2.imread('image4.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template1 = cv2.imread('roi4_1.png', 0)
template2 = cv2.imread('roi4_2.png', 0)
template3 = cv2.imread('roi4_3.png', 0)
template_list = ['roi4_1.png', 'roi4_2.png', 'roi4_3.png']
w1, h1 = template1.shape[::-1]
w2, h2 = template1.shape[::-1]
w3, h3 = template1.shape[::-1]
plt.subplot(131)
plt.imshow(template1)
plt.subplot(132)
plt.imshow(template2)
plt.subplot(133)
plt.imshow(template3)
plt.show()
for i in range(len(template_list)):
template = cv2.imread(template_list[i], 0)
w, h = template.shape[::-1]
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.65
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):
gray = int(255/(i+1))
cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (gray, gray, gray), 2)
plt.imshow(img_rgb)
plt.show()
4. 算法優化
4.1 速度優化
這一塊我沒有看過opencv的優化方法,但是可想而知,直接滑動窗口效率是極其低下的,他們一定不會用這種方法。
- 我看到的一些相關文章,我沒仔細看,放個鏈接
https://www.cnblogs.com/imageshop/p/6108764.html - 另外我自己的一點想法,因爲之前看過卷積網絡的卷積優化,而卷積操作的原理就是滑動窗口,那麼,就可以將滑動窗口操作改成矩陣運算,具體可以看我之前寫過的github博客:各種卷積操作及其矩陣運算。
4.2 精度優化
- 多尺度模板匹配
- 旋轉目標模板匹配
- 非極大值抑制
https://juejin.im/post/5e995243e51d4546d72d42ce