1. 程式人生 > 其它 >OpenCV使用稀疏光流實現視訊物件跟蹤

OpenCV使用稀疏光流實現視訊物件跟蹤

一、概述

  案例:使用稀疏光流實現物件跟蹤

  稀疏光流API介紹:

calcOpticalFlowPyrLK( InputArray prevImg, InputArray nextImg,
                                        InputArray prevPts, InputOutputArray nextPts,
                                        OutputArray status, OutputArray err,
                                        Size winSize 
= Size(21,21), int maxLevel = 3, TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01), int flags = 0, double minEigThreshold = 1e-4 );
prevImg:視訊前一幀影象/金字塔,單通道CV_8UC1
nextImg:視訊後一幀影象/金字塔,單通道CV_8UC1
preVPts:前一幀影象的特徵向量(輸入)需要找到流的2D點的向量(vector of 2D points for which the flow needs to be found;);點座標必須是單精度浮點數
nextPts:後一幀影象的特徵向量(輸出),輸出二維點的向量(具有單精度浮點座標),包含第二影象中輸入特徵的計算新位置;當傳遞OPTFLOW_USE_INITIAL_FLOW標誌時,向量必須與輸入中的大小相同。
status:輸出狀態向量(無符號字元);如果找到相應特徵的流,則向量的每個元素設定為1,否則設定為0
err:輸出錯誤的向量; 向量的每個元素都設定為相應特徵的錯誤,錯誤度量的型別可以在flags引數中設定; 如果未找到流,則未定義錯誤(使用status引數查詢此類情況)
winSize:每個金字塔等級的搜尋視窗的winSize大小
maxLevel:基於0的最大金字塔等級數;如果設定為0,則不使用金字塔(單級),如果設定為1,則使用兩個級別,依此類推;如果將金字塔傳遞給輸入,那麼演算法將使用與金字塔一樣多的級別,但不超過maxLevel
criteria:停止條件,指定迭代搜尋演算法的終止條件(在指定的最大迭代次數criteria.maxCount之後或當搜尋視窗移動小於criteria.epsilon時)。
flags:操作標誌,OPTFLOW_USE_INITIAL_FLOW使用初始估計,儲存在nextPts中;如果未設定標誌,則將prevPts複製到nextPts並將其視為初始估計。OPTFLOW_LK_GET_MIN_EIGENVALS使用最小特徵值作為誤差測量(參見minEigThreshold描述);如果沒有設定標誌,則將原稿周圍的色塊和移動點之間的L1距離除以視窗中的畫素數,用作誤差測量
minEigThreshold:演算法計算光流方程的2x2正常矩陣的最小特徵值,除以視窗中的畫素數;如果此值小於minEigThreshold,則過濾掉相應的功能並且不處理其流程,因此它允許刪除壞點並獲得性能提升

  演算法實現步驟:

    1.例項化VideoCapture

    2.迴圈讀取視訊資料

    3.視訊幀灰度轉換

    4.執行角點檢測

    5.儲存角點檢測的特徵資料

    6.初始化時如果檢測到前一幀為空,把當前幀的灰度影象給前一幀

    7.執行光流跟蹤,並輸出跟蹤後的特徵向量

    8.遍歷光流跟蹤的輸出特徵向量,並得到距離和狀態都符合預期的特徵向量。讓後將其重新填充到fpts[1]中備用

    9.重置集合大小

    10.繪製光流線

    11.交換特徵向量的輸入和輸出

    12.將用於跟蹤的角點繪製出來

    13.展示最終的跟蹤效果

    14.迴圈3~13步驟

    15.結束

二、程式碼示例

KLT_Object_Tracking::KLT_Object_Tracking(QWidget *parent)
    : MyGraphicsView{parent}
{
    isShowLine = false;
    this->setWindowTitle("KLT稀疏光流實現物件跟蹤");
    QPushButton *btn = new QPushButton(this);
    btn->setText("選擇視訊");
    connect(btn,&QPushButton::clicked,[=](){
        //選擇視訊
        path = QFileDialog::getOpenFileName(this,"請選擇視訊","/Users/yangwei/Downloads/",tr("Image Files(*.mp4 *.avi)"));
        qDebug()<<"視訊路徑:"<<path;
        startKltTracking(path.toStdString().c_str());
    });
    //
    QButtonGroup * group = new QButtonGroup(this);
    QRadioButton * radioNo = new QRadioButton(this);
    radioNo->setText("");
    radioNo->setChecked(true);
    QRadioButton *radioYes = new QRadioButton(this);
    radioYes->setText("");
    group->addButton(radioNo,0);
    group->addButton(radioYes,1);

    radioNo->move(0,btn->y()+btn->height()+20);
    radioYes->move(radioNo->x()+radioNo->width()+20,btn->y()+btn->height()+20);
    connect(radioNo,&QRadioButton::clicked,[=](){
        isShowLine = false;//顯示光流線
    });
    connect(radioYes,&QRadioButton::clicked,[=](){
        isShowLine = true;//不顯示光流線
    });

}

void KLT_Object_Tracking::startKltTracking(const char* filePath){
    //【1】例項化VideoCapture並開啟視訊
    VideoCapture capture;//例項化視訊捕獲器
    capture.open(filePath);//開啟視訊檔案(或攝像頭)
    if(!capture.isOpened()){//檢測檔案是否開啟,如果沒開啟直接退出
        qDebug()<<"無法開啟視訊";
        return;
    }

    Mat frame,gray;
    vector<Point2f> features;//檢測出來的角點集合
    vector<Point2f> inPoints;//這個主要是為了畫線用的
    vector<Point2f> fpts[2];//[0],存入的是是二維特徵向量,[1]輸出的二維特徵向量
    Mat pre_frame,pre_gray;
    vector<uchar> status;//光流輸出狀態
    vector<float> err;//光流輸出錯誤
    //【2】迴圈讀取視訊
    while(capture.read(frame)){//迴圈讀取視訊中每一幀的影象
        //【3】將視訊幀影象轉為灰度圖
        cvtColor(frame,gray,COLOR_BGR2GRAY);//ps:角點檢測輸入要求單通道

        //【4】如果特徵向量(角點)小於40個我們就重新執行角點檢測
        if(fpts[0].size()<40){//如果小於40個角點就重新開始執行角點檢測
            //執行角點檢測
            goodFeaturesToTrack(gray,features,5000,0.01,10,Mat(),3,false,0.04);
            //【5】將檢測到的角點放入fpts[0]中作為,光流跟蹤的輸入特徵向量
            //將檢測到的角點插入vector
            fpts[0].insert(fpts[0].begin(),features.begin(),features.end());
            inPoints.insert(inPoints.end(),features.begin(),features.end());
            qDebug()<<"角點檢測執行完成,角點個數為:"<<features.size();
        }else{
            qDebug()<<"正在跟蹤...";
        }
        //【6】初始化的時候如果檢測到前一幀為空,這個把當前幀的灰度影象給前一幀
        if(pre_gray.empty()){//如果前一幀為空就給前一幀賦值一次
            gray.copyTo(pre_gray);
        }

        //執行光流跟蹤
        qDebug()<<"開始執行光流跟蹤";
        //【7】執行光流跟蹤,並將輸出的特徵向量放入fpts[1]中
        calcOpticalFlowPyrLK(pre_gray,gray,fpts[0],fpts[1],status,err);
        qDebug()<<"光流跟蹤執行結束";
        //【8】遍歷光流跟蹤的輸出特徵向量,並得到距離和狀態都符合預期的特徵向量。讓後將其重新填充到fpts[1]中備用
        int k =0;
        for(size_t i=0;i<fpts[1].size();i++){//迴圈遍歷二維輸出向量
            double dist = abs(fpts[0][i].x - fpts[1][i].x) + abs(fpts[0][i].y - fpts[1][i].y);//特徵向量移動距離
            if(dist>2&&status[i]){//如果距離大於2,status=true(正常)
                inPoints[k] = inPoints[i];
                fpts[1][k++] = fpts[1][i];
            }
        }
        //【9】重置集合大小(由於有錯誤/不符合條件的輸出特徵向量),只拿狀態正確的
        //重新設定集合大小
        inPoints.resize(k);
        fpts[1].resize(k);
        //【10】繪製光流線,這一步要不要都行
        //繪製光流線
        if(isShowLine){
            for(size_t i = 0;i<fpts[1].size();i++){
                line(frame,inPoints[i],fpts[1][i],Scalar(0,255,0),1,8,0);
                circle(frame, fpts[1][i], 2, Scalar(0, 0, 255), 2, 8, 0);
            }
        }

        qDebug()<<"特徵向量的輸入輸出交換資料";
        //【11】交換特徵向量的輸入和輸出,(迴圈往復/進入下一個迴圈),此時特徵向量的值會遞減
        std::swap(fpts[1],fpts[0]);//交換特徵向量的輸入和輸出,此處焦點的總數量會遞減

        //【12】將用於跟蹤的角點繪製出來
        //將角點繪製出來
        for(size_t i = 0;i<fpts[0].size();i++){
            circle(frame,fpts[0][i],2,Scalar(0,0,255),2,8,0);
        }

        //【13】重置前一幀影象(每一個迴圈都要重新整理)
        gray.copyTo(pre_gray);
        frame.copyTo(pre_frame);
        //【14】展示最終的效果
        imshow("frame",frame);
        int keyValue = waitKey(100);
        if(keyValue==27){//如果使用者按ese鍵退出播放
            break;
        }
    }
}

 

三、影象演示