一、什么是光流?
在前几篇文章中,我们介绍了2D-2D、3D-2D、3D-3D等相机位姿估计方法,它们都是在特征点匹配的基础上进行的。当我们回过头再重新考虑特征点匹配方法时,可能会注意到一个问题,即使是最快的ORB特征,每帧图片的计算也需要近20ms,对于30ms/帧运行的SLAM系统来说,一大半时间都花在了计算特征点上。
因此,我们也许会考虑能否避免特征点提取操作?
光流法(Optical Flow)就是一种避免频繁计算特征点的方法。另外还有“直接法(Direct Method),将在后续文章中介绍。
所谓“光流”,从字面上理解就是光的流动。其实在日常生活中,人眼观察到的物体的运动就是光流。因为物体反射光进入人的眼睛,当物体移动后,相应的反射光也会移动,从而使人意识到物体在运动。在计算机中,我们就可以利用像素亮度的变化追踪光线移动的方向,从而确定物体运动方向,进而得到相机运动方向。
LK光流是光流法的一种,它对观测量做了“灰度不变”假设和“某个窗口内的像素具有相同的运动”假设。因而能够从前后两幅图片中追踪到同一个点的位置移动。
二、使用OpenCV中的LK光流
在实际应用中,LK光流的作用就是跟踪特征点。与对每一帧提取特征点相比,使用LK光流只需要提取一次特征点,后续视频帧只需要跟踪就可以了,节约了许多特征提取时间。
int main( int argc, char** argv )
{
if ( argc != 2 )
{
cout<<"usage: useLK path_to_dataset"< 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 kps;
cv::Ptr 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 next_keypoints;
vector prev_keypoints;
for ( auto kp:keypoints )
prev_keypoints.push_back(kp);
vector status;
vector 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 time_used = chrono::duration_cast>( t2-t1 );
cout<<"LK Flow use time:"<
代码中,先对第一帧图像提取FAST特征点,后续帧使用LK跟踪这些特征点。调用cv::calcOpticalFlowPyrLK
即可得到新一帧中更新后的特征点位置。效果如下图所示。
每两张图片间相差10帧。由于相机的移动,越来越多的特征点移动到了视野外,因此能够跟踪到的特征点越来越少。另外,我们也发现误跟踪到了一些不该出现特征点的位置,这说明LK光流法的准确度并不高,特别是特征不明显的点,由于各个方向的亮度变化都很相似,容易导致跟踪错误。
LK光流法的结果可以用于相机位姿估计,它与直接对特征点做匹配的效果是一致的。
测试程序的完整代码地址:https://github.com/jingedawang/LKFlow
三、参考资料
《视觉SLAM十四讲》第8讲 视觉里程计2 高翔
光流Optical Flow介绍与OpenCV实现 zouxy09