科学是一个精益求精的过程。

码上教学系列

【码上实战】【立体匹配系列】经典SGM:(1)框架与类设计
【码上实战】【立体匹配系列】经典SGM:(2)代价计算
【码上实战】【立体匹配系列】经典SGM:(3)代价聚合
【码上实战】【立体匹配系列】经典SGM:(4)代价聚合2
【码上实战】【立体匹配系列】经典SGM:(5)视差优化
【码上实战】【立体匹配系列】经典SGM:(6)视差填充
【码上实战】【立体匹配系列】经典SGM:(7)弱纹理优化

完整代码已发布于Github开源项目:Github/SemiGlobalMatching,欢迎免费下载

抱歉同学们,视差优化的完结篇被我安排成了一个系列篇,实在是SGM太过经典,每一点我都想拿出来单独成篇,作者在视差优化模块的确安排了很多子模块,每个模块都有其存在的意义。上一篇我们学习了怎么做一些常规的视差优化操作:一致性检查、唯一性约束、去除小连通区等。本篇我们来说一说视差填充。
我们再次整理下整个优化的子模块,让大家思路更清晰一些:

  • 1. 子像素拟合(Subpixel)
  • 2. 一致性检查(Left/Right Consistency Check)
  • 3. 唯一性约束(Uniqueness)
  • 4. 剔除小连通区(Remove Peaks)
  • 5. 中值滤波(Median Filter)
  • 6. 弱纹理区优化
  • 7. 填补空洞

本篇我们讲的是第7点。即填补空洞,也就是视差图填充。

先看看本篇最后的实验成果,让大家有个初探的兴趣:

视差填充前 视差填充后

上图一看,大家就明白What is 视差填充了,那我们就进主题吧。

视差填充,即给视差图的无效区域像素分配一个有效值。填充之前要问两个问题:

  • 1. 无效区是否一视同仁?
  • 2. 有效值哪里来?

本文前两个小节就是回答这两个问题。

【码上实战】【立体匹配系列】经典SGM:(6)视差填充

  • 遮挡区和误匹配区
  • 有区别填充
  • 实验

遮挡区和误匹配区

回答第一个问题,无效区是否一视同仁? 答案是NO!需要区分遮挡区和误匹配区。

遮挡区:由于前景遮挡而在左视图上可见但在右视图上不可见的像素区域。

误匹配区:位于非遮挡区域的错误匹配像素区域。

从定义可以看出,其实两者都是错误匹配像素区域,我们的主要目的是为了把遮挡区拿出来单独成一类,因为遮挡区比较特殊,它们位于视差非连续区域,一侧是前景,视差值较大,一侧是背景,视差值较小,它理应和背景像素视差更为接近,而和前景视差相差较大,所以填补时应该尽量选择周围背景像素的视差,避免选择前景像素。

误匹配区则不同,它们并不在遮挡区,邻域像素都位于一个连续的视差表面,视差是连续的,所以填补时可以考虑邻域内的所有像素。

总之,在填充前,我们要做个判断,哪些像素是遮挡区,哪些像素是误匹配区。

作者是通过如下方法来判断遮挡区的:

(1)像素ppp是通过各种优化操作而判定的无效像素。
(2)左影像像素ppp在右影像上的匹配像素为q=p−dq=p-dq=p−d,像素qqq在右视差图上的值为drd_rdr​,通过drdrdr找到左影像的匹配点p′p'p′,获取p′p'p′的视差d′d'd′,若d′>dd'>dd′>d,则ppp为遮挡区。

第二条有点绕,因为是两次对应,左找到右,右又反过来找左,换句话描述:假设qqq是ppp通过视差ddd找到的同名点,如果在左影像存在另外一个像素p′p'p′也和qqq是同名点而且它的视差比ddd要大,那么ppp就是遮挡区。

原文描述:A pixel p is occluded, if another pixel with higher disparity maps to the same pixel q in the match image1.

这个判定所基于的两个假设是:

(1)ppp的视差值和周围的背景像素视差值比较接近。
(2)ppp因为遮挡而在右影像上不可见,所以它会匹配到右影像上的前景像素,而前景像素的视差值必定比背景像素大,即比ppp的视差大。

有人问,既然像素ppp是无效像素,那么为啥还有视差值呢,答案就是在给ppp无效值之前判断,这一步可以在一致性检查步骤里完成,实际上遮挡区主要都是通过一致性检查来使其无效的,因为遮挡区存在明显的左右差异性(左可见,右不可见),所以一致性检查大概率会让这些区域的像素无效。我们在一致性检查让ppp的视差值无效之前,判断p是否是遮挡区和误匹配区。

遮挡区和误匹配区判断代码

// 判断两个视差值是否一致(差值在阈值内)
if (abs(disp - disp_r) > threshold) {// 区分遮挡区和误匹配区// 通过右影像视差算出在左影像的匹配像素,并获取视差disp_rl// if(disp_rl > disp) //       pixel in occlusions// else //       pixel in mismatchesconst sint32 col_rl = static_cast<sint32>(col_right + disp_r + 0.5);if(col_rl > 0 && col_rl < width){const auto& disp_l = disp_left_[i*width + col_rl];if(disp_l > disp) {occlusions.emplace_back(i, j);}else {mismatches.emplace_back(i, j);}}else{mismatches.emplace_back(i, j);}// 让视差值无效disp = Invalid_Float;
}

有区别填充

回答第二个问题:有效视差从哪里来?

我们已经把无效像素分为了遮挡区和误匹配区,填充的有效视差来源也要区别分析。

首先,两者的共同点是,有效视差都来自于周围有效像素的视差值,区别在于如何从周围的有效视差中选出最合适的一个。

对于遮挡区像素,因为它的身份是背景像素,所以它是不能选择周围的前景像素视差值的,应该选择周围背景像素的视差值。由于背景像素视差值比前景像素小,所以在收集周围的有效视差值后,应选择较小的几个,具体哪一个呢?SGM作者选择的是次最小视差

对于误匹配像素,它并不位于遮挡区,所以周围的像素都是可见的,而且没有遮挡导致的视差非连续的情况,它就像一个连续的表面凸起的一小块噪声,这时周围的视差值都是等价的,没有哪个应选哪个不应选,这时取中值就很适合。

文章中的公式是这样的:

另一个关键点是如何找到周围的有效像素,博主提供一种思路:以像素为中心,等角度往外发射8条射线,收集每条射线碰到的第一个有效像素,如图所示:

至此,可以编写视差填充代码:

void SemiGlobalMatching::FillHolesInDispMap()
{const sint32 width = width_;const sint32 height = height_;std::vector<float32> disp_collects;// 定义8个方向float32 pi = 3.1415926;float32 angle1[8] = { pi, 3 * pi / 4, pi / 2, pi / 4, 0, 7 * pi / 4, 3 * pi / 2, 5 * pi / 4 };float32 angle2[8] = { pi, 5 * pi / 4, 3 * pi / 2, 7 * pi / 4, 0, pi / 4, pi / 2, 3 * pi / 4 };float32 *angle = angle1;float32* disp_ptr = disp_left_;for (int k = 0; k < 3; k++) {// 第一次循环处理遮挡区,第二次循环处理误匹配区auto& trg_pixels = (k == 0) ? occlusions_ : mismatches_;std::vector<std::pair<int, int>> inv_pixels;if (k == 2) {//  第三次循环处理前两次没有处理干净的像素for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {if (disp_ptr[i * width + j] == Invalid_Float) {inv_pixels.emplace_back(i, j);}}}trg_pixels = inv_pixels;}// 遍历待处理像素for (auto& pix : trg_pixels) {int y = pix.first;int x = pix.second;if (y == height / 2) {angle = angle2;}// 收集8个方向上遇到的首个有效视差值disp_collects.clear();for (sint32 n = 0; n < 8; n++) {const float32 ang = angle[n];const float32 sina = sin(ang);const float32 cosa = cos(ang);for (sint32 n = 1; ; n++) {const sint32 yy = y + n * sina;const sint32 xx = x + n * cosa;if (yy<0 || yy >= height || xx<0 || xx >= width) {break;}auto& disp = *(disp_ptr + yy*width + xx);if (disp != Invalid_Float) {disp_collects.push_back(disp);break;}}}if(disp_collects.empty()) {continue;}std::sort(disp_collects.begin(), disp_collects.end());// 如果是遮挡区,则选择第二小的视差值// 如果是误匹配区,则选择中值if (k == 0) {if (disp_collects.size() > 1) {disp_ptr[y*width + x] = disp_collects[1];}else {disp_ptr[y*width + x] = disp_collects[0];}}else{disp_ptr[y*width + x] = disp_collects[disp_collects.size() / 2];}}}
}

实验

实验如前言所见,博主做了视差填充前后的对比,如图:

视差填充前 视差填充后

可以看到,填充后视差图更加完整,视差估计也更加稠密。

但不得不讨论的是,视差填充始终是不精确的,无论是取最小值还是取中值,它只能说是通过周围的有效值来预测,所以精确程度是有限的,换句话说,遮挡区像素都看不见,何以预测出十分精确的值?所以我们通常会根据应用需求来决定是否执行视差填充,如果实际要求每个点足够准确,而不太要求是否足够完整,那么就不需要做视差填充;而如果要求视差图足够完整,而对填充精度要求不高,则可以执行视差填充。

代码已同步于Github开源项目:Github/GemiGlobalMatching,大家可自行下载,点击项目右上角的star,有更新会实时通知到你的个人中心!

理论恒叨系列

【理论恒叨】【立体匹配系列】经典SGM:(1)匹配代价计算之互信息(MI)
【理论恒叨】【立体匹配系列】经典SGM:(2)匹配代价计算之Census变换
【理论恒叨】【立体匹配系列】经典SGM:(3)代价聚合(Cost Aggregation)
【理论恒叨】【立体匹配系列】经典SGM:(4)视差计算、视差优化

博主简介:
Ethan Li 李迎松
武汉大学 摄影测量与遥感专业博士

主方向立体匹配、三维重建

2019年获测绘科技进步一等奖(省部级)

爱三维,爱分享,爱开源
GitHub: https://github.com/ethan-li-coding
邮箱:ethan.li.whu@gmail.com

个人微信:

欢迎交流!

喜欢博主的文章不妨关注一下博主的博客,感谢!
博客主页:https://ethanli.blog.csdn.net/


  1. HIRSCHMÜLLER H. Hirschmüller, H.: Stereo processing by semiglobal matching and mutual information. IEEE PAMI 30(2), 328-341[J]. IEEE Transactions on Pattern Analysis & Machine Intelligence, 2008,30(2):328-341. ↩︎

【码上实战】【立体匹配系列】经典SGM:(6)视差填充相关推荐

  1. 【码上实战】【立体匹配系列】经典SGM:(2)代价计算

    码上教学系列 [码上实战][立体匹配系列]经典SGM:(1)框架与类设计 [码上实战][立体匹配系列]经典SGM:(2)代价计算 [码上实战][立体匹配系列]经典SGM:(3)代价聚合 [码上实战][ ...

  2. 【码上实战】【立体匹配系列】经典SGM:(4)代价聚合2

    昔人已乘黄鹤去,此地空余黄鹤楼. 2020对武汉.对中国.对世界来说是异常艰难的一年.武汉壮士扼腕,封一城而救一国,引得八方救援,举国抗疫.中国人在灾难面前总是空前团结,勇往直前!中华民族几千年来从未 ...

  3. 【码上实战】【立体匹配系列】经典SGM:(5)视差优化

    千呼万唤始出来,犹抱琵琶半遮面. 抱歉让大家久等,最近事儿繁多,导致更新推迟,实在抱歉. 码上教学系列 [码上实战][立体匹配系列]经典SGM:(1)框架与类设计 [码上实战][立体匹配系列]经典SG ...

  4. 【码上实战】【立体匹配系列】经典SGM:(1)框架与类设计

    前言 一直就想做这样的专题,因为自己是一名算法工程师,而算法落地对算法工程师来说是职责和能力之体现,前面有一个专题是专门介绍Semi-Global Matching(SGM)双目立体匹配算法的理论知识 ...

  5. 【码上实战】【立体匹配系列】经典SGM:(7)弱纹理优化

    欢迎收看长篇小说SGM第七章:弱纹理优化 码上教学系列 [码上实战][立体匹配系列]经典SGM:(1)框架与类设计 [码上实战][立体匹配系列]经典SGM:(2)代价计算 [码上实战][立体匹配系列] ...

  6. 【码上实战】【立体匹配系列】经典AD-Census: (1)框架

    下载AD-Census完整源码,点击进入: https://github.com/ethan-li-coding/AD-Census 欢迎同学们在Github项目里讨论,如果觉得博主代码质量不错,右上 ...

  7. 【码上实战】【立体匹配系列】经典AD-Census: (6)多步骤视差优化

    同学们好久不见! 下载完整源码,点击进入: https://github.com/ethan-li-coding/AD-Census 欢迎同学们在Github项目里讨论! 在实战的上一篇,我们对AD- ...

  8. 【码上实战】【立体匹配系列】经典PatchMatch: (1)框架

    下载完整源码,点击进入: https://github.com/ethan-li-coding/PatchMatchStereo 欢迎同学们在Github项目里讨论,如果觉得博主代码质量不错,右上角s ...

  9. 【码上实战】【立体匹配系列】经典PatchMatch: (4)代价计算

    下载完整源码,点击进入: https://github.com/ethan-li-coding/PatchMatchStereo 欢迎同学们在Github项目里讨论,如果觉得博主代码质量不错,右上角s ...

最新文章

  1. 【2020 Fall】哥伦比亚大学最新《机器学习》课程
  2. 看一下操作闪电网络最大的节点是什么感觉
  3. 解决python 保存json到文件时 中文显示16进制编码的问题
  4. putty利用密钥ssh服务登录ubuntu server 10.4
  5. nginx安装和基础代理配置
  6. Python数据结构——tuple
  7. 蓝桥杯-十六进制转八进制(java)
  8. strlen()函数 与 “\0“ 的关系 与 利用;strcmp()
  9. C指针原理(45)-LINUX应用
  10. GPU Gems1 - 25 用纹理贴图进行快速过滤宽度的计算
  11. Ignition Vision基本操作
  12. 「 Adams 」如何设置积分器与求解器类型
  13. html位置插入透明动画文字,鼠标放上去,图片上方动态显示半透明说明文字(源码)...
  14. mpchart点击_在MPAndroidChart中,如何为Barchart中的每个Bar添加click事件?
  15. PHPer 为什么会被 Javaer 鄙视?
  16. jpg转bmp c语言 linux,C语言实现BMP转换JPG的方法
  17. php 生成条码插件,php 条形码生成插件Composer组件|php条形码code128实现方法-爱测速网...
  18. php symlink,php函数symlink详解
  19. 用 InstallShieldX 做教育片的安装
  20. Kaggle: Jigsaw Multilingual Toxic Comment Classification Top Solutions 金牌思路总结

热门文章

  1. SolidWorks电气设计如何实现自动布线
  2. iOS开发经典问题记录
  3. Jenkins 镜像无法更新插件中心的3种解决方法
  4. windows cmd命令安装
  5. 敏捷开发与 DevOps 实战
  6. vue 打包后找不到路径
  7. 设计模式在项目中的应用之线程池
  8. 工作之余用Python挣外快的5个方法,可别错过!
  9. 一个数组有n个整数,使其前面各数顺序向后移m个位置, 最后m个数变成最前面的m个数
  10. 基于python的汽车销售网站设计与实现-计算机毕业设计源码+LW文档