模板匹配

在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 = [[row1, row2, row3...], [col1, col2, col3]]  注意和opencv的x y转换
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