Lucas-Kanande金字塔光流實現Harris角點追蹤

附上一篇關于金字塔理論的詳細講解,想要深入了解金字塔追蹤原理的可以研究一下,這篇博文則相對簡單易懂些。http://blog.csdn.net/app_12062011/article/details/51880258

首先,我們應該明白什么是角點追蹤。
角點追蹤,即視頻中的特征點隨著幀的改變而移動,在不斷播放的視頻中,查找一個移動的物體軌跡的過程。


接下來,我們可以根據不斷的分析而自動代入金字塔的光流檢測。
既然我們的目的是追蹤動態的角點,那么首先應該有已確定的特征點,這時,可以使用自動設置參數進行特征點的查找,查找后調節一下精度,就可以以備使用了。我們還可以使用鼠標點擊的方式來確定特征點,這里需要補充一個函數:
onMouse函數,函數的定義方式為:

static void onMouse( int event, int x, int y, int /*flags*/, void* /*param*/ )

其中,event代表點擊事件,x,y則代表點擊時鼠標指針的所在位置,引用方式為

namedWindow( "Demo", 1 );
setMouseCallback( "Demo", onMouse, 0 );

表示在某個窗口發生的點擊事件,點擊事件調用的函數,以及傳給回調函數的參數,參數默認值為0。

當我們已經在第一張圖片中固定到了特征點,那么接下來要做的就是在接下來的每幀圖片中找到 該特征點的位置了。

在尋找特征值的位置的時候,則需要正式引入Lucas-Kanande金字塔的相關知識了。

首先,為了簡化追蹤的難度,LK光流法提出了以下三條假設:

(1)亮度恒定,就是同一點隨著時間的變化,其亮度不會發生改變。該假設用于得到光流法基本方程;
(2)小運動,這個也必須滿足,就是時間的變化不會引起位置的劇烈變化,這樣灰度才能對位置求偏導。
(3)空間一致,一個場景上鄰近的點投影到圖像上也是鄰近點,且鄰近點速度一致。這是Lucas-Kanade光流法特有的假定,因為光流法基本方程約束只有一個,而要求x,y方向的速度,有兩個未知變量。我們假定特征點鄰域內做相似運動,就可以連立n多個方程求取x,y方向的速度(n為特征點鄰域總點數,包括該特征點)。

為了正確追蹤到角點,我們可以嘗試在原圖中角點的附近設置一個鄰域,由于是小運動,那么兩幀圖片中的特征點位置變化也不會太大,我們只需要在鄰域中尋找符合原角點的亮度以及周圍環境信息的角點,確定其為運動后的角點即可。

Lucas-Kanade是一種廣泛使用的光流估計的差分方法,這個方法是由Bruce D. Lucas和Takeo Kanade發明的。它假設光流在像素點的鄰域是一個常數,然后使用最小二乘法對鄰域中的所有像素點求解基本的光流方程。

但是這是在假設完全成立的時候才正確的,一旦物體運動過快,那么會造成很大的誤差,而且如果盲目擴大鄰域,算法的時間又是一個很大的問題,所以引出了金字塔分層,針對仿射變換的改進Lucas-Kanade算法。

算法的關鍵內容在于:當在鄰域內尋找對應特征點的時候,首先將原圖像進行縮小,縮小后的圖像相對的特征點的位移像素點就也會變小,可以先在縮小后的圖像中尋找匹配的特征值,尋找出位置之后再進行迭代,直到迭代到原圖像上的位置信息。


如圖所示,最底層是分辨率最大的原圖像,向上依次是采樣1/2的低分辨率圖像,通常在使用當中金字塔的層數為3-4層即可。因為隨著圖像的移動,算法可以應對光流大于窗口尺寸的特征點跟蹤問題。
金字塔L-K光流通常用來估計圖像特征點的光流,以提高圖像光流場的計算速度。

因此,使用Lucas-Kanande金字塔的步驟為:
首先,計算金字塔最頂層圖像的光流,根據最頂層光流結果計算其次上層的光流初始值,再進一步估算其光流的精確值。最后,用計算的次上層光流結果估計下一層光流的初始值,計算其精確的值后再繼續帶入下一層計算,直到金字塔的最底層。

公式的推演可以查看:http://blog.csdn.net/zhe123zhe123zhe123/article/details/50397143

具體的實現步驟可以看代碼:

#include "opencv2/video/tracking.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <iostream>
#include <ctype.h>

using namespace cv;
using namespace std;



//--------------------------------【help( )函數】----------------------------------------------
//      描述:輸出幫助信息
//-------------------------------------------------------------------------------------------------
static void help()
{
    //輸出歡迎信息和OpenCV版本
    cout << "\n\n\t\t\t   當前使用的OpenCV版本為:" << CV_VERSION 
        <<"\n\n  ----------------------------------------------------------------------------" ;
    cout    << "\n\n\t該Demo演示了 Lukas-Kanade基于光流的lkdemo\n";
    cout << "\n\t程序默認從攝像頭讀入視頻,可以按需改為從視頻文件讀入圖像\n";
    cout << "\n\t操作說明: \n"
        "\t\t通過點擊在圖像中添加/刪除特征點\n" 
        "\t\tESC - 退出程序\n"
        "\t\tr -自動進行追蹤\n"
        "\t\tc - 刪除所有點\n"
        "\t\tn - 開/光-夜晚模式\n"<< endl;
}

Point2f point;
bool addRemovePt = false;

//--------------------------------【onMouse( )回調函數】------------------------------------
//      描述:鼠標操作回調
//-------------------------------------------------------------------------------------------------
static void onMouse( int event, int x, int y, int /*flags*/, void* /*param*/ )
{
    if( event == CV_EVENT_LBUTTONDOWN )
    {
        point = Point2f((float)x, (float)y);
        addRemovePt = true;
    }
}

//-----------------------------------【main( )函數】--------------------------------------------
//      描述:控制臺應用程序的入口函數,我們的程序從這里開始
//-------------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
    help();

    VideoCapture cap;
    TermCriteria termcrit(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 20, 0.03);
    Size subPixWinSize(10,10), winSize(31,31);

    const int MAX_COUNT = 500;
    bool needToInit = false;
    bool nightMode = false;

    cap.open(0);

    if( !cap.isOpened() )
    {
        cout << "Could not initialize capturing...\n";
        return 0;
    }

    namedWindow( "LK Demo", 1 );
    setMouseCallback( "LK Demo", onMouse, 0 );

    Mat gray, prevGray, image;
    
    //數組為2*n,每個元素存放的都是一個點信息
    vector<Point2f> points[2];
        Mat frame;
    cap >> frame;

    for(;;)
    {
        cap >> frame;
        if( frame.empty() )
            break;

        frame.copyTo(image);
        cvtColor(image, gray, COLOR_BGR2GRAY);

        if( nightMode )
            image = Scalar::all(0);

        //如果需要自動尋找角點
        if( needToInit )
        {
            // 自動初始化
            //尋找角點,將角點信息載入到points[1]數組中
            //goodFeaturesToTrack函數的參數為:
            //(原圖像,角點存放位置,需要找到的最大角點數目,品質因子,可刪除角點范圍,感興趣區域,計算協方差矩陣時的窗口大小,是否使用Harris角點檢測,Harris角點檢測K值)
            goodFeaturesToTrack(gray, points[1], MAX_COUNT, 0.01, 10, Mat(), 3, 0, 0.04);
            //將能夠將角點位置精確到亞像素級精度。
            // 輸入的源角點信息points[1],精度調整后角點信息依舊存放在points[1]中
            cornerSubPix(gray, points[1], subPixWinSize, Size(-1,-1), termcrit);
            addRemovePt = false;
        }
        else if( !points[0].empty() )
        {
            vector<uchar> status;
            vector<float> err;
            if(prevGray.empty())
                gray.copyTo(prevGray);
            //光流金字塔,作用在于根據第一幀圖片的特征點所在位置找到另一幀圖片的特征點信息
            //參數設置:
            //1.第一幀圖像
            //2.第二幀圖像(需要在這幅圖像上找到特征點改變后的位置)
            //3.第一幀圖像中的特征點
            //4.輸出在第二幀圖像中找到了對應特征點的位置
            //5.輸出特征向量,每個特征點是否找到了改變后的位置,如果找到了,每個元素設置為1,沒有找到設置為0
            //6.輸出出錯信息,誤差測量的類型在第10個參數中定義,如果沒有錯誤信息,則err為no defined
            //7.在每個金字塔水平搜尋窗口的尺寸。
            //8.最大的金字塔0層數;如果設置為0,不使用金字塔(單級),如果設置為1,使用兩層,等等;如果金字塔是通過輸入算法將使用多層次的金字塔有但不超過 maxlevel。
            //9.參數指定的迭代搜索算法終止準則
            //10.參數:optflow_use_initial_flow 使用初始估計,   
            //optflow_lk_get_min_eigenvals 使用最小特征值的誤差測量估計
            //11.一個矩陣光流方程的特征值如果在窗口中小于該值,則忽略不計
            calcOpticalFlowPyrLK(prevGray, gray, points[0], points[1], status, err, winSize,
                3, termcrit, 0, 0.001);
            size_t i, k;
            for( i = k = 0; i < points[1].size(); i++ )
            {
                if( addRemovePt )
                {
                    //兩個像素點之間的距離
                    if( norm(point - points[1][i]) <= 5 )
                    {
                        addRemovePt = false;
                        continue;
                    }
                }

                if( !status[i] )
                    continue;

                points[1][k++] = points[1][i];
                circle( image, points[1][i], 3, Scalar(0,255,0), -1, 8);
            }
            points[1].resize(k);
        }

        if( addRemovePt && points[1].size() < (size_t)MAX_COUNT )
        {
            vector<Point2f> tmp;
            tmp.push_back(point);
            //輸入信息temp中,精度調整后角點信息存放在tmp中
            cornerSubPix( gray, tmp, winSize, cvSize(-1,-1), termcrit);
            points[1].push_back(tmp[0]);
            addRemovePt = false;
        }

        needToInit = false;
        imshow("LK Demo", image);

        char c = (char)waitKey(10);
        if( c == 27 )
            break;
        switch( c )
        {
        case 'r':
            //自動尋找角點
            needToInit = true;
            break;
        case 'c':
            points[0].clear();
            points[1].clear();
            break;
        case 'n':
            nightMode = !nightMode;
            break;
        }

        //交換兩個vector的所有元素
        std::swap(points[1], points[0]);
        cv::swap(prevGray, gray);
    }

    return 0;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容