目录:
  • LK光流介绍
  • 单层LK光流
  • 多层LK光流
LK光流

  LK光流是一种描述图像运动的方法,利用LK光流可以实现对图像的追踪,从而求解图像运动的位姿。其基本思想如下:

  img1,img2分别为两张已知的图像,相机在运动过程中,img1中的特征点P(u,v)P(u,v)P(u,v)经过变换后得到了img2中的P′(u+Δx,v+Δy)P^\prime(u+\Delta x,v+\Delta y)P′(u+Δx,v+Δy)。

  LK光流法求解相机位姿的基本问题可以描述为:已知PPP点,求解最佳的Δx,Δy\Delta x,\Delta yΔx,Δy,从而得到img2中的P′P^\primeP′坐标。因此便求得一对匹配点P−P′P-P^\primeP−P′。在求得多对匹配点的坐标后,整个位姿估计问题可以转换为ICPICPICP或PnPPnPPnP问题。

  为了求解最佳的Δx,Δy\Delta x,\Delta yΔx,Δy,通常采用最小二乘法。在LK光流中有两条非常重要的假设:

1.灰度不变假设,即I(u,v)=I(u+Δx,v+Δy)I(u,v)=I(u+\Delta x,v+\Delta y)I(u,v)=I(u+Δx,v+Δy)
2.小范围灰度不变假设,即存在W×WW×WW×W的窗口,使I(u+w,v+w)=I(u+Δx+w,v+Δy+w)I(u+w,v+w)=I(u+\Delta x+w,v+\Delta y+w)I(u+w,v+w)=I(u+Δx+w,v+Δy+w)

  假设1是基础,假设2则是为了构建最小二乘解。

单层光流法

求解的基本流程如下:

  • 特征点的选取
  • Gauss-Newton法求解Δx,Δy\Delta x,\Delta yΔx,Δy

1.特征点的选取
  关于特征点的选取,笔者前面写了一篇文章进行了详细介绍,这里不在赘述。详情可点击此处.

vector<KeyPoint> kp1;vector<KeyPoint> kp2_single;Ptr<GFTTDetector> detector = cv::GFTTDetector::create(500, 0.01, 20);detector->detect(img1, kp1);

  这里使用的是GFFT特征。

2.Gauss-Newton求解Δx,Δy\Delta x,\Delta yΔx,Δy

  根据假设1有:error=I1(u,v)−I2(u+Δx,v+Δy)error=I_1(u,v)-I_2(u+\Delta x,v+\Delta y)error=I1​(u,v)−I2​(u+Δx,v+Δy),这是基于假设1所得到了,理论上error应该为0,由于噪声的存在,实际上不可能为0,因此只能求解最小二乘解。

  根据假设2有Cost=∑j=1w∑i=1wI1(u+w,v+w)−I2(u+Δx+w,v+Δy+w)Cost=\sum_{j=1}^{w}\sum_{i=1}^{w}I_1(u+w_,v+w)-I_2(u+\Delta x+w,v+\Delta y+w)Cost=∑j=1w​∑i=1w​I1​(u+w,​v+w)−I2​(u+Δx+w,v+Δy+w)。

  导数JJJ是img2的像素梯度,也可以用img1的像素梯度代替,此时为反向光流法。具体形式可参考《视觉SLAM十四讲》。

代码如下:

void OpticalFlowTracker::calculateOpticalFlow(const Range &range) { //参数是一个cv::range的引用// parameters                                      range是一个区间,应该看作一个窗口//第一步:初始化工作:定义全局变量int iteration = 15;  //迭代次数int max_patch_size = 4;  //w的大小for (int i = range.start; i < range.end; i++)  //初始化变量工作{auto kp = kp1[i];double dx = 0, dy = 0;Eigen::Matrix2d H = Eigen::Matrix2d::Zero();Eigen::Vector2d b = Eigen::Vector2d::Zero();Eigen::Vector2d J;double cost = 0;double lastcost = 0;bool succ = true;if (has_initial)   //金字塔的核心{dx = kp2[i].pt.x - kp.pt.x;dy = kp2[i].pt.y - kp.pt.y;  //这个值的加减顺序不能变,} //初始化或者不初始化都可以//第二部:计算e,cost,h,b,jfor (int iter = 0; iter < iteration; iter++){if (inverse == false) {H = Eigen::Matrix2d::Zero();b = Eigen::Vector2d::Zero();}else {// only reset bb = Eigen::Vector2d::Zero();}cost = 0;// 两个for循环为w窗口的迭代for (int x = -max_patch_size; x < max_patch_size; x++){for (int y = -max_patch_size; y < max_patch_size; y++){double error = GetPixelValue(img1, kp.pt.x + x, kp.pt.y + y) -GetPixelValue(img2, kp.pt.x + x + dx, kp.pt.y + y + dy);;if (inverse == false)   //inverse是反向的意思{J = -1.0 * Eigen::Vector2d(  //正向梯度,img20.5*(GetPixelValue(img2, kp.pt.x + x + dx + 1, kp.pt.y + y + dy) -GetPixelValue(img2, kp.pt.x + x + dx - 1, kp.pt.y + y + dy)),0.5*(GetPixelValue(img2, kp.pt.x + x + dx, kp.pt.y + y + dy + 1) -GetPixelValue(img2, kp.pt.x + dx + x, kp.pt.y + dy + y - 1)));}else if (iter == 0){J = -1.0 * Eigen::Vector2d(  //反向梯度,img10.5*(GetPixelValue(img1, kp.pt.x + x + dx + 1, kp.pt.y + y + dy) -GetPixelValue(img1, kp.pt.x + x + dx - 1, kp.pt.y + y + dy)),0.5*(GetPixelValue(img1, kp.pt.x + x + dx, kp.pt.y + y + dy + 1) -GetPixelValue(img1, kp.pt.x + dx + x, kp.pt.y + dy + y - 1)));}cost += error * error;b += -J * error;if (iter == 0 || inverse == false){H += J * J.transpose();}}}//第三部分:第i次迭代完毕,开始求解/Eigen::Vector2d update = H.ldlt().solve(b);//求解后不要急于迭代,先看看结果是否正确if (std::isnan(update[0])) {// sometimes occurred when we have a black or white patch and H is irreversiblecout << "update is nan" << endl;//判断是不是为无穷大,succ = false;break;}if (iter > 0 && cost > lastcost) {  //判断损失函数的增长方向break;}lastcost = cost;dx += update(0, 0);dy += update(1, 0);succ = true;//这一句放在这可以保证每次迭代都不出问题if (update.norm() < 1e-2) {// convergebreak;}}success[i] = succ;//把特征点放到kp2里面  注意:keypoint.pt返回的是Point2f而不是2d;kp2[i].pt = kp.pt + Point2f(dx, dy);  //把结果返回}
}
多层光流法

  多层光流法的基本思想是在单层的基础上,多次运用单层光流法,同时保存每层的求解结果并作为下一层的初始值。这样可以逐层求解,得到比较实用的光流法。其中,最关键的工具应该是金字塔。

  图像金字塔其实是在原图像的基础上,对图像进行缩放,当然,各参数也要进行相应的缩放。一般顶层图像最粗糙,底层为原图,求解过程由上至下,求解结果逐步精确。多层LK光流法的流程如下:

  • 定义金字塔:一般是定义容器
  • 创建金字塔:一般是利用循环以及resize函数,给容器赋值
  • 求解:逐层调用单层的求解算法,但要注意各层之间的结果是如何传递的。

代码如下:

void OpticalFlowMultiLevel(const Mat &img1,const Mat &img2,const vector<KeyPoint> &kp1,vector<KeyPoint> &kp2,vector<bool> &success,bool inverse) {//定义全局参数int pyramid_size = 4;double pyramid_scal[] = { 1.0,0.5,0.25,0.125 };double scal = 0.5;//创建金字塔:第0层为底层,第3层为顶层vector<Mat> img1_pyramid, img2_pyramid;for (int i = 0; i < pyramid_size; i++){if (i == 0) //第0层为原图{img1_pyramid.push_back(img1);img2_pyramid.push_back(img2);}else{Mat pyramid1, pyramid2; //利用好resize函数resize(img1_pyramid[i - 1], pyramid1,cv::Size(img1_pyramid[i - 1].cols*scal, img1_pyramid[i - 1].rows*scal));img1_pyramid.push_back(pyramid1);resize(img2_pyramid[i - 1], pyramid2,cv::Size(img2_pyramid[i - 1].cols*scal, img2_pyramid[i - 1].rows*scal));img2_pyramid.push_back(pyramid2); //图像金字塔仅仅作为导数的计算用}}//同理,坐标也要创建为金字塔vector<KeyPoint> kp1_pyramid, kp2_pyramid;for (auto kp_top : kp1){kp_top.pt *= pyramid_scal[pyramid_size - 1];kp1_pyramid.push_back(kp_top);kp2_pyramid.push_back(kp_top);}//开始迭代了for (int i = pyramid_size - 1; i >= 0; i--){success.clear();OpticalFlowSingleLevel(img1_pyramid[i], img2_pyramid[i], kp1_pyramid, kp2_pyramid, success, inverse, true);if (i > 0){for (auto &kp : kp1_pyramid) {kp.pt /= scal;}for (auto &kp : kp2_pyramid) {kp.pt /= scal;}}}//迭代完毕后的kp2是最需要的for (auto &kp_end : kp2_pyramid) { kp2.push_back(kp_end); };
}

注意:
1.坐标的修正
if (i > 0)
{
for (auto &kp : kp1_pyramid) {
kp.pt /= scal;
}
for (auto &kp : kp2_pyramid) {
kp.pt /= scal;
}

  这里是利用参数的引用间接地修改了关键点的坐标,其实kp1_pyramid是没有变化的,只是对原坐标缩放,关键是kp2_pyramid,kp2_pyramid是求解得到的特征点。

  从顶层到底层,与kp2_pyramidkp1_pyramid的匹配程度越来越高,在每层求解后均需要进行缩放,以便于下一层使用。

2.求解结果的传递

OpticalFlowSingleLevel(img1_pyramid[i], img2_pyramid[i], kp1_pyramid, kp2_pyramid, success, inverse, true);

  认真理解 true 这个参数,我们翻到单层求解的算法中,true对应的参数是has_initial,相应的功能是改变dx,dy的初始化方式,前面说过,LK光流法的关键是求解dx,dy。而多层LK光流是步骤得到好的dx,dy初始值。

当has_initial=true时:
  dx = kp2[i].pt.x - kp.pt.x;
  dy = kp2[i].pt.y - kp.pt.y;

  kp2是上一层求解的结果,经过坐标缩放后,用于初始化dx,dy,这也说明了上层求解得到的kp2是如何在下一层被利用的。

当has_initial=false时:
  dx=0,dy=0。此时为单层求解

  因此,金字塔的核心是关于被优化变量的初始化方式。思想是:每层初始化越接近真实值越好

参考文献:
[1]《视觉SLAM十四讲》高翔

视觉SLAM前端——LK光流法相关推荐

  1. SLAM - 视觉里程计 - LK光流法

    CMakeLists.txt: cmake_minimum_required( VERSION 2.8 ) project( 项目名 )set( CMAKE_BUILD_TYPE Release ) ...

  2. OpenCV Using Python——基于SURF特征提取和金字塔LK光流法的单目视觉三维重建 (光流、场景流)...

    https://blog.csdn.net/shadow_guo/article/details/44312691 基于SURF特征提取和金字塔LK光流法的单目视觉三维重建 1. 单目视觉三维重建问题 ...

  3. OpenCV3学习(11.2)LK光流法原理及opencv实现

    光流的概念:(Optical flow or optic flow) 它是一种运动模式,这种运动模式指的是一个物体.表面.边缘在一个视角下由一个观察者(比如眼睛.摄像头等)和背景之间形成的明显移动.光 ...

  4. LK光流法与反向LK光流法

    文章目录 一.基本概念 二.2D中的LK光流法 1.空间点在图像中的灰度表示 2.2D中的LK光流法推导 3.将2D光流法抽象成超定方程问题 4.超定线性方程的最小二乘最优解定理证明 5.将2D光流法 ...

  5. 图像金字塔LK光流法原理分析

    图像金字塔LK光流法原理分析 1.LK光流法原理分析 2.基于图像金字塔的LK光流法原理分析 本篇博客只讲述原理,c++代码实现请参考博客< 基于金字塔LK的光流法实现-根据论文自己实现的c++ ...

  6. OpenCV:金字塔LK光流法

    金字塔LK光流法的三个假设 亮度恒定,即图像场景中目标的像素在帧间运动时外观上保持不变: 时间连续或者运动是"小运动",即图像的运动随时间的变化比较缓慢: 空间一致,即一个场景中同 ...

  7. 详解LK光流法(含金字塔多层光流),反向光流法(附代码)

    LK光流法可用来跟踪特征点的位置. 比如在img1中的特征点,由于相机或物体的运动,在img2中来到了不同的位置.后面会称img1为Template(T),img2为I. 光流法有个假设: 灰度不变假 ...

  8. 【老生谈算法】matlab实现金字塔LK光流法源码——金字塔LK光流法

    基于金字塔LK光流法的MATLAB代码 1.原文下载: 本算法原文如下,有需要的朋友可以点击进行下载 序号 原文(点击下载) 本项目原文 [老生谈算法]基于金字塔LK光流法的MATLAB代码.docx ...

  9. 视觉SLAM前端特征检测与跟踪的思考

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 就目前视觉SLAM的引用来区分,分为基于特征法的和直接法的视觉SL ...

最新文章

  1. 「Python」一文读懂装饰器
  2. 反射型 DDoS 攻击的原理和防范措施
  3. Python爬虫应用实战-网站数据爬取及数据分析
  4. 获取程序所有加载的dll名称
  5. 【X264系列】之命令参数解析
  6. 转!面向对象设计原则
  7. php rewrite重写,yaf 自定义重写路由rewrite
  8. java 回文素数_java实现回文质数
  9. 数学建模matlab视频教程,matlab编程教程_求matlab视频教程,主要用于数学建模方面的...
  10. devexpress,dotnetbar控件
  11. \u5168\u56fd\u7f8e\u5bb9\u5927\u592b数据采集数据(\u82b1\u5bb9\u7f51 huaroo 公开数据),爬虫120例之26例
  12. 这是啥SQL,室友看了人傻了
  13. 'rm' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
  14. 鲸探APP处罚60余位转售数字藏品用户 | 产业区块链发展周报
  15. Aspose.Cells使用教程:使用 .NET 在 Linux 上创建或编辑 Excel 文件
  16. 【探花交友】用户登录总结
  17. 大数据未来发展趋势,主要取决于这八个要素
  18. 清除电脑各种使用记录不留痕迹,保护你的隐私!
  19. Python数据分析案例14——文本计算TF-IDF值和LDA主题模型
  20. Linux Shell 通配符、元字符、转义符使用实例介绍--Learning the korn shell

热门文章

  1. 商圈热点事件:极智嘉拟科创板上市、小鹅通D轮融资……
  2. Python数据分析-笔记01
  3. JVM内存模型(摘抄至五月的仓颉的博客)
  4. 计算机-IEEE ACCESS-论文投稿上岸经验分享
  5. 我的世界模组-minecraft fpv
  6. 安全帽识别与火焰识别系统功能应用
  7. 家庭媒体中心解决方案(四、 群晖系列nas基本功能使用指南篇2)
  8. 法雷奥:进军L4市场,带来比航空标准更加安全的自动驾驶车 | CES 2019
  9. 计算机专业退休有退休金,那些专业有退休金 哪个行业的退休金最多?
  10. p50, p90, p99 (pct 50, pct 90, pct 99)指什么?