视觉SLAM(六)后续作业

第六章作业

作者: 曾是少年

二 LK光流

2.1 光流文献综述 (1 分)

我们课上演示了 Lucas-Kanade 稀疏光流,用 OpenCV 函数实现了光流法追踪特征点。实际上,光流法有很长时间的研究历史,直到现在人们还在尝试用 Deep learning 等方法对光流进行改进 [1, 2]。本题将指导你完成基于 Gauss-Newton 的金字塔光流法。首先,请阅读文献 [3](paper 目录下提供了 pdf),回答下列问题。
问题:

1. 按此文的分类,光流法可分为哪几类?

:作者在文中对光流法按照两种不同的方法进行分类。

原文中如下:

	One difference between the various approaches is whether they estimate an additive increment to the parameters (the additive approach (Lucas and Kanade, 1981)), or whether they estimate an incremental warp that is then composed with the current estimate of the warp (the compositional approach (Shum and Szeliski, 2000)).

原文第一章最后一段有如下:

	We categorize algorithms as either additive or compositional, and as either forwards or inverse.
  1. 可以看出,此文按照估计的是参数的叠加增量还是增量Warp将光流法分为叠加(additional)和组合(compositional)算法
  2. 按照Warp更新规则可以将光流法分为前向forward)和逆向(inverse)两种算法

2. 在 compositional 中,为什么有时候需要做原始图像的 warp?该warp 有何物理意义?

: 与Lucas-Kanade 算法中那样简单地将迭代的更新 (Delta p) 添加到当前估计的参数 (p) 不同,组合(compositional)算法中,对扭曲(W(x;Delta p))的增量更新必须由Warp的当前估计组成 (W(x;p))

需要在当前位姿估计之前引入增量式 warp(incremental warp)以建立半群约束要求(the semi-group requirement)

原文片段:

For more complex warps the composition of the two warps can be more involved (Baker and Matthews, 2001).We therefore have two requirements on the set of warps: (1) the set of warps must contain the identity warp and (2) the set of warps must be closed under composition. The set ofwarps must therefore form a semi-group. This requirement is not very strong. Most warps used in computer vision, including homographies and 3D rotations (Shum and Szeliski, 2000), naturally form semi-groups.

Forward Compositionalwarp 集合包含 identity warp, warp 集 合 包 含 在Composition 操作上是半群(semi-group), 其中包括 Homograph, 3D rotation 等. Inverse Compositional 也是 semi-group, 另外要求增量 warp 可逆, 其中包括 Homograph,3D rotation 等, 但不包括 piece wise affine.

3. forward 和 inverse 有何差别?

: 原文如下:

The only differences between the forwards and inverse compositional algorithms (see Figs. 3 and 4) are: (1) the error image is computed after switching the roles of I and T , (2) Steps 3, 5, and 6 use the gradient of T rather than the gradient of I and can be precomputed, (3) Eq. (35) is used to computep rather than Eq. (10), and finally (4) the incremental warp is inverted before it is composed with the current estimate in Step 9.

向前方法对于输入图像进行参数化(包括仿射变换及放射增量). 反向方法则同时输入图像参数和模板图像, 其中输入图像参数化仿射变换, 模板图像参数化仿射增量. 因此反向方法的计算量显著降低,计算效率较高.。由于图像灰度值和运动参数非线性, 整个优化过程为非线性。forward方法和inverse方法在目标函数上不太一样,一个是把运动向量 p 都是跟着被匹配图像,但是向前方法中的迭代的微小量 Δp 使用 I 计算,反向方法中的 Δp 使用 T计算,这样计算量便小了。

2.2 forward-addtive Gauss-Newton 光流的实现

接下来我们来实现最简单的光流,即上文所说的 forward-addtive。我们先考虑单层图像的 LK 光流,然后再推广至金字塔图像。按照教材的习惯,我们把光流法建模成一个非线性优化问题,再使用 Gauss-Newton 法迭代求解。设有图像 1.png2.png,我们在 1.png 中提取了 GFTT 角点 [4] (1 这是一种角点提取算法,没有描述子。经常与光流配合使用,但是计算量比 FAST 更大.),然后希望在 2.png中追踪这些关键点。设两个图分别为 (I_i), (I_2),第一张图中提取的点集为 (P = {p_i}),其中 (p_i = [x_i, y_i]^T)为像素坐标值。考虑第 i 个点,我们希望计算 (∆xi, ∆yi),满足:

[min_{∆x_i,∆y_i}sum_W||I_1 (x_i,y_i) − I_2 (x_i + ∆x_i, y_i + ∆y_i)||^2_2 ]

即最小化二者灰度差的平方,其中 (sum_W)表示我们在某个窗口(Window)中求和(而不是单个像素,因为问题有两个未知量,单个像素只有一个约束,是欠定的)。实践中,取此 window8×8 大小的小块,即从 (x_i − 4) 取到 (x_i + 3)y 坐标亦然。显然,这是一个 forward-addtive 的光流,而上述最小二乘问题可以用 Gauss-Newton 迭代求解。请回答下列问题,并根据你的回答,实现 code/optical_flow.cpp 文件中的OpticalFlowSingleLevel 函数。


1. 从最小二乘角度来看,每个像素的误差怎么定义?

答: 第(x,y)的像素的误差定义如下:

[egin{aligned} e & = I_1 (x,y) − I_2 (x + ∆x, y + ∆y)\ end{aligned} ]

其中, (I_1(x,y))是像素点((x,y))在图1中的灰度, (I_2(x+Delta x,y+Delta y))是在图二对应像素点的灰度

2. 误差相对于自变量的导数如何定义?

答:

[frac{partial e}{partial Delta x}=-(frac{partial I_2(x + ∆x, y + ∆y)}{partial Delta x}) ]

[frac{partial e}{partial Delta y}=-(frac{partial I_2(x + ∆x, y + ∆y)}{partial Delta y}) ]

实践中的计算过程如下:

  1. 计算雅克比矩阵
Eigen::Vector2d J;  // Jacobian
J(0) = -0.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));
 J(1) = -0.5*(GetPixelValue(img2,kp.pt.x + x + dx,kp.pt.y + y+dy+1) - GetPixelValue(img2,kp.pt.x + x + dx,kp.pt.y + y+dy-1));
  1. 计算误差
 error = -double(GetPixelValue(img2,kp.pt.x + x + dx,kp.pt.y + y+dy) - GetPixelValue(img1,kp.pt.x + x ,kp.pt.y + y));
  1. 计算H,b,cost
H +=  J*J.transpose();
b += -J*error;
cost += error*error;
  1. 计算更新
Eigen::Vector2d update;
update = H.ldlt().solve(b);
// 更新dx,dy
dx += update[0];
dy += update[1];

下面是有关实现过程中的一些提示

  1. 同上一次作业,你仍然需要去除那些提在图像边界附近的点,不然你的图像块可能越过边界。
  2. 该函数称为单层的光流,下面我们要基于这个函数来实现多层的光流。在主函数中,我们对两张图像分别测试单层光流、多层光流,并与 OpenCV 结果进行对比。作为验证,正向单层光流结果应该如图 1 所示,它结果不是很好,但大部分还是对的。
  3. 在光流中,关键点的坐标值通常是浮点数,但图像数据都是以整数作为下标的。之前我们直接取了浮点数的整数部分,即把小数部分归零。但是在光流中,通常的优化值都在几个像素内变化,所以我们还用浮点数的像素插值。函数 GetPixelValue 为你提供了一个双线性插值方法(这也是常用的图像插值法之一),你可以用它来获得浮点的像素值。

正向法运行结果如下:

image-20200627232530453

感悟:

  • 使用函数 GetPixelValue进行插值很重要; 我一开始没有使用插值代码如下:

    J(0) = -0.5 * (img2.at<uchar>(kp.pt.x+dx+x+1, kp.pt.y+dy+y)   - img2.at<uchar>(kp.pt.x+dx+x-1, kp.pt.y+dy+y));
    J(1) = -0.5 * (img2.at<uchar>(kp.pt.x+dx+x,   kp.pt.y+dy+y+1) - img2.at<uchar>(kp.pt.x+dx+x,   kp.pt.y+dy+y-1));
    error = -double(img2.at<uchar>(kp.pt.x + x + dx , kp.pt.y + y+dy) - img1.at<uchar>(kp.pt.x + x, kp.pt.y + y));
    

    运行结果如下:

    T_SingleLevel

    可以看出,不进行插值,效果不会好;

2.3 反向法(1 分)

在你实现了上述算法之后,就会发现,在迭代开始时,Gauss-Newton 的计算依赖于 (I_2)((x_i+∆x_i, y_i+∆y_i)) 处的梯度信息。然而,角点提取算法仅保证了 (I_1(x_i, y_i)) 处是角点(可以认为角度点存在明显梯度),但对于 (I_2),我们并没有办法假设 (I_2)(x_i, y_i) 处亦有梯度,从而 (Gauss-Newton) 并不一定成立。反向的光流法(inverse)则做了一个巧妙的技巧,即用 (I_1(x_i, y_i)) 处的梯度,替换掉原本要计算的 (I_2(x_i +∆x_i, y_i +∆y_i))
的梯度。这样做的好处有:

(I_1(x_i, y_i)) 是角点,梯度总有意义;
(I_1(x_i, y_i)) 处的梯度不随迭代改变,所以只需计算一次,就可以在后续的迭代中一直使用,节省了大量计算时间。

我们为 OpticalFlowSingleLevel 函数添加一个 bool inverse 参数,指定要使用正常的算法还是反向的算法。请你根据上述说明,完成反向的 LK 光流法。


答:

[error = I_2(x,y)-I_1(x-dx,y-dy) ]

[frac{partial e}{partial dx } = frac{partial I_1(x-dx)}{partial x} ]

代码修改如下(主要是雅克比矩阵计算过程):

J(0) = -0.5*(GetPixelValue(img1,kp.pt.x + x + 1 , kp.pt.y + y) - GetPixelValue(img1,kp.pt.x + x - 1 , kp.pt.y + y));
J(1) = -0.5*(GetPixelValue(img1,kp.pt.x + x , kp.pt.y + y + 1) - GetPixelValue(img1,kp.pt.x + x , kp.pt.y + y - 1));

使用反向法运行结果如下:

T_SingleLevel

可以看出,效果不错;

2.4 推广至金字塔(2 分)

通过实验,可以看出光流法通常只能估计几个像素内的误差。如果初始估计不够好,或者图像运动太大,光流法就无法得到有效的估计(不像特征点匹配那样)。但是,使用图像金字塔,可以让光流对图像运动不那么敏感。下面请你使用缩放倍率为 2,共四层的图像金字塔,实现 coarse-to-fineLK 光流。函数在 OpticalFlowMultiLevel 中。

答:

要点:

  1. 使用resize函数进行图像缩放;

代码修改过程如下:

  1. 构建图像缩放的金字塔
// 构建金字塔
vector<Mat> pyr1, pyr2; // image pyramids 图像金字塔
// TODO START YOUR CODE HERE (~8 lines)
for (int i = 0; i < pyramids; i++) {
    if(i==0)
    {
        pyr1.push_back(img1);
        pyr2.push_back(img2);
    }
    else
    {
        Mat dst1,dst2;
        cv::resize(img1, dst1, Size(),scales[i],scales[i]);
        cv::resize(img2, dst2, Size(),scales[i],scales[i]);
        pyr1.push_back(dst1);
        pyr2.push_back(dst2);
        /* code */
    }
    }
  1. 把所有的特征点经过缩放之后放入特征点集合中
vector<KeyPoint> kp1_pyr;
vector<KeyPoint> kp2_pyr;
for(auto &kp:kp1)
{
    auto kp_top=kp;
    kp_top.pt *= scales[pyramids-1];
    kp1_pyr.push_back(kp_top);
    kp2_pyr.push_back(kp_top);
}
  1. 从金字塔上层到下层提取特征点
for(int level = pyramids-1;level>=0;level--)
{
    success.clear();
    cout<<pyr1.size()<<' '<<level<<endl;
    OpticalFlowSingleLevel(pyr1[level], pyr2[level], kp1_pyr, kp2_pyr, success,true);
    if(level>0)
    {
        for(auto &kp:kp1_pyr)
        {
            kp.pt /= pyramid_scale;
        }
        for(auto &kp:kp2_pyr)
        {
            kp.pt /= pyramid_scale;
        }
    }  
}
  1. 把提取到的特征都添加到kp2中.
for(auto &kp:kp2_pyr)
{
    kp2.push_back(kp);
}

金字塔正向运行结果如下所示:

T_MultiLevel

金字塔反向运行结果如下:

T_MultiLevel

OpenCV原版结果如下所示:

T_opencv

可以看出使用金字塔的效果比单层的要好.

实现完成后,给出你的光流截图(正向、反向、金字塔正向、金字塔反向),可以和 OpenCV 作比较。
然后回答下列问题:

1. 所谓 coarse-to-fine 是指怎样的过程?

:对一个图像按倍率进行缩放,得到不同分辨率的图像. 构成一个图像金字塔. 在计算光流时,从顶层分辨率较低的图像开始计算,然后把上一层的追踪结果,作为下一层的初始值. 依此规律进行迭代,该过程即是由粗到精(Coarse-to-fine)的光流.

该方法的优点在于,当原始图像的像素运动较大时,在金字塔顶层的图像看来,运动仍然在一个小范围内

因此,coarse-to-fine是说从最粗糙的顶层金字塔开始向下迭代,不断细化估计的过程

2. 光流法中的金字塔用途和特征点法中的金字塔有何差别?

: 光流法中的金字塔是逐层迭代寻找最佳的关键点位置和光流方向,目的是解决光流在运动过程中难以检测的问题,或者说图像运动过快导致跟踪不上的问题。本质是通过下采样后的图像进行层层细化

特征点法中的金字塔是通过逐层检测特征点来增加尺度描述,有时候图像从远处看是一个特征点,但是近看可能不是,所以特征点法用金字塔目的是解决特征点的尺度不变性问题.

提示:你可以使用上面写的单层光流来帮助你实现多层光流。

因此,特征点法的金字塔主要用于不同层级之间的匹配,以使得匹配对缩放不敏感。光流中金字塔 主要用于 coarse-to-fine的估计

2.5 讨论

现在你已经自己实现了光流,看到了基于金字塔的 LK光流能够与OpenCV 达到相似的效果(甚至更好)。根据光流的结果,你可以和上讲一样,计算对极几何来估计相机运动。下面针对本次实验结果,谈谈你对下面问题的看法:

1. 我们优化两个图像块的灰度之差真的合理吗?哪些时候不够合理?你有解决办法吗?

: 灰度不变假设不满足时即不合理。可以考虑去掉均值(Zero-mean),加入曝光时间等做法。

光流法拥有三个基本的假设
a. 灰度不变假设,即同一个空间点的像素灰度值在各个图像中固定不变,看似不太合理,但是当帧率输出较快,两张图像非常接近时,这种假设也能吻合;因此光流法易受外界光照的影响, 针对与灰度变化的情况,可以对相机进行光度模型标定来解决这个问题
b. 小运动假设, 即光流法追踪特征要求运动不能太大, 当相机发生大尺度或旋转时容易产生局部极值,跟踪效果不好,通常的解决办法可以用金字塔的方法改善局部极值,用组合光流法(增加旋转描述)可以改善旋转问题。;
c. 局部一致性假设。

2. 图像块大小是否有明显差异?取 16x168x8 的图像块会让结果发生变化吗?

: 差异不大,用大图像块时比较耗时,太小时结果不稳定。

当采用金字塔时,窗口固定,将图像生成金字塔过程中,在每一层金字塔上都用同一个大小的窗口来进行光流计算,这样很好的去解决了图像块的问题,这样一来图像块大小并不会带来明显差异。

当不使用金字塔方法时。当窗口较大时,光流计算更鲁棒,当窗口较小时,光流计算更正确。原因在于,当图像中每一个部分的运动都不一致的时候如果开的窗口过大,很容易违背窗口(邻域)内的所有点光流一致的基本假设,这可能与实际不一致,所以窗口小,包含的像素少,更精确些。

使用单层直接法进行测试 :

  • half_patch_size=4

image-20200627184150029

  • half_patch_size=8

image-20200627161426971

  • half_patch_size =16

image-20200627184103906

3. 金字塔层数对结果有怎样的影响?缩放倍率呢?

: 差別均不大,2倍缩放倍率比较容易计算。层数太少时结果不稳定

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

三 直接法

3.1 单层直接法(3 分)

我们说直接法是光流的直观拓展。在光流中,我们估计的是每个像素的平移(在 additive 的情况下)。
而在直接法当中,我们最小化光流误差,来估计相机的旋转和平移(以李代数的形式)。现在我们将使用和前一个习题非常相似的做法来实现直接法,请同学体现二者之间的紧密联系。本习题中,你将使用 Kitti 数据集中的一些图像。给定 left.pngdisparity.png,我们知道,通过这两个图可以得到 left.png 中任意一点的 3D 信息。现在,请你使用直接法,估计图像 000001.png000005.png 的相机位姿。我们称 left.png 为参考图像(reference,简称 ref),称 000001.png -000005.png 中任意一图为当前图像(current,简称 cur),如图2 所示。设待估计的目标为 Tcur,ref,那么在 ref 中取一组点 ({p_i}), 位姿可以通过最小化下面的目标函数求解:

[T_{cur,ref} =frac{1}{N}sum^N_{i=1}sum_{Wi} ||I_{ref}(π(p_i)) − I_{cur}(π (T_{cur,ref }p_i))||^2_2 ]

其中 N 为点数,π 函数为针孔相机的投影函数

[R^3→ R^2 ]

(W_i) 为第 i 个点周围的小窗口。同光流法,该问题可由 Gauss-Newton 函数求解。请回答下列问题,然后实现 code/direct_method.cpp 中的 DirectPoseEs?timationSingleLayer 函数。

1. 该问题中的误差项是什么?

[egin{aligned} error &= I_{ref}(p_1)-I_{cur}(p_2)\ end{aligned} ]

其中p1,p2 为非其次像素坐标,ξ为R,t对应的李代数

[egin{aligned} p_1 &= left[ egin{array}{ccc} u\vend{array} ight]_1 = Dfrac{1}{Z_1}KP end{aligned} ]

[egin{aligned} p_2 &= left[ egin{array}{ccc} u\vend{array} ight]_2 = Dfrac{1}{Z_2}(KP+t) = Dfrac{1}{Z_2}Kexp(xi^{wedge})P end{aligned} ]

2. 误差相对于自变量的雅可比维度是多少?如何求解?

答: 本题中自变量是位姿,唯独时6维的,光度变化是1维的, 自变量T是6维的. 雅克比矩阵时1*6维的.求解过程如下: 给李代数添加扰动:

[egin{aligned} e(xi oplus delta xi ) &= I_1(frac{1}{Z_1}DKP)-I_2(frac{1}{Z_2}DKexp(xi^{wedge})P+u) \ & approx I_1(frac{1}{Z_1}DKP)-I_2(frac{1}{Z_2}DKexp(xi^{wedge})) - frac{partial (I_{2}{(u)})}{partial u} frac{partial u}{partial q} frac{partial q}{partial delta xi} delta xi \ end{aligned} ]

(q=TP),(u=frac{1}{Z_2}Kq),有

[egin{aligned} frac{partial e}{partial delta xi} &= frac{partial (-I_{cur}{(u)})}{partial u} frac{partial u}{partial q} frac{partial q}{partial delta xi} \& = -frac{partial I_2}{partial u} frac{partial u}{partial delta xi} \ &= -frac{partial I_2}{partial u} left[ egin{array}{ccc} frac{f_x}{Z} & 0 & -frac{f_x X}{Z^2}\ 0 & frac{f_y}{Z} & -frac{f_y Y}{Z^2} end{array} ight] left[ egin{array}{ccc} I & -q^{wedge} end{array} ight] \ &= -frac{partial I_2}{partial u} left[ egin{array}{ccc} frac{f_x}{Z} & 0 & -frac{f_xX}{Z^2} & -frac{f_xXY}{Z^2} & f_x+frac{fxX^2}{Z^2} & -frac{f_xY}{Z}\ 0 & frac{f_y}{Z} & -frac{f_yY}{Z^2} & -f_y-frac{fyY^2}{Z^2} & frac{f_yXY}{Z^2} & frac{f_yX}{Z}\ end{array} ight] end{aligned} ]

3. 窗口可以取多大?是否可以取单个点?

: 窗口大小的选择和误差以及计算量有关系。窗口不能取太大,随着窗口的增大,两帧之间的关键点的计算量将会呈指数增长;窗口可以取单点。但是这样会导致像素梯度计算误差增大. 单个像素在两张图片里灰度不变的假设太过强烈,使用多个像素有助于提供鲁棒性。


下面是一些实现过程中的提示:

  1. 这次我们在参考图像中随机取 1000 个点,而不是取角点。请思考为何不取角点,直接法也能工作。
  2. 由于相机运动,参考图像中的点可能在投影之后,跑到后续图像的外部。所以最后的目标函数要对投影在内部的点求平均误差,而不是对所有点求平均。程序中我们以 good 标记出投影在内部的点。
  3. 单层直接法的效果不会很好,但是你可以查看每次迭代的目标函数都会下降。

窗口的大小与计算量以及计算精度相关,窗口越大,计算量将会呈指数增长,同时同一个窗口内计算像素梯度,但是采用了相同的深度信息,当像素块越大,像素块内的深度信息误差也就越大,可以计算单个像素的差异,这个差异是由灰度直接相减得到的,但是单个像素没有什么区分性,周围很可能有好多像素和亮度会差不多,所以我们有时会使用小的图像块儿,并且使用更复杂的差异度量方式,旅游规划相关性等。

3.2 多层直接法(2 分)

下面,类似于光流,我们也可以把直接法以 coarse-to-fine 的过程,拓展至多层金字塔。多层金字塔的直接法允许图像在发生较大运动时仍能追踪到所有点。下面我们使用缩放倍率为 2 的四层金字塔,实现金字塔上的直接法。请实现DirectPoseEstimationMultiLayer 函数,下面是一些提示:

  1. 在缩放图像时,图像内参也需要跟着变化。那么,例如图像缩小一倍,(f_x, f_y, c_x, c_y) 应该如何变化?
  2. 根据 coarse-to-fine 的过程,上一层图像的位姿估计结果可以作为下一层图像的初始条件。
  3. 在调试期间,可以画出每个点在 ref 和 cur 上的投影,看看它们是否对应。若准确对应,则说明位姿估计是准确的。
    作为验证,图像 000001 和 000005 的位姿平移部分应该接近2:
    (t1 = [0.005876, −0.01024, −0.725]^T)
    (t5 = [0.0394, −0.0592, −3.9907]^T)
    可以看出车辆基本是笔直向前开的。根据各人实现不同,此题结果可能有所差异,但不应该大于 0.5 米。

结果:

image-20200627113535943

多层光流结果如下:

序号 平移
t1 (-0.00183501,0.00266685,-0.725144)
t2 (0.00740888,-0.0013171,-1.4707)
t3 (0.00708387,0.00377631,-2.20888)
t4 (0.00832254,0.00281999,-2.9961)
t5 (0.0189283,-0.0101808,-3.7931)

可以看出最后的误差在半米之内.

在第五张图片上的金字塔如下所示

image-20200627194724447

image-20200627194752775

image-20200627194917715

image-20200627194825295

3.3 * 延伸讨论

现在你已经实现了金字塔上的 Gauss-Newton 直接法。你可以调整实验当中的一些参数,例如图像点
数、每个点周围小块的大小等等。请思考下面问题:

1. 直接法是否可以类似光流,提出 inverse, compositional 的概念?它们有意义吗?

:直接法通过求解相机运动来计算第二帧图像对应关键点的位置,如果提出 inverse, compositional 的概念,第二帧图像的信息将会失效,没有意义。

2. 请思考上面算法哪些地方可以缓存或加速?

:加窗口部分,采用合适大小的窗口会使计算加速。图像梯度可以提前计算好。

3. 在上述过程中,我们实际假设了哪两个 patch 不变?

:1. 灰度不变假设;2. 同窗口内深度不变假设。

4. 为何可以随机取点?而不用取角点或线上的点?那些不是角点的地方,投影算对了吗?

:直接法对图像中的灰度值优化,最小化的是目标像素块的是光度误差,不是角点也没有关系。而特征点法提取的特征点是为了能够进行图像匹配,而在图像中选择具有代表性的区域,

5. 请总结直接法相对于特征点法的异同与优缺点。

:直接法最大的贡献在于,以更整体、更优雅的方式处理了数据关联问题。特征点法需要依赖重复性较强的特征提取器,以及正确的特征匹配,才能得正确地计算相机运动。而直接法,则并不要求一一对应的匹配,只要先前的点在当前图像当中具有合理的投影残差,我们就认为这次投影是成功的。而成功与否,主要取决于我们对地图点深度以及相机位姿的判断,并不在于图像局部看起来是什么样子。

优势:

  1. 相比特征点法(只使用了特征点周围的信息),直接法使用了图像中全部的信息(半直接法只用了边缘梯度);
  2. 节省特征提取与匹配的大量时间,易于移植到嵌入式系统中,以及与IMU进行融合;
  3. 使用的是像素梯度而不必是角点,可以在特征缺失的场合使用,如环境中存在许多重复纹理或是缺乏角点,但出现许多边缘或光线变量不明显区域;
  4. 可以进行稠密或半稠密的地图重建;

劣势:

  1. 一般对相机要求较高,需要全局快门相机,进行光度标定等
  2. 灰度不变是一个强假设,难以满足(易受曝光和模糊影像);
  3. 单像素区没有区分度,需要计算图像块或是相关性;
  4. 直接法成功的前提,是目标函数从初始值到最优值之间一直是下降的,然而图像非凸。因此需要有一个相当不错的初始估计,还需要一个质量较好的图像;
  5. 难以实现地图复用、回环检测、丢失后的重定位等:除非存储所有的关键帧图像,否则很难利用先前建好的地图;即使有办法存储所有关键帧的图像,那么在重用地图时,我们还需要对位姿有一个比较准确的初始估计——这通常是困难的。

image-20200711192332081

附加题 四 * 使用光流计算视差

请注意本题为附加题。
在上一题中我们已经实现了金字塔 LK 光流。光流有很多用途,它给出了两个图像中点的对应关系,所以我们可以用光流进行位姿估计,或者计算双目的视差。回忆第四节课的习题中,我们介绍了双目可以通过视差图得出点云,但那时直接给出了视差图,而没有进行视差图的计算。现在,给定图像 left.png, right.png,请你使用上题的结果,计算 left.png 中的 GFTT 点在 right.png 中的(水平)视差,然后与 disparity.png 进行比较。这样的结果是一个稀疏角点组成的点云。请计算每个角点的水平视差,然后对比视差图比较结果。
本程序不提供代码框架,请你根据之前习题完成此内容,


答:

视差计算代码如下,(前提:需要把上题的直接法函数代码稍作修改,把追踪到的像素返回):

//视差计算
vector<double > disp;
vector<double > cost;
for(int i = 0 ;i<pixels_ref.size();i++)
{
    double dis =  pixels_ref[i].x()-pixel_right[i].x(); //两点之间的水平视差
    disp.push_back(dis);
    //        cout<<"dis: "<<dis<<" "<<GetPixelValue(disparity_img,pixels_ref[i].x(),pixels_ref[i].y())<<endl;
    cost.push_back(dis-GetPixelValue(disparity_img,pixels_ref[i].x(),pixels_ref[i].y()));
}
//计算视差的平均误差
double sum = accumulate(cost.begin(),cost.end(),0.0);
cout<<"平均误差:"<<sum/cost.size()<<endl;

使用单层直接法最后得到的平均误差为-7.02735:

image-20200627105959829

使用多层直接法最后的平均误差为:平均误差:0.223348

image-20200627105911354

原文地址:https://www.cnblogs.com/guoben/p/13285134.html