LK光流跟蹤

一、什么是光流?

在前幾篇文章中,我們介紹了2D-2D、3D-2D、3D-3D等相機位姿估計方法,它們都是在特征點匹配的基礎上進行的。當我們回過頭再重新考慮特征點匹配方法時,可能會注意到一個問題,即使是最快的ORB特征,每幀圖片的計算也需要近20ms,對于30ms/幀運行的SLAM系統(tǒng)來說,一大半時間都花在了計算特征點上。

因此,我們也許會考慮能否避免特征點提取操作?

光流法(Optical Flow)就是一種避免頻繁計算特征點的方法。另外還有“直接法(Direct Method),將在后續(xù)文章中介紹。

所謂“光流”,從字面上理解就是光的流動。其實在日常生活中,人眼觀察到的物體的運動就是光流。因為物體反射光進入人的眼睛,當物體移動后,相應的反射光也會移動,從而使人意識到物體在運動。在計算機中,我們就可以利用像素亮度的變化追蹤光線移動的方向,從而確定物體運動方向,進而得到相機運動方向。

LK光流是光流法的一種,它對觀測量做了“灰度不變”假設和“某個窗口內(nèi)的像素具有相同的運動”假設。因而能夠從前后兩幅圖片中追蹤到同一個點的位置移動。

二、使用OpenCV中的LK光流

在實際應用中,LK光流的作用就是跟蹤特征點。與對每一幀提取特征點相比,使用LK光流只需要提取一次特征點,后續(xù)視頻幀只需要跟蹤就可以了,節(jié)約了許多特征提取時間。

int main( int argc, char** argv )
{
    if ( argc != 2 )
    {
        cout<<"usage: useLK path_to_dataset"<<endl;
        return 1;
    }
    string path_to_dataset = argv[1];
    string associate_file = path_to_dataset + "/associate.txt";
    
    ifstream fin( associate_file );
    if ( !fin ) 
    {
        cerr<<"I cann't find associate.txt!"<<endl;
        return 1;
    }
    
    string rgb_file, depth_file, time_rgb, time_depth;
    list< cv::Point2f > keypoints;      // 因為要刪除跟蹤失敗的點,使用list
    cv::Mat color, depth, last_color;
    
    for ( int index=0; index<100; index++ )
    {
        fin>>time_rgb>>rgb_file>>time_depth>>depth_file;
        color = cv::imread( path_to_dataset+"/"+rgb_file );
        depth = cv::imread( path_to_dataset+"/"+depth_file, -1 );
        if (index == 0 )
        {
            // 對第一幀提取FAST特征點
            vector<cv::KeyPoint> kps;
            cv::Ptr<cv::FastFeatureDetector> detector = cv::FastFeatureDetector::create();
            detector->detect( color, kps );
            for ( auto kp:kps )
                keypoints.push_back( kp.pt );
            last_color = color;
            continue;
        }
        if ( color.data==nullptr || depth.data==nullptr )
            continue;
        // 對其他幀用LK跟蹤特征點
        vector<cv::Point2f> next_keypoints; 
        vector<cv::Point2f> prev_keypoints;
        for ( auto kp:keypoints )
            prev_keypoints.push_back(kp);
        vector<unsigned char> status;
        vector<float> error; 
        chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
        cv::calcOpticalFlowPyrLK( last_color, color, prev_keypoints, next_keypoints, status, error );
        chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
        chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>( t2-t1 );
        cout<<"LK Flow use time:"<<time_used.count()<<" seconds."<<endl;
        // 把跟丟的點刪掉
        int i=0; 
        for ( auto iter=keypoints.begin(); iter!=keypoints.end(); i++)
        {
            if ( status[i] == 0 )
            {
                iter = keypoints.erase(iter);
                continue;
            }
            *iter = next_keypoints[i];
            iter++;
        }
        cout<<"tracked keypoints: "<<keypoints.size()<<endl;
        if (keypoints.size() == 0)
        {
            cout<<"all keypoints are lost."<<endl;
            break; 
        }
        // 畫出 keypoints
        cv::Mat img_show = color.clone();
        for ( auto kp:keypoints )
            cv::circle(img_show, kp, 10, cv::Scalar(0, 240, 0), 1);
        cv::imshow("corners", img_show);
        cv::waitKey(0);
        last_color = color;
    }
    return 0;
}

代碼中,先對第一幀圖像提取FAST特征點,后續(xù)幀使用LK跟蹤這些特征點。調(diào)用cv::calcOpticalFlowPyrLK即可得到新一幀中更新后的特征點位置。效果如下圖所示。

Paste_Image.png

每兩張圖片間相差10幀。由于相機的移動,越來越多的特征點移動到了視野外,因此能夠跟蹤到的特征點越來越少。另外,我們也發(fā)現(xiàn)誤跟蹤到了一些不該出現(xiàn)特征點的位置,這說明LK光流法的準確度并不高,特別是特征不明顯的點,由于各個方向的亮度變化都很相似,容易導致跟蹤錯誤。

LK光流法的結果可以用于相機位姿估計,它與直接對特征點做匹配的效果是一致的。

測試程序的完整代碼地址:https://github.com/jingedawang/LKFlow

三、參考資料

《視覺SLAM十四講》第8講 視覺里程計2 高翔
光流Optical Flow介紹與OpenCV實現(xiàn) zouxy09

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容