1. 程式人生 > 其它 >opencv的模板匹配方法

opencv的模板匹配方法

模板匹配

在opencv中進行模板匹配使用cv2.matchTemplate函式,簡單使用如下:

import cv2
import numpy as np

img = cv2.imread('landscape.jpg', cv2.IMREAD_GRAYSCALE)
tpl = cv2.imread('template.jpg', cv2.IMREAD_GRAYSCALE)

result = cv2.matchTemplate(img, tpl, cv2.TM_CCORR_NORMED)
# 返回的max_loc 是按照Opencv 的座標(x, y),不是numpy的(row, col)
(_, max_val, _, max_loc) = cv2.minMaxLoc(result)
# 利用最大值*0.99 作為閾值,閾值可根據情況設定
threshold = max_val * 0.99
# loc = [[x1, x2, x3...], [y1, y2, y3]]  注意和numpy的row col轉換
loc = np.where(result >= threshold)

tr = img.copy()
for pt in zip(*loc[::-1]):
	cv2.rectangle(tr, pt, (pt[0] + tw, pt[1] + th), (0, 0, 255), 1)

其中

result = cv2.matchTemplate(img, tpl, cv2.TM_CCORR_NORMED)

假設landscape圖片大小為5120x9760,讀取進來的img矩陣大小為9760*5120,template圖片大小為366x533,讀入tpl的大小為533x366。則我們可以推算出result的大小:(9760-533+1, 5120-366+1)=(9208, 4755)。陣列中記錄的是進行模板匹配的相合資料。result[m][n]代表的是從img的m行n列畫素點開始進行匹配的結果。你可以把result[m][n]轉化到PyQT的座標系,變為(n, m),那麼影象在PyQt的範圍就是(n, m) ~ (n+tw, m+th)。

loc返回值是一個二維矩陣:[[row0, row1, row2, ...],[col0, col1, col2, ...]](row0, col0)是指在result中row0行col0列符合條件。

關於[::-1]的解釋:a = np.array([1, 2, 3, 4])執行a[::-1]後返回結果為[4, 3, 2, 1],對於二維矩陣b = np.array([[1, 2],[5, 6]])執行b[::-1]後返回結果為[[5, 6], [1, 2]]。對於返回loc執行這步操作是因為result是按(x, y)順序排列,對於x來說其實代表的是列,如果我們想要行在前,必須反過來。

*loc[::-1]

最前方的*,是解除一層[],例如a = np.array([[1, 2],[5, 6]])*a[1, 2], [5, 6]

上述的程式碼存在一些問題:匹配出的模板有重疊,例如,圖片((0, 0), (100, 100))區域匹配度比較高,但是((1, 1), (101, 101))的匹配度也很高,這就導致匹配出多個重疊區域。可以通過nms極大值抑制。

th, tw = tpl.shape[:2]
loc = np.where(result >= threshold)
score = result[result >= max_val*0.99] # 返回的是值,值原來順序變為一維矩陣

xmin = np.array(loc[1])
ymin = np.array(loc[0])
xmax = xmin + tw
ymax = ymin + th
# 全部轉為一維列向量
xmin, ymin, xmax, ymax = xmin.reshape(-1, 1), ymin.reshape(-1, 1), xmax.reshape(-1, 1), ymax.reshpae(-1, 1)
score = score.reshape(-1, 1)

data_hlist = []
data_hlist.append(xmin); data_hlist.append(ymin); data_hlist.append(xmax); data_hlist.append(ymax); data_hlist.append(score)
data_hstack = np.hstack(data_hlist) #按照 xmin, ymin, xmax, ymax, score 順序
thresh = 0.3 #設定NMS互動比
keep_dets = py_nms(data_hstack, thresh)
dets = data_hstack[keep_dets]

tr = img.copy()
for rec in dets:
	cv2.rectangle(tr, (int(rec[0]), int(rec[1])), (int(rec[2]), int(rec[3])), (0, 0, 255), 2)

實現py_nms函式:

# NMS 非極大值抑制
def py_nms(dets, thresh):
    """Pure Python NMS baseline."""
    # x1、y1、x2、y2、以及score賦值
    # (x1、y1)(x2、y2)為box的左上和右下角座標
    x1 = dets[:, 0]
    y1 = dets[:, 1]
    x2 = dets[:, 2]
    y2 = dets[:, 3]
    scores = dets[:, 4]
    # 每一個候選框的面積
    areas = (x2 - x1 + 1) * (y2 - y1 + 1)
    # order是按照score降序排序的有,返回的是索引陣列,[::-1]為了將升序變為降序
    order = scores.argsort()[::-1]
    keep = []
    while order.size > 0:
        i = order[0]
        keep.append(i)
        # 計算當前概率最大矩形框與其他矩形框的相交框的座標,得到的是向量
        xx1 = np.maximum(x1[i], x1[order[1:]]) # x1[i]雖然為一個數,但是由於numpy的broadcast機制,xx1是一個數組
        yy1 = np.maximum(y1[i], y1[order[1:]])
        xx2 = np.minimum(x2[i], x2[order[1:]])
        yy2 = np.minimum(y2[i], y2[order[1:]])
        # 計算相交框的面積,注意矩形框不相交時w或h算出來會是負數,用0代替
        w = np.maximum(0.0, xx2 - xx1 + 1)
        h = np.maximum(0.0, yy2 - yy1 + 1)
        inter = w * h
        # 計算重疊度IOU:重疊面積/(面積1+面積2-重疊面積)
        ovr = inter / (areas[i] + areas[order[1:]] - inter)
        # 找到重疊度不高於閾值的矩形框索引
        inds = np.where(ovr <= thresh)[0]
        # 將order序列更新,由於前面得到的矩形框索引要比矩形框在原order序列中的索引小1,所以要把這個1加回來
        order = order[inds + 1]
    return keep