1. 程式人生 > >opencv筆記(3)——模板匹配實現目標識別與跟蹤

opencv筆記(3)——模板匹配實現目標識別與跟蹤

1 知識補充

1.1 回撥函式

在影象處理時,如果我們需要實現實時的改變值,並重新開始程式,就需要我們自己實現回撥函式,其中,對於滑鼠事件的回撥,需要我們重寫滑鼠回撥函式void onMouse(int event, int x, int y, int flags, void* ustc); //滑鼠回撥函式

函式中的主要引數:

  • event對應於滑鼠事件
  • x,y滑鼠的位置引數
  • flags標誌位
  • ustc(不常用)

其中,int型event事件分別有:

  • EVENT_MOUSEMOVE 滑動
  • EVENT_LBUTTONDOWN 左擊
  • EVENT_RBUTTONDOWN 右擊
  • EVENT_MBUTTONDOWN 中鍵點選
  • EVENT_LBUTTONUP 左鍵放開
  • EVENT_RBUTTONUP 右鍵放開
  • EVENT_MBUTTONUP 中鍵放開
  • EVENT_LBUTTONDBLCLK 左鍵雙擊
  • EVENT_RBUTTONDBLCLK 右鍵雙擊
  • EVENT_MBUTTONDBLCLK 中鍵雙擊

重寫回調函式後,我們只要使用setMouseCallback函式開啟呼叫即可使用

minMaxLoc()函式找最大最小值
函式原型:void minMaxLoc(InputArray src,double * minVal=0,double* maxVal=0,Point* minLoc=0,Point* maxLoc=0,InputArray mask=noArray())

引數說明:

  • InputArray型的src,是單通道的輸入陣列
  • double * 型的minVal,返回最小值的指標,無返回則是NULL
  • double * 型的maxVal,返回最大值的指標,無返回則是NULL
  • Point * 型的minLoc,返回最小值的指標(二維),無返回則是NULL
  • Point * 型的miaxLoc,返回最大值的指標(二維),無返回則是NULL
  • InputArray型的mask,選擇可選掩膜

模板匹配函式matchTemplate()
模板匹配是從一張圖片裡找到與另一模板影象最匹配的部分
函式原型:void matchTemplate(InputArray image,InputArray templ,OutputArray result,int method)

引數說明:

  • 第一個引數,是待搜尋的圖片,為8位或32位浮點圖
  • 第二個引數,是帶匹配的圖片,格式應與源圖相同
  • 第三個引數,輸出結果圖片,單通道,32位浮點型
  • 第四個引數,匹配方式,共六種

六種匹配方式及引數:

  • 平方差匹配 method=TM_SQDIFF
  • 歸一化平方差匹配 method=TM_SQDIFF_NORMED
  • 相關匹配 method=TM_CCORR
  • 歸一化相關匹配method=TM_CCORR_NORMED
  • 係數匹配 method=TM_CCOEFF
  • 化相關係數匹配method=TM_CCOEFF_NORMED
    在這裡插入圖片描述
    注意:
    對於1和2兩種匹配方式,最小值為最好匹配,其餘的最大值為最好匹配
    程式碼思路
    首先讀取視訊,並且提取幀影象,使用回撥函式選中模板影象, 開始在下一幀中匹配,每次匹配完,都擷取相應位置的圖,作為下一次匹配的模板圖,並且用框框框選,輸出到輸出檔案中

可能有幫助的解釋
對於matchTemplate函式中result大小的解釋:
比如源圖尺寸為WH,待匹配圖為wh,那麼輸出結果大小就是(W-w+1)(H-h+1),原因是匹配是拿著小圖在大圖上移動來計算值的,所以移動的範圍就是(W-w+1)(H-h+1),計算的結果就儲存在這個結果矩陣中

總體程式碼及註釋

#include <core/core.hpp>    
#include <highgui/highgui.hpp>    
#include <imgproc/imgproc.hpp>    
#include<iostream>    
using namespace cv;
using namespace std;
Mat image;         //當前幀影象  
Mat imageCopy;     //用於拷貝的當前幀影象  
Mat rectImage;     //子影象  
bool leftButtonDownFlag = false; //左鍵單擊後視訊暫停標誌位  
Point beginPoint;  //矩形框起點  
Point endPoint;    //矩形框終點  
int resultRows;    //模板匹配result的行  
int resultcols;    //模板匹配result的列  
Mat ImageResult;   //模板匹配result  
double minValue;   //模板匹配result最小值  
double maxValude;  //模板匹配result最大值  
Point minPoint;    //模板匹配result最小值位置  
Point maxPoint;    //模板匹配result最大值位置  
int frameCount = 0; //幀數統計  

void onMouse(int event, int x, int y, int flags, void* ustc); //滑鼠回撥函式  

void Tips() {
    cout << "請選擇要使用的匹配規則:" << endl;
    cout << "                1.平方差匹配" << endl;
    cout << "                2.歸一化平方差匹配" << endl;
    cout << "                3.相關匹配" << endl;
    cout << "                4.歸一化相關匹配" << endl;
    cout << "                5.係數匹配" << endl;
    cout << "                6.化相關係數匹配" << endl;
    cout << "                0.退出" << endl;
}

int main(int argc, char*argv[])
{
    VideoCapture capture("C:\\Users\\14527\\Desktop\\Video\\cut.AVI");
    int capture_fps = capture.get(CV_CAP_PROP_FPS); //獲取視訊幀率 
    int capture_count = capture.get(CV_CAP_PROP_FRAME_COUNT);
    int capture_width = capture.get(CV_CAP_PROP_FRAME_WIDTH);
    int capture_height = capture.get(CV_CAP_PROP_FRAME_HEIGHT);
    cout << "視訊幀率:" << capture_fps<<endl;
    cout << "視訊幀數:" << capture_count << endl;
    cout << "視訊寬度:" << capture_width << endl;
    cout << "視訊高度:" << capture_height << endl;
    int pauseTime = 1000 / capture_fps; //兩幅畫面中間間隔  
    VideoWriter writer("C:\\Users\\14527\\Desktop\\Video\\out.avi", CV_FOURCC('X', 'V', 'I', 'D'), capture_fps, Size(capture_width, capture_height));
    Tips();
    int choice = 0;
    cin >> choice;
    namedWindow("Video");
    setMouseCallback("Video", onMouse);   //設定滑鼠回撥函式
    while (choice)
    {
        if (!leftButtonDownFlag) //滑鼠左鍵按下繪製矩形時,視訊暫停播放  
        {
            capture >> image;
            frameCount++;   //幀數  
        }
        if (!image.data || waitKey(pauseTime + 30) == 27)  //影象為空或Esc鍵按下退出播放  
        {
            break;
        }
        if (rectImage.data)  
        {
            ImageResult = Mat::zeros(resultRows, resultcols, CV_32FC1);//建立結果矩陣,注意是單通道的32位浮點型
            switch (choice) { //根據一開始的選擇使用對應的匹配模式
            case 1:
                matchTemplate(image, rectImage, ImageResult, TM_SQDIFF);
                break;
            case 2:
                matchTemplate(image, rectImage, ImageResult, TM_SQDIFF_NORMED);
                break;
            case 3:
                matchTemplate(image, rectImage, ImageResult, TM_CCORR);
                break;
            case 4:
                matchTemplate(image, rectImage, ImageResult, TM_CCORR_NORMED);
                break;
            case 5:
                matchTemplate(image, rectImage, ImageResult, TM_CCOEFF);
                break;
            case 6:
                matchTemplate(image, rectImage, ImageResult, TM_CCOEFF_NORMED);
                break;
            }
            minMaxLoc(ImageResult, &minValue, &maxValude, &minPoint, &maxPoint, Mat());  //最小值最大值獲取  
            Point point;
            switch (choice) {//為了統一處理,所以先把Point取出來
            case 1:
                point = minPoint;
                break;
            case 2:
                point = minPoint;
                break;
            case 3:
                point = maxPoint;
                break;
            case 4:
                point = maxPoint;
                break;
            case 5:
                point = maxPoint;
                break;
            case 6:
                point = maxPoint;
                break;
            }
            rectangle(image, point, Point(point.x + rectImage.cols, point.y + rectImage.rows), Scalar(0, 0, 255), 2); //繪製
            //更新當前模板匹配的模板  
            Mat resultImage = image(Rect(point, Point(point.x + rectImage.cols, point.y + rectImage.rows)));
            rectImage = resultImage.clone();
            //當前幀數輸出到視訊流  
            writer << image;
        }
        imshow("Video", image);
    }
    return 0;
}

//滑鼠回撥函式    
void onMouse(int event, int x, int y, int flags, void *ustc)
{
    if (event == CV_EVENT_LBUTTONDOWN)   //檢測到左鍵按下時
    {
        leftButtonDownFlag = true; //標誌位為true,也就是停止讀取下一幀影象  
        beginPoint = Point(x, y);  //設定左鍵按下點的矩形起點  
        endPoint = beginPoint;
    }
    if (event == CV_EVENT_MOUSEMOVE && leftButtonDownFlag)
    {                               //當滑鼠移動且之前左鍵有按下的話
        imageCopy = image.clone();  
        endPoint = Point(x, y);
        if (beginPoint != endPoint)
        {
            //在複製的影象上繪製矩形  
            rectangle(imageCopy, beginPoint, endPoint, Scalar(0, 0, 255), 2);
        }
        //imshow("Video", imageCopy);
    }
    if (event == CV_EVENT_LBUTTONUP) //左鍵放開時,開始匹配
    {
        leftButtonDownFlag = false;
        Mat subImage = image(Rect(beginPoint, endPoint)); //擷取影象  
        rectImage = subImage.clone();              //給全域性的待匹配影象 
        resultRows = image.rows - rectImage.rows + 1;   //輸出結果影象的行數及列數
        resultcols = image.cols - rectImage.rows + 1;
        //imshow("Sub Image", rectImage);
    }
}