模板匹配的作用在圖像識別領域作用可大了。那什么是模板匹配?
模板匹配,就是在一幅圖像中尋找另一幅模板圖像最匹配(也就是最相似)的部分的技術。
說的有點抽象,下面給個例子說明就很明白了。
在上面這幅全明星照中,我們想找出姚明頭像的位置,並把它標記出來,可以做到嗎?
可以,這就是模板匹配的要做的事情。
其實模板匹配實現的思想也是很簡單很暴力的,就是拿着模板圖片(姚明頭像)在原圖(全明星照)中從左上至右下依次滑動,直到遇到某個區域的相似度低於我們設定的閾值,那么我們就認為該區域與模板匹配了,也就是我們找到了姚明的位置,並把它標記出來。
OpenCV中是通過MtachTemplate函數完成。
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
int main()
{
Mat img, templ, result;
img = imread("nba.jpg");
templ = imread("76.png");
int result_cols = img.cols - templ.cols + 1;
int result_rows = img.rows - templ.rows + 1;
result.create(result_cols, result_rows, CV_32FC1);
matchTemplate(img, templ, result, CV_TM_SQDIFF_NORMED);//這里我們使用的匹配算法是標准平方差匹配 method=CV_TM_SQDIFF_NORMED,數值越小匹配度越好
normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
double minVal = -1;
double maxVal;
Point minLoc;
Point maxLoc;
Point matchLoc;
cout << "匹配度:" << minVal << endl;
minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());
cout << "匹配度:" << minVal << endl;
matchLoc = minLoc;
rectangle(img, matchLoc, Point(matchLoc.x + templ.cols, matchLoc.y + templ.rows), Scalar(0, 255, 0), 2, 8, 0);
imshow("img", img);
waitKey(0);
return 0;
}
結果看來,大姚的頭像位置確實被綠框標記出來了!很准!
我還在程序中特意打印出匹配度的最小值,因為我們知道這個算法是數值越小匹配度越高,由輸出的結果看來這個數值還真的很小,說明匹配度真的相當高!
既然我們可以取得匹配度的數值,那我們是不是也可以利用該數值進行閾值對比呢?比如我想把在閾值范圍之內的頭像都標記出來。可以這么做:
//閾值判別,小於0.01才認為匹配成功,才將頭像框出來
if (minVal < 0.001)
{
rectangle(img, matchLoc, Point(matchLoc.x + templ.cols, matchLoc.y + templ.rows), Scalar(0, 255, 0), 2, 8, 0);
}
同理,如果是數值越大表明匹配度越大的算法,就使用maxVal來對比就可以了。
上面的模板匹配我們使用了標准平方差匹配 CV_TM_SQDIFF_NORMED算法,看起來效果還不錯,那還有其他算法嗎?
問得好。OpenCV通過函數 matchTemplate 實現了模板匹配算法。可用的方法有6個:
通常,隨着從簡單的測量(平方差)到更復雜的測量(相關系數),我們可獲得越來越准確的匹配(同時也意味着越來越大的計算代價)。
最好的辦法是對所有這些設置多做一些測試實驗,以便為自己的應用選擇同時兼顧速度和精度的最佳方案。
你想采用哪種算法,只需要將對應的傳進函數matchTemplate里就可以了。
下面給出利用trackbar顯示出多種模板那匹配算法的代碼。
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat g_srcImage, g_tempalteImage, g_resultImage;
int g_nMatchMethod;
int g_nMaxTrackbarNum = 5;
void on_matching(int, void*)
{
Mat srcImage;
g_srcImage.copyTo(srcImage);
int resultImage_cols = g_srcImage.cols - g_tempalteImage.cols + 1;
int resultImage_rows = g_srcImage.rows - g_tempalteImage.rows + 1;
g_resultImage.create(resultImage_cols, resultImage_rows, CV_32FC1);
matchTemplate(g_srcImage, g_tempalteImage, g_resultImage, g_nMatchMethod);
normalize(g_resultImage, g_resultImage, 0, 2, NORM_MINMAX, -1, Mat());
double minValue, maxValue;
Point minLocation, maxLocation, matchLocation;
minMaxLoc(g_resultImage, &minValue, &maxValue, &minLocation, &maxLocation);
if (g_nMatchMethod == TM_SQDIFF || g_nMatchMethod == CV_TM_SQDIFF_NORMED)
{
matchLocation = minLocation;
}
else
{
matchLocation = maxLocation;
}
rectangle(srcImage, matchLocation, Point(matchLocation.x + g_tempalteImage.cols, matchLocation.y + g_tempalteImage.rows), Scalar(0, 0, 255), 2, 8, 0);
rectangle(g_resultImage, matchLocation, Point(matchLocation.x + g_tempalteImage.cols, matchLocation.y + g_tempalteImage.rows), Scalar(0, 0, 255), 2, 8, 0);
imshow("原始圖", srcImage);
imshow("效果圖", g_resultImage);
}
int main()
{
g_srcImage = imread("nba.jpg");
if (!g_srcImage.data)
{
cout << "原始圖讀取失敗" << endl;
return -1;
}
g_tempalteImage = imread("76.png");
if (!g_tempalteImage.data)
{
cout << "模板圖讀取失敗" << endl;
return -1;
}
namedWindow("原始圖", CV_WINDOW_AUTOSIZE);
namedWindow("效果圖", CV_WINDOW_AUTOSIZE);
createTrackbar("方法", "原始圖", &g_nMatchMethod, g_nMaxTrackbarNum, on_matching);
on_matching(0, NULL);
waitKey(0);
return 0;
}
當然也會有一些算法匹配失敗的.
實驗證明,該段程序效果很不錯,但注意的是,模板配在原圖摳出模板圖的形式下准確率才比較高,不然的話可能准確度就不太高了。
那么模板匹配能在哪些項目有應用呢?我說一下我的經驗。
最近我在參與實驗室的一個項目,做的是發票的分類,分類的方法我首先采用的是模板匹配,也就是從一類發票中摳出一些特征區域,以此作為模板,自己設定閾值,低於閾值就是算是跟該類發票匹配了,就可以 對其進行分類。在我的測試看來,准確率還可以,不過也隱藏這一個比較大的隱患就是,一旦發票種類多了,比如100種,那么檢測時間就會指數上升,這是不可取的。