视觉SLAM理论与实践6-1

本文深入探讨了视觉SLAM理论与实践中的LK光流算法,涵盖基础介绍、文献综述、Gauss-Newton光流实现、金字塔方法应用及算法讨论。对比了加法与复合算法,正向与反向方法,并详细解析了forward-addtive Gauss-Newton光流的实现过程。同时,介绍了金字塔方法如何解决图像运动过快导致的跟踪难题。

一、LK光流基础介绍

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、LK光流文献综述

在这里插入图片描述

  1. LK 算法可以分为加法算法(additive)和复合算法(compositional),正向算法(forwards)以及反向算法(inverse)。
  2. 因为在Lucas-Kanade算法中,需要首先对图像I进行warp,计算出来矫正图像的像素I(W(x,p)),并与模板图像的像素值作差。其差值就是后面迭代优化的目标函数了。Warp的物理意义是,将模板T坐标系下的像素x映射到图像I的亚像素坐标系下。
  3. forward方法对于输入图像进行参数化(包括仿射变换和仿射增量). inverse方法除了将输入图像参数化还会将模板图像参数化, 其中输入图像参数化仿射变换, 模板图像参数化仿射增量. 因此inverse方法的计算量显著降低。 此外这两种方法的目标函数不太一样,fowerd方法中迭代的微小量是使用矫正到模板图像坐标系下的图像I(W(x;p))计算的,而inverse方法是使用模板图像T计算的。因此计算雅克比矩阵的时候,一个的微分在x处,而另外一个在0处。所以如果使用雅克比矩阵计算Hessian矩阵,后者计算的结果是固定的。

三、forward-addtive Gauss-Newton 光流的实现

在这里插入图片描述

  1. 从最小二乘的角度,每个像素的误差为:
    在这里插入图片描述

  2. 误差相对于自变量的导数为:
    在这里插入图片描述

  3. 单层正向反向光流实现·
    代码片段如下:

void OpticalFlowSingleLevel(
const Mat &img1,
const Mat &img2,
const vector<KeyPoint> &kp1,
vector<KeyPoint> &kp2,
vector<bool> &success,
bool inverse
) {
// parameters
int half_patch_size = 4;
int iterations = 10;
bool have_initial = !kp2.empty();
for (size_t i = 0; i < kp1.size(); i++) {
auto kp = kp1[i];
double dx = 0, dy = 0; // dx,dy need to be estimated
if (have_initial) {
dx = kp2[i].pt.x - kp.pt.x;
dy = kp2[i].pt.y - kp.pt.y;
}
double cost = 0, lastCost = 0;
bool succ = true; // indicate if this point succeeded
// Gauss-Newton iterations
for (int iter = 0; iter < iterations; iter++) {
Eigen::Matrix2d H = Eigen::Matrix2d::Zero();
Eigen::Vector2d b = Eigen::Vector2d::Zero();
cost = 0;
if (kp.pt.x + dx <= half_patch_size || kp.pt.x + dx >= img1.cols -
half_patch_size ||
kp.pt.y + dy <= half_patch_size || kp.pt.y + dy >= img1.rows -
half_patch_size) { // go outside
succ = false;
break;
}
// compute cost and jacobian
for (int x = -half_patch_size; x < half_patch_size; x++)
for (int y = -half_patch_size; y < half_patch_size; y++) {
// TODO START YOUR CODE HERE (~8 lines)
double error = 0;
float u1 = float(kp.pt.x + x), v1 = float(kp.pt.y+y);
float u2 = float(u1+dx), v2 = float(v1+dy);
Eigen::Vector2d J; // Jacobian
if (inverse == false) {
// Forward Jacobian
J.x() = double(GetPixelValue(img2,u2+1,v2) -
GetPixelValue(img2,u2-1,v2))/2;
J.y() = double(GetPixelValue(img2,u2,v2+1) -
GetPixelValue(img2,u2,v2-1))/2;
error = double(GetPixelValue(img2,u2,v2) -
GetPixelValue(img1,u1,v1));
// END YOUR CODE HERE
} else {
// Inverse Jacobian
// NOTE this J does not change when dx, dy is updated, so we
can store it and only compute error
J.x() = double(GetPixelValue(img1,u1+1,v1) -
GetPixelValue(img1,u1-1,v1))/2;
J.y() = double(GetPixelValue(img1,u1,v1+1) -
GetPixelValue(img1,u1,v1-1))/2;
error = double(GetPixelValue(img2,u2,v2) -
GetPixelValue(img1,u1,v1));
}
// compute H, b and set cost;
H+= J*J.transpose();
b+= -J*error;
cost+= error*error;
// TODO END YOUR CODE HERE
}
// compute update
// TODO START YOUR CODE HERE (~1 lines)
Eigen::Vector2d update;
// TODO END YOUR CODE HERE
update = H.ldlt().solve(b);
if (isnan(update[0])) {
// sometimes occurred when we have a black or white patch and H is
irreversible
cout << "update is nan" << endl;
succ = false;
break;
}
if (iter > 0 && cost > lastCost) {
cout << "cost increased: " << cost << ", " << lastCost << endl;
break;
}
// update dx, dy
dx += update[0];
dy += update[1];
lastCost = cost;
succ = true;
}
success.push_back(succ);
// set kp2
if (have_initial) {
kp2[i].pt = kp.pt + Point2f(dx, dy);
} else {
KeyPoint tracked = kp;
tracked.pt += cv::Point2f(dx, dy);
kp2.push_back(tracked);
}
}
}

G-N法实现的单层光流与OpenCV实现的光流如下图所示:

  1. OpenCV实现
    在这里插入图片描述

  2. G-N算法实现
    在这里插入图片描述

四、推广至金字塔

在这里插入图片描述

  1. Coarse-to-fine指的是从粗到精迭代跟踪的过程,在光流法中是指从低分辨率的图像开始跟踪,然后向高分辨率的图像逐渐迭代跟踪,从金字塔顶层开始向底层逐渐跟踪,并且上一层得到的值作为下一层的初值。大概就是这个过程。

  2. 光流法中的金字塔是逐层迭代寻找最佳的关键点位置和光流方向,目的是解决光流在运动过程中难以检测的问题,或者说图像运动过快导致跟踪不上的问题。特征点法中的金字塔是通过逐层检测特征点来增加尺度描述,有时候图像从远处看是一个特征点,但是近看可能不是,所以特征点法用金字塔目的是解决特征点的尺度不变性问题。

多层金字塔实现代码片段:

void OpticalFlowMultiLevel(
const Mat &img1,
const Mat &img2,
const vector<KeyPoint> &kp1,
vector<KeyPoint> &kp2,
vector<bool> &success,
bool inverse) {
// parameters
int pyramids = 4;
double pyramid_scale = 0.5;
double scales[] = {1.0, 0.5, 0.25, 0.125};
// create pyramids
vector<Mat> pyr1, pyr2; // image pyramids
// TODO START YOUR CODE HERE (~8 lines)
for (int i = 0; i < pyramids; i++) {
Mat img1_temp, img2_temp;
resize(img1, img1_temp,Size(img1.cols*scales[i], img1.rows*scales[i]));
resize(img2, img2_temp,Size(img2.cols*scales[i], img2.rows*scales[i]));
pyr1.push_back(img1_temp);
pyr2.push_back(img2_temp);
cout<<"Pyramid"<<i<<"im1 size: "<<img1_temp.cols<<" "
<<img1_temp.rows<<endl;
}
// TODO END YOUR CODE HERE
// coarse-to-fine LK tracking in pyramids
// TODO START YOUR CODE HERE
vector<KeyPoint> vkp2_now;
vector<KeyPoint> vkp2_last;
vector<bool> vsucc;
for(int i = pyramids-1;i>=0;i--)
{
vector<KeyPoint> vkp1;
for(int j = 0; j<kp1.size();j++)
{
KeyPoint kp1_temp = kp1[j];
kp1_temp.pt *= scales[i];
vkp1.push_back(kp1_temp);
if(i<pyramids-1)
{
KeyPoint kp2_temp = vkp2_last[j];
kp2_temp.pt /= pyramid_scale;
vkp2_now.push_back(kp2_temp);
}
}
vsucc.clear();
OpticalFlowSingleLevel(pyr1[i], pyr2[i], vkp1, vkp2_now, vsucc,
inverse);
vkp2_last.clear();
vkp2_last.swap(vkp2_now);
cout<<"pyramid: "<<i<<"vkp2_last size: "<<vkp2_last.size()<<"vkp2_noe
size "<<vkp2_now.size()<<endl;
}
kp2 = vkp2_last;
success = vsucc;
// TODO END YOUR CODE HERE
// don't forget to set the results into kp2
}

五、讨论

  1. 我们优化两个图像块的灰度之差真的合理吗?哪些时候不够合理?你有解决办法吗?
    光流法有三个基本的假设:
    a. 灰度不变假设,即同一个空间点的像素灰度值在各个图像中固定不变,看似不太合理,但是当帧率输出较快,两张图像非常接近时,这种假设也凑合;
    b. 小运动假设;
    c. 局部一致性假设。
    其中,基于灰度不变假设容易受外界光照的影响。解决办法可以对相机进行光度模型标定相机发生大尺度或旋转时容易产生局部极值,跟踪效果不好,通常的解决办法可以用金字塔的方法改善局部极值,用组合光流法(增加旋转描述)可以改善旋转问题。

  2. 图像块大小是否有明显差异?取 16x16 和 8x8 的图像块会让结果发生变化吗?
    当采用了金字塔的方法时,窗口固定,将图像生成金字塔过程中,在每一层金字塔上都用同一个大小的窗口来进行光流计算,这样很好的去解决了图像块的问题,这样一来图像块大小并不会带来明显差异。当没有采用金字塔方法时。当窗口较大时,光流计算更鲁棒,当窗口较小时,光流计算更正确。原因在于,当图像中每一个部分的运动都不一致的时候如果开的窗口过大,很容易违背窗口(邻域)内的所有点光流一致的基本假设,这可能与实际不一致,所以窗口小,包含的像素少,更精确些。

  3. 金字塔层数对结果有怎样的影响?缩放倍率呢?
    金字塔层数一般越多效果越好,但是一般图像大于4~5层之后都变得太小,特征点像素太过紧密容易出现错误追踪。放大倍率的话,放大倍率小,金字塔的层数可以增加,迭代层数增多,效果可以变得更好。

[1] A. Dosovitskiy, P. Fischer, E. Ilg, P. Hausser, C. Hazirbas, V. Golkov, P. van der Smagt, D. Cremers,and T. Brox, “Flownet: Learning optical flow with convolutional networks,” in Proceedings of the IEEE International Conference on Computer Vision, pp. 2758–2766, 2015.
[2] E. Ilg, N. Mayer, T. Saikia, M. Keuper, A. Dosovitskiy, and T. Brox, “Flownet 2.0: Evolution of optical flow estimation with deep networks,” arXiv preprint arXiv:1612.01925, 2016.
[3] S. Baker and I. Matthews, “Lucas-kanade 20 years on: A unifying framework,” International journal of computer vision, vol. 56, no. 3, pp. 221–255, 2004.
[4] J. Shi and C. Tomasi, “Good features to track,” in Computer Vision and Pattern Recognition, 1994. Proceedings CVPR’94., 1994 IEEE Computer Society Conference on, pp. 593–600, IEEE, 1994.

友情提示:代码下载需要C币,请事先判断是否对您有帮助,谨慎下载哦!!!

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值