模板匹配概述

模板匹配是通过一张模板图片去另一张图中找到与模板相似部分的一种算法。一个模板是一张小图片,这个图片有一定的尺寸,有角度(一般是不旋转的矩形, 角度为0)。

基于像素匹配算法

模板匹配算法一般是通过滑窗的方式在待匹配的图像上滑动,通过比较模板与子图的相似度,找到相似度最大的子图。这种算法最核心部分在于如何设计一个相似性函数。
最容易想到的一个相似性函数便是欧式距离:

欧式距离相似性函数

将这个相似性函数展开,可以得:

展开后相似性函数

可以看出,只有第二项是有意义的,因为第一项和第三项的值在选定模板后是固定的。对于欧式距离相似函数,值越大表示越不相似,也就是说,第二项的值越小则越不相似。

将第二项进行归一化:

归一化相似性函数

那么当R(i, j)为1时,表示模板与子图完全相等。

opencv实现

cv::matchTemplate(const CvArr* image,   //欲搜索的图像。它应该是单通道、8-比特或32-比特 浮点数图像
const CvArr* template,  //搜索模板,不能大于输入图像,且与输入图像具有一样的数据类型
CvArr* result,     //比较结果的映射图像。单通道、32-比特浮点数.
            若图像是W×H而templ是w×h,则result一定是(W-w+1)×(H-h+1)
int method      //CV_TM_SQDIFF、CV_TM_SQDIFF_NORMED、CV_TM_CCORR、
            CV_TM_CCORR_NORMED、CV_TM_CCOEFF、CV_TM_CCOEFF_NORMED
);

函数来进行模板匹配。其中的method参数具体如下:

  • CV_TM_SQDIFF 平方差匹配法:该方法采用平方差来进行匹配;最好的匹配值为0;匹配越差,匹配值越大。
  • CV_TM_CCORR 相关匹配法:该方法采用乘法操作;数值越大表明匹配程度越好。
  • CV_TM_CCOEFF 相关系数匹配法:1表示完美的匹配;-1表示最差的匹配。
  • CV_TM_SQDIFF_NORMED 归一化平方差匹配法
  • CV_TM_CCORR_NORMED 归一化相关匹配法
  • CV_TM_CCOEFF_NORMED 归一化相关系数匹配法

在通过matchTemplate函数进行模板匹配后,可以得到一个映射图,这张图中最大值的地方便是匹配度最大的子图的左上角坐标,可以使用cv::minMaxLoc函数获得子图位置和相应分数,再进行后续操作。

基于像素模板匹配的提速方案

  • 使用局部匹配,即刚开始匹配时,并不是把所有的点同时进行匹配,而是选择隔行隔列进行匹配,当匹配度达到自己所规定的阈值时,再继续匹配剩下的没匹配的行列。
  • 构建<a href="http://www.jianshu.com/p/b2da54d961ee">图像金字塔</a>进行逐步匹配,就是进行缩小扩大操作

传统模板匹配的缺陷在于不具有旋转不变形,若待匹配的图进行了旋转,那么这种滑窗的模板匹配方法当即失效。

可应对旋转缩放的模板匹配算法

使用传统的模板匹配速度较快,但是无法应对旋转和缩放问题。要解决旋转不变的 问题,必须要得到旋转不变的特征量,例如特征点。

SIFT/SURF/ORB特征匹配

使用SIFT或SURF计算得到模板和待匹配图像的特征点,然后使用RANSAC或者FLANN进行特征点匹配, 最后进行仿射变换便可得到匹配的位置。

  • 特征点提取
  • 特征点匹配
  • 仿射变化
    这种方法的缺点有几个:
  • 速度慢
  • 对于很小的图提取不到足够多的特征点

python opencv实现(surf):
# -- coding:utf-8 --
author = 'Microcosm'

import cv2
from image_process .image_process import *
import numpy as np

def filter_matches(kp1, kp2, matches, ratio = 0.75):
    mkp1, mkp2 = [], []
    for m in matches:
        if len(m) == 2 and m[0].distance < m[1].distance * ratio:
            m = m[0]
            mkp1.append( kp1[m.queryIdx] )
            mkp2.append( kp2[m.trainIdx] )
    p1 = np.float32([kp.pt for kp in mkp1])
    p2 = np.float32([kp.pt for kp in mkp2])
    kp_pairs = zip(mkp1, mkp2)
    return p1, p2, kp_pairs

def explore_match(win, img1, img2, kp_pairs, status = None, H = None):
    h1, w1 = img1.shape[:2]
    h2, w2 = img2.shape[:2]
    vis = np.zeros((max(h1, h2), w1+w2), np.uint8)
    vis[:h1, :w1] = img1
    vis[:h2, w1:w1+w2] = img2
    vis = cv2.cvtColor(vis, cv2.COLOR_GRAY2BGR)
    # get the position of template object find in detected image
    if H is not None:
        corners = np.float32([[0, 0], [w1, 0], [w1, h1], [0, h1]])
        img2 = cv2.warpPerspective(img2, H, (img2.shape[1], img2.shape[0]))
        # cv2.imshow('jj', img2)
        # cv2.waitKey()
        corners = np.int32( cv2.perspectiveTransform(corners.reshape(1, -1, 2), H).reshape(-1, 2) + (w1, 0) )
        cv2.polylines(vis, [corners], True, (0, 0, 255)) 
    # draw lines that connect match feature points
    if status is None:
        status = np.ones(len(kp_pairs), np.bool)
    p1 = np.int32([kpp[0].pt for kpp in kp_pairs])
    p2 = np.int32([kpp[1].pt for kpp in kp_pairs]) + (w1, 0)

    green = (0, 255, 0)
    red = (0, 0, 255)
    white = (255, 255, 255)
    kp_color = (51, 103, 236)
    for (x1, y1), (x2, y2), inlier in zip(p1, p2, status):
        if inlier:
            col = green
            cv2.circle(vis, (x1, y1), 2, col, -1)
            cv2.circle(vis, (x2, y2), 2, col, -1)
        else:
            col = red
            r = 2
            thickness = 3
            cv2.line(vis, (x1-r, y1-r), (x1+r, y1+r), col, thickness)
            cv2.line(vis, (x1-r, y1+r), (x1+r, y1-r), col, thickness)
            cv2.line(vis, (x2-r, y2-r), (x2+r, y2+r), col, thickness)
            cv2.line(vis, (x2-r, y2+r), (x2+r, y2-r), col, thickness)
    vis0 = vis.copy()
    for (x1, y1), (x2, y2), inlier in zip(p1, p2, status):
        if inlier:
            cv2.line(vis, (x1, y1), (x2, y2), green)

    cv2.imshow(win, vis)

import time
start = time.time()
img1 = cv2.imread("../image/timg.png")
img2 = cv2.imread("../image/timg.jpg")

img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# img2_gray=rotate_about_center(img2_gray, 90)
#img2_gray = cv2.resize(img2_gray, (img1_gray.shape[1], img1_gray.shape[0]))

start = time.time()
sift = surf = cv2.xfeatures2d.SURF_create()

kp1,des1 = sift.detectAndCompute(img1_gray, None)
kp2,des2 = sift.detectAndCompute(img2_gray, None)

end = time.time()
print "Extract feature time:" + str(end-start)

start = time.time()
# BFmatcher with default parms
bf = cv2.BFMatcher(cv2.NORM_L2)
matches = bf.knnMatch(des1, des2, k=2)
p1,p2,kp_pairs = filter_matches(kp1,kp2,matches,ratio=0.8)
end = time.time()
print "KNN match time:" + str(end-start)

if len(p1) >= 4:
    H , status = cv2.findHomography(p1 , p2 , cv2.RANSAC , 5.0)
    print H
    print '%d / %d  inliers/matched' % (np.sum(status) , len(status))
    # do not draw outliers (there will be a lot of them)
    kp_pairs = [kpp for kpp , flag in zip(kp_pairs , status) if flag]
else:
    H , status = None , None
    print '%d matches found, not enough for homography estimation' % len(p1)
explore_match('matches', img1_gray,img2_gray,kp_pairs, H=H)
# img3 = cv2.drawMatchesKnn(img1_gray,kp1,img2_gray,kp2,good[:10],flag=2)
end = time.time()
print "time"+str(end-start)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行的具体信息如下:
操作系统:ubuntu 14.04
运行环境:
opencv版本:opencv 3.0
模板大小:126x96 png
匹配图像大小:750x407 jpg
特征提取时间:0.15 s
KNN匹配时间:0.0024s
匹配效果:

匹配结果图,左侧为模板,右侧为待匹配图, 红色部分为匹配结果

opencv reg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容