【码上实战】【立体匹配系列】经典SGM:(6)视差填充
科学是一个精益求精的过程。
码上教学系列
【码上实战】【立体匹配系列】经典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/
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)视差填充相关推荐
- 【码上实战】【立体匹配系列】经典SGM:(2)代价计算
码上教学系列 [码上实战][立体匹配系列]经典SGM:(1)框架与类设计 [码上实战][立体匹配系列]经典SGM:(2)代价计算 [码上实战][立体匹配系列]经典SGM:(3)代价聚合 [码上实战][ ...
- 【码上实战】【立体匹配系列】经典SGM:(4)代价聚合2
昔人已乘黄鹤去,此地空余黄鹤楼. 2020对武汉.对中国.对世界来说是异常艰难的一年.武汉壮士扼腕,封一城而救一国,引得八方救援,举国抗疫.中国人在灾难面前总是空前团结,勇往直前!中华民族几千年来从未 ...
- 【码上实战】【立体匹配系列】经典SGM:(5)视差优化
千呼万唤始出来,犹抱琵琶半遮面. 抱歉让大家久等,最近事儿繁多,导致更新推迟,实在抱歉. 码上教学系列 [码上实战][立体匹配系列]经典SGM:(1)框架与类设计 [码上实战][立体匹配系列]经典SG ...
- 【码上实战】【立体匹配系列】经典SGM:(1)框架与类设计
前言 一直就想做这样的专题,因为自己是一名算法工程师,而算法落地对算法工程师来说是职责和能力之体现,前面有一个专题是专门介绍Semi-Global Matching(SGM)双目立体匹配算法的理论知识 ...
- 【码上实战】【立体匹配系列】经典SGM:(7)弱纹理优化
欢迎收看长篇小说SGM第七章:弱纹理优化 码上教学系列 [码上实战][立体匹配系列]经典SGM:(1)框架与类设计 [码上实战][立体匹配系列]经典SGM:(2)代价计算 [码上实战][立体匹配系列] ...
- 【码上实战】【立体匹配系列】经典AD-Census: (1)框架
下载AD-Census完整源码,点击进入: https://github.com/ethan-li-coding/AD-Census 欢迎同学们在Github项目里讨论,如果觉得博主代码质量不错,右上 ...
- 【码上实战】【立体匹配系列】经典AD-Census: (6)多步骤视差优化
同学们好久不见! 下载完整源码,点击进入: https://github.com/ethan-li-coding/AD-Census 欢迎同学们在Github项目里讨论! 在实战的上一篇,我们对AD- ...
- 【码上实战】【立体匹配系列】经典PatchMatch: (1)框架
下载完整源码,点击进入: https://github.com/ethan-li-coding/PatchMatchStereo 欢迎同学们在Github项目里讨论,如果觉得博主代码质量不错,右上角s ...
- 【码上实战】【立体匹配系列】经典PatchMatch: (4)代价计算
下载完整源码,点击进入: https://github.com/ethan-li-coding/PatchMatchStereo 欢迎同学们在Github项目里讨论,如果觉得博主代码质量不错,右上角s ...
最新文章
- 【2020 Fall】哥伦比亚大学最新《机器学习》课程
- 看一下操作闪电网络最大的节点是什么感觉
- 解决python 保存json到文件时 中文显示16进制编码的问题
- putty利用密钥ssh服务登录ubuntu server 10.4
- nginx安装和基础代理配置
- Python数据结构——tuple
- 蓝桥杯-十六进制转八进制(java)
- strlen()函数 与 “\0“ 的关系 与 利用;strcmp()
- C指针原理(45)-LINUX应用
- GPU Gems1 - 25 用纹理贴图进行快速过滤宽度的计算
- Ignition Vision基本操作
- 「 Adams 」如何设置积分器与求解器类型
- html位置插入透明动画文字,鼠标放上去,图片上方动态显示半透明说明文字(源码)...
- mpchart点击_在MPAndroidChart中,如何为Barchart中的每个Bar添加click事件?
- PHPer 为什么会被 Javaer 鄙视?
- jpg转bmp c语言 linux,C语言实现BMP转换JPG的方法
- php 生成条码插件,php 条形码生成插件Composer组件|php条形码code128实现方法-爱测速网...
- php symlink,php函数symlink详解
- 用 InstallShieldX 做教育片的安装
- Kaggle: Jigsaw Multilingual Toxic Comment Classification Top Solutions 金牌思路总结