使用OpenCV&&C++進行模板匹配


一:課程介紹

1.1:學習目標

  •   學會用imread載入圖像,和imshow輸出圖像。
  •   用nameWindow創建窗口,用createTrackbar加入滾動條和其回調函數的寫法。
  •   熟悉OpenCV函數matchTemplate並學會通過該函數實現模板匹配。
  •      學會怎樣將一副圖片中自己感興趣的區域標記出來

1.2:什么是模板匹配?

  在一副圖像中尋找和另一幅圖像最相似(匹配)部分的技術。

1.3:案例展示

  輸入有兩幅圖像一副是 template.jpg 另一幅是 original.jpg 。匹配完成的結果是result.jpg

 

 

二:實驗原理

  讓模板圖片在原圖片上的一次次滑動(從左到右,從上到下一個像素為單位的移動),然后將兩張圖片的像素值進行比對,然后選擇相似度最高的部分進行標記,當遇到相似度更高的部分時更換標記部分。掃描完畢之后,將相似度最高的部分標記出來,作為圖片進行輸出操作。

三:環境搭建

$ cd ~
$ sudo apt-get update
$ wget http://labfile.oss.aliyuncs.com/courses/671/opencv.sh
$ sudo chmod 777 opencv.sh
$ ./opencv.sh

在執行完之后執行如下語句,檢查是否安裝成功

./facedetect --cascade="/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_alt.xml" --scale=1.5 lena.jpg

 四:實驗步驟

4.1定義頭文件

  在這里我們用了

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>

  這三個頭文件:

    highgui.hpp:定義了創建窗口的flag,窗口事件的flag,Qt窗口的flag,事件回調函數原型,以及窗口/控件操作相關的系列函數,openGL的包裝函數;圖像輸入輸出顯示的相關函數;視頻攝像頭輸入輸出顯示的相關函數,VideoCapture,VideoWriter。

    imgproc.hpp:定義了圖像處理模塊之平滑和形態學操作。

    iostream:不再贅述。

4.2:設計主要功能並對其解析(從main函數入口開始分析

imread函數:

  img = imread("original.jpg");//載入元圖像
  templ = imread("template.jpg");//載入模版圖像

 

  imread函數可以將圖片讀取然后放到Mat容器里面用於后續操作。

nameWindow函數:

  namedWindow( image_window, CV_WINDOW_AUTOSIZE ); // 窗口名稱,窗口標識CV_WINDOW_AUTOSIZE是自動調整窗口大小以適應圖片尺寸。
  namedWindow( result_window, CV_WINDOW_AUTOSIZE );

  創建窗口。第一個參數是窗口名稱,第二個窗口是int類型的Flag可以填寫以下的值

  1. WINDOW_NORMAL設置了這個值,用戶便可以改變窗口的大小(沒有限制)
  2. WINDOW_AUTOSIZE如果設置了這個值,窗口大小會自動調整以適應所顯示的圖像,並且不能手動改變窗口大小

createTrackba函數:

 

/// 創建滑動條
  createTrackbar("匹配方法", image_window, &match_method, max_Trackbar, MatchingMethod ); //滑動條提示信息,滑動條所在窗口名,匹配方式(滑塊移動之后將移動到的值賦予該變量),回調函數。

  創建滑動條,第一個參數是匹配方法,第二個參數是確定滑動條所在窗口,第三個參數是對應滑動條的值,第四個參數是滑動條的最大值,第五個參數是回調函數。

自己寫的回調函數

MatchingMethod( 0, 0 );//初始化顯示

  先調用回調函數,在沒有滑動滑塊的時候也有圖像。

waitkey函數:

 waitKey(0); //等待按鍵事件,如果值0為則永久等待。

  其取值可以是<=0或大於0.當取值為<=0的時候,如果沒有鍵盤觸發則一直等待,否則返回值為按下去的ascll對應數字。

 Mat::copyto函數:

Mat img_display;
img.copyTo( img_display ); //將 img的內容拷貝到 img_display

創建Mat類型數據結構img_display。並將img內容賦值給img_display

Mat::create函數:

/// 創建輸出結果的矩陣
  int result_cols =  img.cols - templ.cols + 1;     //計算用於儲存匹配結果的輸出圖像矩陣的大小。
  int result_rows = img.rows - templ.rows + 1;

  result.create( result_cols, result_rows, CV_32FC1 );//被創建矩陣的列數,行數,以CV_32FC1形式儲存。

matchTemplate (模版匹配)函數

matchTemplate( img, templ, result, match_method ); //待匹配圖像,模版圖像,輸出結果圖像,匹配方法(由滑塊數值給定。)

我們在createTrackba函數那里見到過match_method變量,這個是決定匹配方法的變量,由滑塊確定。

normalize(歸一化函數)

normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );//輸入數組,輸出數組,range normalize的最小值,range normalize的最大值,歸一化類型,當type為負數的時候輸出的type和輸入的type相同。

  歸一化就是要把需要處理的數據經過處理后(通過某種算法)限制在你需要的一定范圍內。首先歸一化是為了后面數據處理的方便,其次是保證程序運行時收斂加快。歸一化的具體作用是歸納統一樣本的統計分布性。歸一化在0-1之間是統計的概率分布,歸一化在某個區間上是統計的坐標分布。歸一化有同一、統一和合一的意思。

minMaxLoc函數

minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );//用於檢測矩陣中最大值和最小值的位置

  用於尋找距震中的最大值和最小值

不同方法之間選擇最佳精確度

  if( match_method  == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED )
    { matchLoc = minLoc; }
  else
    { matchLoc = maxLoc; }

  對於方法CV_TM_SQDIFF,和CV_TM_SQDIFF_NORMED,越小的數值代表越准確匹配結果,而對於其他方法,數值越大匹配的准確度越高。

將最后得到的結果顯性的標記出來

 

  /// 讓我看看您的最終結果
  rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar(0,0,255), 2, 8, 0 ); //將得到的結果用矩形框起來
  rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar(0,0,255), 2, 8, 0 );

 

  第一個參數(img):將要被操作的圖像,第二個和第三個參數分別是一個矩形的對角點。第四個(color)參數是線條的顏色(RGB)。第五個參數(thickness):組成矩陣線條的粗細程度。第六個參數(line_type):線條的類型,見cvLine的描述。第七個參數shift:坐標點的小數點位數

4.3:應用算法解析

  matchTemplate實現了末班匹配散發:其中可用的方法有六個:

    1:  平方差匹配: method = CV_TM_SQDIFF

          這類方法利用平方差來進行匹配最好匹配為0.匹配差越大,匹配值越大。

          R(x,y)= \sum _{x',y'} (T(x',y')-I(x+x',y+y'))^2

    2:  標准平方差匹配:method = CV_TM_SQDIFF_NORMED

          R(x,y)= \frac{\sum_{x',y'} (T(x',y')-I(x+x',y+y'))^2}{\sqrt{\sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}

          這類方法利用平方差來進行匹配最好匹配為0.匹配差越大,匹配值越大。

    3:  相關匹配method=CV_TM_CCORR

          R(x,y)= \sum _{x',y'} (T(x',y')  \cdot I(x+x',y+y'))

          這類方法采用模板和圖像間的乘法操作,所以較大的數表示匹配程度較高,0標識最壞的匹配效果.

    4:  標准相關匹配 method=CV_TM_CCORR_NORMED

          R(x,y)= \frac{\sum_{x',y'} (T(x',y') \cdot I'(x+x',y+y'))}{\sqrt{\sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}

          同標准平方差和平方差,以下不再贅述。

    5:  相關匹配 method=CV_TM_CCOEFF

          R(x,y)= \sum _{x',y'} (T'(x',y')  \cdot I(x+x',y+y'))

          這類方法將模版對其均值的相對值與圖像對其均值的相關值進行匹配,1表示完美匹配,-1表示糟糕的匹配,0表示沒有任何相關性(隨機序列).

    6:  標准相關匹配 method=CV_TM_CCOEFF_NORMED

          R(x,y)= \frac{ \sum_{x',y'} (T'(x',y') \cdot I'(x+x',y+y')) }{ \sqrt{\sum_{x',y'}T'(x',y')^2 \cdot \sum_{x',y'} I'(x+x',y+y')^2} }

          通常,隨着從簡單的測量(平方差)到更復雜的測量(相關系數),我們可獲得越來越准確的匹配(同時也意味着越來越大的計算代價). 最好的辦法是對所有這些設置多做一些測試實驗,以便為自己的應用選擇同時兼顧速度和精度的最佳方案.

有了上述相關知識之后相信你就可以看懂並且對下方代碼進行改造的能力了

五:試驗程序

  這里就是完整的代碼,上面對這些代碼已經做了完整的解析。

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>


using namespace std;
using namespace cv;

Mat img; Mat templ; Mat result;
char* image_window = "Source Image"; //窗口名稱定義
char* result_window = "Result window";  //窗口名稱定義


int match_method;
int max_Trackbar = 5;




void MatchingMethod( int, void* )
{

  Mat img_display;
  img.copyTo( img_display ); //將 img的內容拷貝到 img_display

  /// 創建輸出結果的矩陣
  int result_cols =  img.cols - templ.cols + 1;     //計算用於儲存匹配結果的輸出圖像矩陣的大小。
  int result_rows = img.rows - templ.rows + 1;

  result.create( result_cols, result_rows, CV_32FC1 );//被創建矩陣的列數,行數,以CV_32FC1形式儲存。

  /// 進行匹配和標准化
  matchTemplate( img, templ, result, match_method ); //待匹配圖像,模版圖像,輸出結果圖像,匹配方法(由滑塊數值給定。)
  normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );//輸入數組,輸出數組,range normalize的最小值,range normalize的最大值,歸一化類型,當type為負數的時候輸出的type和輸入的type相同。

  /// 通過函數 minMaxLoc 定位最匹配的位置
  double minVal; double maxVal; Point minLoc; Point maxLoc;
  Point matchLoc;

  minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() );//用於檢測矩陣中最大值和最小值的位置

  /// 對於方法 SQDIFF 和 SQDIFF_NORMED, 越小的數值代表更高的匹配結果. 而對於其他方法, 數值越大匹配越好
  if( match_method  == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED )
    { matchLoc = minLoc; }
  else
    { matchLoc = maxLoc; }

  /// 讓我看看您的最終結果
  rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar(0,0,255), 2, 8, 0 ); //將得到的結果用矩形框起來
  rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar(0,0,255), 2, 8, 0 );

  imshow( image_window, img_display );//輸出最終的到的結果
  imwrite("result.jpg",img_display); //將得到的結果寫到源代碼目錄下。
  imshow( result_window, result );   //輸出匹配結果矩陣。

  return;
}


int main( int argc, char** argv )
{

  img = imread("original.jpg");//載入待匹配圖像
  templ = imread("template.jpg");//載入模版圖像

  /// 創建窗口
  namedWindow( image_window, CV_WINDOW_AUTOSIZE ); // 窗口名稱,窗口標識CV_WINDOW_AUTOSIZE是自動調整窗口大小以適應圖片尺寸。
  namedWindow( result_window, CV_WINDOW_AUTOSIZE );

  /// 創建滑動條
  createTrackbar("jackchen", image_window, &match_method, max_Trackbar, MatchingMethod ); //滑動條提示信息,滑動條所在窗口名,匹配方式(滑塊移動之后將移動到的值賦予該變量),回調函數。

  MatchingMethod( 0, 0 );//初始化顯示

  int logo = waitKey(5000); //等待按鍵事件,如果值0為則永久等待。

  return 0;
}

 

 六:完整實驗流程

  在源代碼存放的文件夾里面有兩幅圖片,一幅original.jpg是待匹配的圖片,另一幅是template.jpg是模版圖片,然后輸出一副匹配成功的圖片。

 一下分別是 original.jpg,template,result.jpg

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM