嗨,各位读者朋友们好!今天我们接着讲DSO全家桶的第二讲——提取梯度点。
首先,我们会先介绍一下DSO中从图像读取到提取梯度点的整个流程,然后再对重点的部分——“提取梯度点”进行讲解,让大家对整个流程的逻辑有个较为清晰的认识。这样写的目的主要是让大家在后续自己实现代码的时候,可以有目标地写测试用例。
图1. 图像预处理到提取梯度点流程图
OK,我们开始吧!如图1所示,我们将简单阐述一下每一步的操作。
1. 根据输入的标定文件确定图像的原始尺寸和裁剪尺寸,并且甚至图像的路径。若有光度标定[1]文件,还需要设置光度标定参数;
2. 在imageRW_OpenCV.cpp[2]中采用cv::imread[3]函数读取指定序号的图像,并拷贝到指定数据类型MinimalImageB中;
3. 若事先进行了光度标定,DSO会对输入图像先进行光度校正;
4. 对原始图像进行去畸变[4],并对输出图像进行高斯滤波,目标应该是使得图像平滑一些;
5. 构建图像金字塔,并逐层逐像素计算梯度值以及梯度值的平方和;
6. 对图像金字塔第0层(即原始图像)提取梯度点;
7. 对于图像金字塔第1-5层,设置固定大小的网格,在每个网格中根据梯度值大小选择四类点;
8. 对步骤6-7选择的点进行初始化,包括坐标、深度值和阈值等。
上述步骤即是DSO从输入图像开始一直到完成提取梯度点的完整流程。但是我们这里并不会详细介绍每一个步骤,只挑比较重要的部分进行讲解,即上述的步骤6和步骤7。实际上,在自己实现代码的时候,可以借助OpenCV等工具完成图像读取、滤波和构建金字塔等操作,因此我们并不会花大篇幅介绍这些内容。
DSO前端:提取梯度点
DSO中对点的提取策略分为两类:
1. 原始图像层的梯度点提取,即图像金字塔的第0层:由小到大的比较每一个像素的梯度值与阈值,找出满足要求的点;
2. 图像金字塔第1-5层的梯度点选择:在每个固定大小的网格中,提取四类点。
接下来,我们对这两个步骤分别进行介绍。
图像金字塔第0层
首先我们需要先设置密度权重,因为整张图像的像素多到吓人,为了秉承DSO中的Sparse,DSO将第0层的密度权重设置为0.03,即需要提取的目标点数是0.03*width*height。
处理图像金字塔第0层的函数是PixelSelector::makeMaps(...),表示从无到有的过程。为了更加直观地显示整个过程,笔者引用了泡泡机器人SLAM龚益群同学发布在【泡泡读者来稿】的作品:DSO代码解读[5]中的一些图示进行介绍。
图2. 梯度直方图
1. 如图2所示,首先需要对每个32*32的网格创建梯度直方图,在最开始介绍系统流程时,我们提到过需要对图像计算梯度值以及梯度的平方和(dx2+dy2)。而在这里,就是利用梯度平方和统计直方图(梯度直方图的横轴为梯度值,纵轴为像素个数),统计该网格中同一梯度值的像素个数;
2. 在梯度直方图中找到处于中间(即中位数)的梯度值,作为阈值;
3. 在步骤2中我们计算了每个32*32的网格中位数的梯度值,对阈值矩阵进行3*3窗口的均值滤波,得到阈值矩阵;
图3. 图像金字塔第0层梯度点筛选策略
4. 如图3所示,DSO中采用了四级网格策略,其中蓝色网格是16*16,绿色网格是8*8,红色网格是4*4,最内层是1*1,并且彼此之间是包含关系;
5. 在提取梯度点的时候需要用到我们步骤3计算得到的阈值矩阵,通过由内到外比较每个点的梯度值与阈值的大小,确定提取到的点。这里有几个值得注意的地方,第一是阈值的选取:
假设阈值是th,
在1*1的窗口时,阈值被设置为2*th;
在4*4的窗口中,阈值被设置为th;
在8*8的窗口中,阈值被设置为0.75*th;
在16*16的窗口中,阈值被设置为0.75*0.75*th。
由于DSO中采取的策略是,先从最小的网格开始提取梯度点,在确保梯度值大于阈值的前提下找出网格中梯度值最大的点。若不满足阈值条件,则进入更大的网格进行筛选。而更大的网格都是从小的网格组合得到的,若是阈值不改变,仍然是无法提取到点的。因此,DSO才有这种渐变式的梯度阈值条件。
值得注意的是,DSO中的起始网格大小并不是图示的4*4,而是3*4。当且仅当第一次提取的点数量不满足要求时,才有可能改变为2*4或者4*4。DSO中采用0.25作为分界线:
当quotia = numWant / numHave > 1.25,表示提取到的点远小于期望值,需要减小窗口大小以获得更多的点,这时窗口会变成2*4;
当quotia = numWant / numHave < 0.25,表示提取到的点远大于期望值,需要增大窗口大小以获得更少的点,这时窗口会变成4*4。
图像金字塔第1-5层
不同于第0层,第1-5层的梯度点筛选是通过在固定网格中计算四类点的最大值来确定的。
图4. 图像金字塔第1-5层梯度点筛选策略
1. 首先需要确定的是,DSO预设的网格大小为5*5;
2. 如图4所示,emmmm....笔者纯粹只是为了避免去画图(偷懒),姑且这里就按4*4算好了。DSO会在每个4*4的网格中逐个像素地计算梯度值sqrt(dx2+dy2);
3. 整体的阈值条件被设置为th = 1 * 10 *0.75 = 7.5;
4. 后续我们需要对网格中的所有大于阈值th的像素挨个比较gradX / gradY / gradX - gradY / gradX + gradY的大小,选择该网格中四类条件中最大的四个点。
总结
至此,第二讲——提取梯度点,我们就已经讲完了。我们在开头介绍了DSO从输入流进来到完成提取梯度点的整个流程,可以帮助读者更快地建立起写一个测试用例应该要考虑的东西,同时也是直接给读者介绍了DSO在图像预处理这块儿做了哪些功夫。后面我们详细介绍了DSO是如何提取梯度点的,主要还是区分图像金字塔第0层和第1-5层的策略。后续笔者完成代码以后会在总结这边贴一些实际运行的结果图,先欠着哈。
再次感谢一些龚益群同学提供的PPT,感兴趣的同学可以详细阅读参考文献[5]。也非常欢迎各位同行向泡泡机器人SLAM投稿,我们有专门的泡泡读者来稿专栏平台供大家发挥,心动不如行动哇。
最后的最后,劳烦各位亲爱的读者朋友们给小弟我点个关注吧!谢谢撒!
PS:
哈喽,今天是2021-01-21,笔者来补一个源码实现:PixelSelector,供大家参考,若是大家对代码满意,麻烦给我点个star哈,谢谢。
参考文献
[1] 光度标定.
[2] imageRW_OpenCV.cpp.
[3] cv::imread.
[4] 去畸变.
[5] 【泡泡读者来稿】DSO代码解读.
版权声明
如需转载,请联系本人邮箱peichu.ye at mail2.gdut.edu.cn。未经允许,不可转载。