昔人已乘黄鹤去,此地空余黄鹤楼。

2020对武汉、对中国、对世界来说是异常艰难的一年。武汉壮士扼腕,封一城而救一国,引得八方救援,举国抗疫。中国人在灾难面前总是空前团结,勇往直前!中华民族几千年来从未向任何恶势力低头,必定会彻底战胜病毒,重振辉煌!今天,武汉全面接触离汉通道,标志着我国抗疫的阶段性胜利,真让人热泪盈眶,衷心祝愿各位身体健康,远离疾病!风雨过后,一切付出终会有回报!

黄鹤归来碧空净,乘风万里入青云!
2020年4月8日 中国武汉 晴

码上教学系列

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

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

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

  • 对角线路径
  • 代价聚合
  • 实验

思绪拉回来,上回咱们说到4路径聚合,想必大家对实验效果有深刻的印象,不同路径下的效果有显著的区别,结论显示多路径比单路径有明显的效果改善,4个邻居还是比1个邻居靠谱啊。那有人就要说了,8个邻居岂不是更靠谱,聪明!不过我先偷偷告诉你们,4邻居其实性价比很高,8邻居会有人会偷懒不干事。

好了,我们先回顾下4路径的效果,免得你们再回去看一遍博客(我是不是傻!)。

其实效果已经不错了,剔除错误的匹配,也能达到不错的效果。咦?我怎么老是在说4路径的好!好吧,我坦白我用4路径用的还不少,主要因为我要应对不少对高效率的任务需求。

回正题!上 8路径!

对角线路径

8路径说白了,就是比4路径多4个对角线路径,如图所示:

聚合的整体逻辑和4路径是一致的,可以按照4路径的代码思路来写,大家只要清楚怎么计算下一个像素的代价、灰度的内存地址,实现起来就不难。实际上8个路径聚合的逻辑都是一样,最本质不同的地方就是下个像素相对于当前像素是怎么偏移的,比如左到右,位置偏移就是列号加1;上到下,位置偏移就是行号加1;对角线路径中的左上到右下,位置偏移就是行号列号各加1。每个路径前后像素的代价和灰度的位置偏移量都是固定的,比如左到右偏移量是1个像素(disp_range个代价);上到下偏移量是 w 个像素(w * disp_range 个代价);左上到右下偏移量是 (w + 1) 个像素((w+1) * disp_range个代价)。

对角线路径还有另一点不同,就是它们会撞南墙(对,就是撞南墙才回头那种撞)!画个图感受下:

“撞南墙就回头啊!”
“不,聚合任务没完成怎么能回头呢?”
“好,那就行号继续保持前进,列号打道回府,重新来过。”

也就是行号继续按方向走一步,列号重置为起始列号!再画个图感受下:

右上到左下的对角线撞墙图我就不画了,累!

就这两点,其他和4-路径没啥区别了,不信?来给你们看代码:

void sgm_util::CostAggregateDagonal_1(const uint8* img_data, const sint32& width, const sint32& height,const sint32& min_disparity, const sint32& max_disparity, const sint32& p1, const sint32& p2_init,const uint8* cost_init, uint8* cost_aggr, bool is_forward)
{assert(width > 1 && height > 1 && max_disparity > min_disparity);// 视差范围const sint32 disp_range = max_disparity - min_disparity;// P1,P2const auto& P1 = p1;const auto& P2_Init = p2_init;// 正向(左上->右下) :is_forward = true ; direction = 1// 反向(右下->左上) :is_forward = false; direction = -1;const sint32 direction = is_forward ? 1 : -1;// 聚合// 存储当前的行列号,判断是否到达影像边界sint32 current_row = 0;sint32 current_col = 0;for (sint32 j = 0; j < width; j++) {// 路径头为每一列的首(尾,dir=-1)行像素auto cost_init_col = (is_forward) ? (cost_init + j * disp_range) : (cost_init + (height - 1) * width * disp_range + j * disp_range);auto cost_aggr_col = (is_forward) ? (cost_aggr + j * disp_range) : (cost_aggr + (height - 1) * width * disp_range + j * disp_range);auto img_col = (is_forward) ? (img_data + j) : (img_data + (height - 1) * width + j);// 路径上上个像素的代价数组,多两个元素是为了避免边界溢出(首尾各多一个)std::vector<uint8> cost_last_path(disp_range + 2, UINT8_MAX);// 初始化:第一个像素的聚合代价值等于初始代价值memcpy(cost_aggr_col, cost_init_col, disp_range * sizeof(uint8));memcpy(&cost_last_path[1], cost_aggr_col, disp_range * sizeof(uint8));// 路径上当前灰度值和上一个灰度值uint8 gray = *img_col;uint8 gray_last = *img_col;// 对角线路径上的下一个像素,中间间隔width+1个像素// 这里要多一个边界处理// 沿对角线前进的时候会碰到影像列边界,策略是行号继续按原方向前进,列号到跳到另一边界current_row = is_forward ? 0 : height - 1;current_col = j;if (is_forward && current_col == width - 1 && current_row < height - 1) {// 左上->右下,碰右边界cost_init_col = cost_init + (current_row + direction) * width * disp_range;cost_aggr_col = cost_aggr + (current_row + direction) * width * disp_range;img_col = img_data + (current_row + direction) * width;}else if (!is_forward && current_col == 0 && current_row > 0) {// 右下->左上,碰左边界cost_init_col = cost_init + (current_row + direction) * width * disp_range + (width - 1) * disp_range;cost_aggr_col = cost_aggr + (current_row + direction) * width * disp_range + (width - 1) * disp_range;img_col = img_data + (current_row + direction) * width + (width - 1);}else {cost_init_col += direction * (width + 1) * disp_range;cost_aggr_col += direction * (width + 1) * disp_range;img_col += direction * (width + 1);}// 路径上上个像素的最小代价值uint8 mincost_last_path = UINT8_MAX;for (auto cost : cost_last_path) {mincost_last_path = std::min(mincost_last_path, cost);}// 自方向上第2个像素开始按顺序聚合for (sint32 i = 0; i < height - 1; i ++) {gray = *img_col;uint8 min_cost = UINT8_MAX;for (sint32 d = 0; d < disp_range; d++) {// Lr(p,d) = C(p,d) + min( Lr(p-r,d), Lr(p-r,d-1) + P1, Lr(p-r,d+1) + P1, min(Lr(p-r))+P2 ) - min(Lr(p-r))const uint8  cost = cost_init_col[d];const uint16 l1 = cost_last_path[d + 1];const uint16 l2 = cost_last_path[d] + P1;const uint16 l3 = cost_last_path[d + 2] + P1;const uint16 l4 = mincost_last_path + std::max(P1, P2_Init / (abs(gray - gray_last) + 1));const uint8 cost_s = cost + static_cast<uint8>(std::min(std::min(l1, l2), std::min(l3, l4)) - mincost_last_path);cost_aggr_col[d] = cost_s;min_cost = std::min(min_cost, cost_s);}// 重置上个像素的最小代价值和代价数组mincost_last_path = min_cost;memcpy(&cost_last_path[1], cost_aggr_col, disp_range * sizeof(uint8));// 当前像素的行列号current_row += direction;current_col += direction;// 下一个像素,这里要多一个边界处理// 这里要多一个边界处理// 沿对角线前进的时候会碰到影像列边界,策略是行号继续按原方向前进,列号到跳到另一边界if (is_forward && current_col == width - 1 && current_row < height - 1) {// 左上->右下,碰右边界cost_init_col = cost_init + (current_row + direction) * width * disp_range;cost_aggr_col = cost_aggr + (current_row + direction) * width * disp_range;img_col = img_data + (current_row + direction) * width;}else if (!is_forward && current_col == 0 && current_row > 0) {// 右下->左上,碰左边界cost_init_col = cost_init + (current_row + direction) * width * disp_range + (width - 1) * disp_range;cost_aggr_col = cost_aggr + (current_row + direction) * width * disp_range + (width - 1) * disp_range;img_col = img_data + (current_row + direction) * width + (width - 1);}else {cost_init_col += direction * (width + 1) * disp_range;cost_aggr_col += direction * (width + 1) * disp_range;img_col += direction * (width + 1);}// 像素值重新赋值gray_last = gray;}}
}

你品,你细品,是不是就下个路径的计算方法不一样,并增加了触碰边界处理(咦?边界是啥?兄弟!别问!自己感受!我总不能在代码里写南墙!)

自我吐槽一下,函数名字取的不行,什么叫对角线1,这样的阿拉伯数字怎么能当名字,好吧,自我反省,各位有才之人自己换吧。郑重的解释一下:对角线1表示:“左上>>右下” 路径

是的兄弟,我还有个对角线2,表示:“右下>>左上” 路径

void sgm_util::CostAggregateDagonal_2(const uint8* img_data, const sint32& width, const sint32& height,const sint32& min_disparity, const sint32& max_disparity, const sint32& p1, const sint32& p2_init,const uint8* cost_init, uint8* cost_aggr, bool is_forward)
{assert(width > 1 && height > 1 && max_disparity > min_disparity);// 视差范围const sint32 disp_range = max_disparity - min_disparity;// P1,P2const auto& P1 = p1;const auto& P2_Init = p2_init;// 正向(右上->左下) :is_forward = true ; direction = 1// 反向(左下->右上) :is_forward = false; direction = -1;const sint32 direction = is_forward ? 1 : -1;// 聚合// 存储当前的行列号,判断是否到达影像边界sint32 current_row = 0;sint32 current_col = 0;for (sint32 j = 0; j < width; j++) {// 路径头为每一列的首(尾,dir=-1)行像素auto cost_init_col = (is_forward) ? (cost_init + j * disp_range) : (cost_init + (height - 1) * width * disp_range + j * disp_range);auto cost_aggr_col = (is_forward) ? (cost_aggr + j * disp_range) : (cost_aggr + (height - 1) * width * disp_range + j * disp_range);auto img_col = (is_forward) ? (img_data + j) : (img_data + (height - 1) * width + j);// 路径上上个像素的代价数组,多两个元素是为了避免边界溢出(首尾各多一个)std::vector<uint8> cost_last_path(disp_range + 2, UINT8_MAX);// 初始化:第一个像素的聚合代价值等于初始代价值memcpy(cost_aggr_col, cost_init_col, disp_range * sizeof(uint8));memcpy(&cost_last_path[1], cost_aggr_col, disp_range * sizeof(uint8));// 路径上当前灰度值和上一个灰度值uint8 gray = *img_col;uint8 gray_last = *img_col;// 对角线路径上的下一个像素,中间间隔width-1个像素// 这里要多一个边界处理// 沿对角线前进的时候会碰到影像列边界,策略是行号继续按原方向前进,列号到跳到另一边界current_row = is_forward ? 0 : height - 1;current_col = j;if (is_forward && current_col == 0 && current_row < height - 1) {// 右上->左下,碰左边界cost_init_col = cost_init + (current_row + direction) * width * disp_range + (width - 1) * disp_range;cost_aggr_col = cost_aggr + (current_row + direction) * width * disp_range + (width - 1) * disp_range;img_col = img_data + (current_row + direction) * width + (width - 1);}else if (!is_forward && current_col == width - 1 && current_row > 0) {// 左下->右上,碰右边界cost_init_col = cost_init + (current_row + direction) * width * disp_range ;cost_aggr_col = cost_aggr + (current_row + direction) * width * disp_range;img_col = img_data + (current_row + direction) * width;}else {cost_init_col += direction * (width - 1) * disp_range;cost_aggr_col += direction * (width - 1) * disp_range;img_col += direction * (width - 1);}// 路径上上个像素的最小代价值uint8 mincost_last_path = UINT8_MAX;for (auto cost : cost_last_path) {mincost_last_path = std::min(mincost_last_path, cost);}// 自路径上第2个像素开始按顺序聚合gray = *img_col;for (sint32 i = 0; i < height - 1; i++) {uint8 min_cost = UINT8_MAX;for (sint32 d = 0; d < disp_range; d++) {// Lr(p,d) = C(p,d) + min( Lr(p-r,d), Lr(p-r,d-1) + P1, Lr(p-r,d+1) + P1, min(Lr(p-r))+P2 ) - min(Lr(p-r))const uint8  cost = cost_init_col[d];const uint16 l1 = cost_last_path[d + 1];const uint16 l2 = cost_last_path[d] + P1;const uint16 l3 = cost_last_path[d + 2] + P1;const uint16 l4 = mincost_last_path + P2_Init / (abs(gray - gray_last) + 1);const uint8 cost_s = cost + static_cast<uint8>(std::min(std::min(l1, l2), std::min(l3, l4)) - mincost_last_path);cost_aggr_col[d] = cost_s;min_cost = std::min(min_cost, cost_s);}// 重置上个像素的最小代价值和代价数组mincost_last_path = min_cost;memcpy(&cost_last_path[1], cost_aggr_col, disp_range * sizeof(uint8));// 当前像素的行列号current_row += direction;current_col -= direction;// 下一个像素,这里要多一个边界处理// 这里要多一个边界处理// 沿对角线前进的时候会碰到影像列边界,策略是行号继续按原方向前进,列号到跳到另一边界if (is_forward && current_col == 0 && current_row < height - 1) {// 右上->左下,碰左边界cost_init_col = cost_init + (current_row + direction) * width * disp_range + (width - 1) * disp_range;cost_aggr_col = cost_aggr + (current_row + direction) * width * disp_range + (width - 1) * disp_range;img_col = img_data + (current_row + direction) * width + (width - 1);}else if (!is_forward && current_col == width - 1 && current_row > 0) {// 左下->右上,碰右边界cost_init_col = cost_init + (current_row + direction) * width * disp_range;cost_aggr_col = cost_aggr + (current_row + direction) * width * disp_range;img_col = img_data + (current_row + direction) * width;}else {cost_init_col += direction * (width - 1) * disp_range;cost_aggr_col += direction * (width - 1) * disp_range;img_col += direction * (width - 1);}// 像素值重新赋值gray_last = gray;gray = *img_col;}}
}

对角线2的代码我就不多说了,大家应该能融会贯通了吧!(大哥,你对角线1的代码也没怎么说啊!)(…)

代价聚合

相比之前的4-路径,再增加4条对角线路径:

void SemiGlobalMatching::CostAggregation() const
{// 路径聚合// 1、左->右/右->左// 2、上->下/下->上// 3、左上->右下/右下->左上// 4、右上->左上/左下->右上//// ↘ ↓ ↙   5  3  7// →    ←    1    2// ↗ ↑ ↖   8  4  6//const auto& min_disparity = option_.min_disparity;const auto& max_disparity = option_.max_disparity;assert(max_disparity > min_disparity);const sint32 size = width_ * height_ * (max_disparity - min_disparity);if(size <= 0) {return;}const auto& P1 = option_.p1;const auto& P2_Int = option_.p2_init;// 左右聚合sgm_util::CostAggregateLeftRight(img_left_, width_, height_, min_disparity, max_disparity, P1, P2_Int, cost_init_, cost_aggr_1_, true);sgm_util::CostAggregateLeftRight(img_left_, width_, height_, min_disparity, max_disparity, P1, P2_Int, cost_init_, cost_aggr_2_, false);// 上下聚合sgm_util::CostAggregateUpDown(img_left_, width_, height_, min_disparity, max_disparity, P1, P2_Int, cost_init_, cost_aggr_3_, true);sgm_util::CostAggregateUpDown(img_left_, width_, height_, min_disparity, max_disparity, P1, P2_Int, cost_init_, cost_aggr_4_, false);// 对角线1聚合sgm_util::CostAggregateDagonal_1(img_left_, width_, height_, min_disparity, max_disparity, P1, P2_Int, cost_init_, cost_aggr_5_, true);sgm_util::CostAggregateDagonal_1(img_left_, width_, height_, min_disparity, max_disparity, P1, P2_Int, cost_init_, cost_aggr_6_, false);// 对角线2聚合sgm_util::CostAggregateDagonal_2(img_left_, width_, height_, min_disparity, max_disparity, P1, P2_Int, cost_init_, cost_aggr_7_, true);sgm_util::CostAggregateDagonal_2(img_left_, width_, height_, min_disparity, max_disparity, P1, P2_Int, cost_init_, cost_aggr_8_, false);// 把4/8个方向加起来for(sint32 i =0;i<size;i++) {cost_aggr_[i] = cost_aggr_1_[i] + cost_aggr_2_[i] + cost_aggr_3_[i] + cost_aggr_4_[i];if (option_.num_paths == 8) {cost_aggr_[i] += cost_aggr_5_[i] + cost_aggr_6_[i] + cost_aggr_7_[i] + cost_aggr_8_[i];}}
}

嗯,这节省力了!(其实这是应该的,前面架构都搭好了,后面就越写越轻松。)

实验

春暖花开,又到了喜闻悦见的实验环节。

国际惯例,首先贴核线像对:

实验(1):只做从左上到右下聚合:

实验(2):只做从右下到左上聚合:

实验(3):只做从右上到左下聚合:

实验(4):只做从左下到右上聚合:

实验(5):从左上到右下+从右下到左上聚合:

实验(6):从右上到左下+从左下到右上聚合:

实验(7):4-路径聚合:(感觉似曾相识?)

实验(8):8-路径聚合:

发现了什么?4路径效果图是不是哪儿见过?8路径感觉也没好多少?

嗯,感觉是对的,对角线4路径和上篇4路径效果很接近,8路径并不会有很大的提升,我说过8路径有的会偷懒不干事(当然提升确实也是有的,我承认,Hirschmuller老爷子不要打我,去统计错误匹配率就能发现了)。我们再放一起贴一下两个4路径以及8路径的效果:

4路径-1 4路径-2 8-路径

话说回来,8-路径提升有限主要还是因为4-路径已经达到了较好的效果,提升幅度有限。4-邻域和8-邻域在图像处理领域本来也是二可选其一的选项。

劝大家不要去打2-路径的主意,真不行!

代价聚合到此篇就宣告完结,后面将给大家介绍的是视差优化

最后大家对完整代码感兴趣,请移步开源项目Github/GemiGlobalMatching下载全代码,点下项目右上角的star,有更新会实时通知到你的个人中心!。

敬礼!

下篇:恒叨立码|码上教学|立体匹配系列|经典SGM:5 视差优化

理论恒叨系列

【理论恒叨】【立体匹配系列】经典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/

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

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

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

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

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

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

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

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

    科学是一个精益求精的过程. 码上教学系列 [码上实战][立体匹配系列]经典SGM:(1)框架与类设计 [码上实战][立体匹配系列]经典SGM:(2)代价计算 [码上实战][立体匹配系列]经典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. java WebMvcConfig 全局设置时间服务器时区
  2. matplotlib关于坐标轴数据显示的几个重要参数
  3. 算术运算符_四则与取模运算
  4. 今天的快乐从何而来的飞鸽传书
  5. RestAPI的进化之路,后端MVVM模式或许来临,通过观察者模式,后端收集前端的GET类请求,主动推送数据变更到前端
  6. 【05】CC - 格式
  7. pytnon 学习day-1
  8. 树莓派python485设备通信
  9. coj #10066新的开始(最小生成树)
  10. 纳什均衡定义、举例、分类
  11. Page migration
  12. Vue3+Naive踩坑
  13. 计算机为什么无法访问公司共享文件夹,win10系统共享文件夹无法访问的详细方案...
  14. 【软件】网梭浏览器v2.4.7 思路
  15. 【操作系统基础】进程管理(三)进程同步与互斥
  16. QQ客户端源代码发布
  17. 运行python代码import cv2时报错的解决方法
  18. 紫外线强度检测传感器GUVA-S12SD的应用
  19. Java实现MD5加密及验证(两种方式,加盐md5与未加盐md5)
  20. Kaggle(Gun Violence Data)—美国枪支暴力事件分析(1)

热门文章

  1. 单体仓库与多仓库都有哪些优势劣势,微服务选择哪种方案比较好?
  2. 2021-2027全球与中国电动助力车市场现状及未来发展趋势
  3. 视频教程-从零开始深度学习(仅视频课)-深度学习
  4. 细说PHP笔记03(第7章)--数组与数据结构,数组定义,数组遍历,数组内部指针遍历,键值操作函数,统计数组函数,回调函数处理数组元素,数组排序,拆分、合并、分解、结合数组,数组实现堆栈,随机选取元素
  5. 2023年北京科技大学通信考研上岸前辈备考经验指导
  6. 【MYSQL优化之道摘抄】mysql常见的SQL优化方法
  7. 「Java工具类」发现一个身份证地址解析出省市区的工具类
  8. 画数学公式的简单使用
  9. 如何用计算机支持协同产品开发,应用共享框架下的计算机支持的协同设计技术研究...
  10. access_ok和get_user和put_user简介