一、什么是光流?
在前幾篇文章中,我們介紹了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即可得到新一幀中更新后的特征點位置。效果如下圖所示。

每兩張圖片間相差10幀。由于相機的移動,越來越多的特征點移動到了視野外,因此能夠跟蹤到的特征點越來越少。另外,我們也發(fā)現(xiàn)誤跟蹤到了一些不該出現(xiàn)特征點的位置,這說明LK光流法的準確度并不高,特別是特征不明顯的點,由于各個方向的亮度變化都很相似,容易導致跟蹤錯誤。
LK光流法的結果可以用于相機位姿估計,它與直接對特征點做匹配的效果是一致的。
測試程序的完整代碼地址:https://github.com/jingedawang/LKFlow
三、參考資料
《視覺SLAM十四講》第8講 視覺里程計2 高翔
光流Optical Flow介紹與OpenCV實現(xiàn) zouxy09