点击上方“计算机视觉life”,选择“星标”
快速获得最新干货
作者李迎松授权发布,武汉大学 摄影测量与遥感专业 博士
https://ethanli.blog.csdn.net/article/details/105065660
详解立体匹配系列经典SGM: (1) 框架与类设计详解立体匹配系列经典SGM: (2) 代价计算 代码已同步于Github开源项目:https://github.com/ethan-li-coding/SemiGlobalMatching
上一篇博客代价计算中,我们介绍了初始代价计算的代码,并做了实验来验证初始代价的计算结果,效果是显而易见的糟糕,但是这个结果有它独特的意义,一方面它告诉大家只算初始代价值很难得到好的视差结果,提醒大家聚合对SGM匹配来说是非常重要的;另一方面,初始代价为聚合代价提供一个初值,如果初值错的离谱(离谱的意思是几乎所有像素都无法算出正确视差,图中可以隐约看到有很多像素还是能算出大概正确的视差的,能担任合格的初值),也会让聚合结果大打折扣。
让我们再次近距离感受下它!
好了,闲话不多说,我们来揭开神秘的代价聚合步骤的面纱!
视差主序
看过前篇的同学们肯定已经接触了视差主序的概念,我再次重申一遍是因为它很重要,关系到我如何存取代价数组的值。
我们谈到主序,大家会想起二维数组中数据的存储方式,如果是行主序,则数据优先在行内按顺序紧密排列,即第0行第0列和第0行第1列是相邻的元素;如果是列主序,则数据优先在列内按顺序紧密排列,即第0行第0列和第1行第0列是相邻的元素。主序类型,决定了通过行列号计算元素在数组中相对首地址的位置偏移量的方式,也决定了数组采用哪种遍历顺序会更高效(缓存原理)。
代价数组有三个维度:行、列、视差,视差主序的意思是同一个像素点各视差下的代价值紧密排列,即代价数组元素的排列顺序为:
(0,0)像素的所有视差对应的代价值;
(0,1)像素的所有视差对应的代价值;
…
…
(0,w-1)像素的所有视差对应的代价值;
(1,0)像素的所有视差对应的代价值;
(1,1)像素的所有视差对应的代价值;
…
…
第(h-1,w-1)个像素的所有视差对应的代价值;
这样排列的好处是:单个像素的代价值都挨在一起,聚合时可以达到很高的存取效率。这对于大尺寸影像来说可带来明显的效率优势,对于像CUDA这类存储效率至关重要的平台来说就有明显优势。
视差主序下,(i,j,d)( i, j, d )" role="presentation">(i,j,d)(i,j,d) ( i, j, d )(i,j,d)位置的代价值由如下方式获得(cost为代价数组):
cost[i * width * disp_range + j*disp_range + d]
大家思考下为何是这样获取,理解下视差主序的含义。
介绍完主序方式,就可以开始摄入正餐了!
左右路径聚合
其实所有路径的聚合可以放到一个循环体中实现,但是为了更清晰,我们把为每一条路径的聚合都单独实现,最后把所有路径的聚合值相加,就得到最终多路径的聚合值。这样还可以方便的选择任意路径的组合,来测试实验效果。
我另外开辟了8个聚合代价数组
// ↘ ↓ ↙ 5 3 7
// → ← 1 2
// ↗ ↑ ↖ 8 4 6
/** \brief 聚合匹配代价-方向1 */
uint8* cost_aggr_1_;
/** \brief 聚合匹配代价-方向2 */
uint8* cost_aggr_2_;
/** \brief 聚合匹配代价-方向3 */
uint8* cost_aggr_3_;
/** \brief 聚合匹配代价-方向4 */
uint8* cost_aggr_4_;
/** \brief 聚合匹配代价-方向5 */
uint8* cost_aggr_5_;
/** \brief 聚合匹配代价-方向6 */
uint8* cost_aggr_6_;
/** \brief 聚合匹配代价-方向7 */
uint8* cost_aggr_7_;
/** \brief 聚合匹配代价-方向8 */
uint8* cost_aggr_8_;
而左右路径,就是在同一行内从左到右执行聚合,上面的1-2方向,如图:
我们再来看代价聚合公式:
像素p沿着某条路径r的路径代价计算公式
公式中p代表像素,r代表路径,左右路径的情形下,p−rp-r" role="presentation">p−r就是pp" role="presentation">p左侧(从左到右聚合)或者右侧(从右到左聚合)的相邻像素,他们行号相等,列号相差1。LL" role="presentation&