作者I dulingwen@CSDN

编辑I 3D视觉开发者社区

01

什么是局部匹配算法?优势如何?

局部(Local)立体匹配是相对于半全局以及全局(Non-Local)立体匹配算法而言的,它不构建能量函数,而是利用某种代价函数(或称做相似性度量),仅仅通过比较左右视图中相同大小的图像块来确定视差,它的基本流程一般为代价计算、代价聚合、视差计算、视差细化。虽然非局部立体匹配算法在性能上可能优于局部算法,但是它们也有很多难点,并非是所有情况下的最好选择,例如:

全局算法或半全局算法由于需要相当多的计算量,因此运算耗时可能很长,特别是对于高分辨率的图像如1080x720、1920x1080等,即使使用硬件加速也难以做到实时。然而局部算法占用计算资源很少,同时运算效率很高,利用滑动窗口的数据复用策略再结合并行编程,完全可以做到实时,若能实现到硬件上则更能发挥其无可比拟的速度优势,特别适合算力较低的计算平台。

对于包含结构光发射器的双目或单目传感器,全局匹配算法的和局部匹配算法的匹配效果相比,差距可能并不大,在对视差图的精度和完整性没有太高要求的情况下,这时选择具有更高运算效率的局部匹配算法可能会是一种更好的选择。

02

代价函数

常用来做匹配代价的方法有以下几种:SAD/SSD/ZSAD/BT、NCC/ZNCC、Census/StarCensus、rank等,关于这些代价方法的原理这里就不详细描述了。下面通过OpenCV来实现局部立体匹配算法,为了简化代码的复杂性,代价聚合部分会略去不写,视差细化部分仅包括亚像素插值、唯一性检测、左右一致性检测和中值滤波。算法假定输入图像都是经过极线校正的。

03

代码实现

下面我们把代码放在这里供大家参考,其中核心的匹配部分代码也就100行左右。完整代码可以到这里来下载:
https://download.csdn.net/download/dulingwen/20449594

1、头文件LocalStereoMatch.h

#include <opencv2/opencv.hpp>typedef char int8;
typedef unsigned char uint8;
typedef short int16;
typedef int int32;
typedef float float32;enum
{LSM_SAD = 1,LSM_NCC = 2,LSM_CENSUS = 3,LSM_RANK = 4,
};enum
{LSM_LINEAR = 1,LSM_PARABOLA = 2,LSM_EQUALIZE = 3,
};/**
* @brief    局部立体匹配算法
* @param           left                左图像
* @param           right               右图像
* @param[out]      disp                视差图
* @param           window_size         匹配窗口尺寸
* @param           max_disparities     最大搜索视差
* @param           mode                匹配代价类型
* @param           ip_mode             亚像素插值方式:线性插值、抛物线插值、直方图均衡化的线性插值
* @param           uinqueness_ratio    唯一性检测比率
* @param           lrc_threshold       左右一致性检测阈值
*/
void LocalStereoMatching(const cv::Mat& left, const cv::Mat& right, cv::Mat& disp,
int window_size = 11, int max_disparities = 64, int mode = LSM_SAD, int ip_mode = LSM_PARABOLA,int uinqueness_ratio = 5, int lrc_threshold = 1);

2.源文件LocalStereoMatch.cpp

#include "LocalStereoMatch.h"// ----------------------------------------------------------------------------------------------------------------------------------
template<typename T>
inline float32 parabola_ip(T c1, T c2, T c, float32 d)
{float32 denom = std::max((float32)1, (float32)(c1 - c + c2 - c));float32 diff = (float32)(c1 - c2) / (denom * 2.f);float32 ds = d + diff;return ds;
}template<typename T>
inline float32 linear_ip(T c1, T c2, T c, float32 d)
{float32 ldiff = c1 - c;float32 rdiff = c2 - c;float32 diff = 0;if (ldiff <= rdiff)diff = -0.5f + 0.5f * (ldiff / rdiff);elsediff = 0.5f - 0.5f * (rdiff / ldiff);float32 ds = d + diff;return ds;
}template<typename T>
inline float32 equiangular_equalize_ip(T c1, T c2, T c, float32 d)
{float32 ldiff = c1 - c;float32 rdiff = c2 - c;float32 diff = 0;if (ldiff <= rdiff){float32 x = ldiff / rdiff;diff = -0.5f + 0.25f * (x * x + x);}else{float32 x = rdiff / ldiff;diff = 0.5f - 0.25f * (x * x + x);}float32 ds = d + diff;return ds;
}
// ----------------------------------------------------------------------------------------------------------------------------------// ----------------------------------------------------------------------------------------------------------------------------------
static int32 compute_cost_sad(const uint8* lptr, const uint8* rptr, int x, int y, int w, int rad, int d)
{int32 sad = 0;for (int i = x - rad; i < x + rad; i++){for (int j = y - rad; j < y + rad; j++){uint8 val1 = lptr[i * w + j];uint8 val2 = rptr[i * w + std::max(j - d, 0)];sad += std::abs(val1 - val2);}}return sad;
}static float32 compute_cost_ncc(const uint8* lptr, const uint8* rptr, int x, int y, int w, int rad, int d)
{int32 a = 0, b = 0, c = 0;for (int i = x - rad; i < x + rad; i++){for (int j = y - rad; j < y + rad; j++){uint8 val1 = lptr[i * w + j];uint8 val2 = rptr[i * w + std::max(j - d, 0)];a += val1 * val1;b += val2 * val2;c += val1 * val2;}}float32 ncc = FLT_MAX;if (c != 0) {ncc = (float32)(std::sqrt(a) * std::sqrt(b)) / (float32)c;}return ncc;
}static int16 compute_cost_census(const uint8* lptr, const uint8* rptr, int x, int y, int w, int rad, int d)
{int16 hamming = 0;const uint8& cval1 = lptr[x * w + y];const uint8& cval2 = rptr[x * w + std::max(y - d, 0)];for (int i = x - rad; i < x + rad; i++){for (int j = y - rad; j < y + rad; j++){if (i == x && j == y) continue;uint8 val1 = lptr[i * w + j];uint8 val2 = rptr[i * w + std::max(j - d, 0)];if (val1 > cval1 && val2 <= cval2)hamming += 1;else if (val1 <= cval1 && val2 > cval2)hamming += 1;}}return hamming;
}static int16 compute_cost_rank(const uint8* lptr, const uint8* rptr, int x, int y, int w, int rad, int d)
{int16 F = 0;const uint8& cval1 = lptr[x * w + y];const uint8& cval2 = rptr[x * w + std::max(y - d, 0)];int16 u = 2;int16 v = 9;int16 iu = -u;int16 iv = -v;int8 rank1 = 0;int8 rank2 = 0;for (int i = x - rad; i < x + rad; i++){for (int j = y - rad; j < y + rad; j++){int16 val1 = lptr[i * w + j];int16 val2 = rptr[i * w + std::max(j - d, 0)];int16 diff1 = val1 - cval1;int16 diff2 = val2 - cval2;if (diff1 < iv) rank1 = -2;else if (diff1 >= iv && diff1 < iu) rank1 = -1;else if (diff1 >= iu && diff1 <= u) rank1 = 0;else if (diff1 > u && diff1 <= v) rank1 = 1;else if (diff1 > v) rank1 = 2;if (diff2 < iv) rank2 = -2;else if (diff2 >= iv && diff2 < iu) rank2 = -1;else if (diff2 >= iu && diff2 <= u) rank2 = 0;else if (diff2 > u && diff2 <= v) rank2 = 1;else if (diff2 > v) rank2 = 2;F += std::abs(rank1 - rank2);}}return F;
}
// ----------------------------------------------------------------------------------------------------------------------------------// ----------------------------------------------------------------------------------------------------------------------------------
template <typename CostType>
void pseudo_lrc_check(CostType* cost_mat, float32* disp, const int& w, const int& h, const double& lrc_thr, float32 invalid_disp)
{if (!cost_mat || lrc_thr <= 0)return;CostType* disp2cost = (CostType*)malloc(w * sizeof(CostType));float32* disp2buf = (float32*)malloc(w * sizeof(float32));if (!disp2cost || !disp2buf)return;CostType max_cost = (CostType)0;if (std::is_same<CostType, float32>::value) max_cost = FLT_MAX;else if (std::is_same<CostType, int32>::value) max_cost = INT_MAX;else if (std::is_same<CostType, int16>::value) max_cost = SHRT_MAX;int y = 0;for (int x = 0; x < h; x++){float32* dptr = disp + x * w;for (y = 0; y < w; y++){disp2buf[y] = invalid_disp;disp2cost[y] = max_cost;}const CostType* cptr = cost_mat + x * w;for (y = 0; y < w; y++){float32 d = dptr[y];CostType c = cptr[y];if (d == invalid_disp)continue;int y2 = (int)(y - d);if (y2 < 0)continue;if (disp2cost[y2] > c){disp2cost[y2] = c;disp2buf[y2] = d;}}for (y = 0; y < w; y++){float32 d0 = dptr[y];if (d0 == invalid_disp)continue;float32 d1 = d0 + 1.f;int y0 = (int)(y - d0);int y1 = (int)(y - d1);if ((0 <= y0 && y0 < w && disp2buf[y0] > invalid_disp && std::fabs(disp2buf[y0] - d0) > lrc_thr) &&(0 <= y1 && y1 < w && disp2buf[y1] > invalid_disp && std::fabs(disp2buf[y1] - d0) > lrc_thr))dptr[y] = invalid_disp;}}free(disp2cost);free(disp2buf);
}// ----------------------------------------------------------------------------------------------------------------------------------// ----------------------------------------------------------------------------------------------------------------------------------template<typename CostType>
void matchImpl(const uint8* lptr, const uint8* rptr, float32* dptr, const int& w, const int& h, int& ws, int& ndisp, int mode, int ip_mode, int uniq, int lrc_thr)
{int num = ws * ws;int rad = ws / 2;CostType* cost0 = (CostType*)malloc((ndisp + 2) * sizeof(CostType));memset(cost0, 0, (ndisp + 2) * sizeof(CostType));CostType* cost = cost0 + 1;if (std::is_same<CostType, float32>::value)uniq = 0;  // ncc代价不是在任何情况下都适合用下面的唯一性检测,故设为0float32 invalid_disp = -1.f;CostType* cost_mat = nullptr;if (lrc_thr > 0){cost_mat = (CostType*)malloc(w * h * sizeof(CostType));}for (int i = rad; i < h - rad; i++){for (int j = rad; j < w - rad; j++){for (int k = 0; k < ndisp + 2; k++){cost0[k] = (CostType)0;}// 代价计算for (int d = 0; d < ndisp; d++){if (mode == 1)cost[d] = compute_cost_sad(lptr, rptr, i, j, w, rad, d);if (mode == 2)cost[d] = compute_cost_ncc(lptr, rptr, i, j, w, rad, d);if (mode == 3)cost[d] = compute_cost_census(lptr, rptr, i, j, w, rad, d);if (mode == 4)cost[d] = compute_cost_rank(lptr, rptr, i, j, w, rad, d);}// 寻找最优视差CostType mincost = 0;int32 bestd = 0;if (std::is_same<CostType, float32>::value) mincost = FLT_MAX;else if (std::is_same<CostType, int32>::value) mincost = INT_MAX;else if (std::is_same<CostType, int16>::value) mincost = SHRT_MAX;for (int d = 0; d < ndisp; d++){const CostType& currcost = cost[d];if (currcost < mincost){mincost = currcost;bestd = d;}}if (cost_mat)cost_mat[i * w + j] = mincost;// 唯一性检测if (uniq > 0){CostType thresh = mincost + mincost * ((CostType)(uniq)) / ((CostType)(100));int d = 0;for (d = 0; d < ndisp; d++){const auto& costp = cost[d];if ((d < bestd - 1 || d > bestd + 1) && costp <= thresh)break;}if (d < ndisp){dptr[i * w + j] = invalid_disp;continue;}}// 亚像素插值const CostType cost1 = cost[bestd - 1];const CostType cost2 = cost[bestd + 1];if (((bestd - 1) == -1) || ((bestd + 1) == ndisp)){dptr[i * w + j] = (float32)bestd;continue;}if (ip_mode == 1)dptr[i * w + j] = parabola_ip<CostType>(cost1, cost2, mincost, bestd);else if (ip_mode == 2)dptr[i * w + j] = linear_ip<CostType>(cost1, cost2, mincost, bestd);else if (ip_mode == 3)dptr[i * w + j] = equiangular_equalize_ip<CostType>(cost1, cost2, mincost, bestd);elsedptr[i * w + j] = (float32)bestd;}}// 伪左右一致性检测pseudo_lrc_check(cost_mat, dptr, w, h, lrc_thr, invalid_disp);if (cost_mat)free(cost_mat);cost_mat = nullptr;
}void LocalStereoMatching(const cv::Mat& left, const cv::Mat& right, cv::Mat& disp, int window_size, int max_disparities, int mode, int ip_mode, int uinqueness_ratio, int lrc_threshold)
{assert(!left.empty());assert(!right.empty());assert(left.size() == right.size());const int width = left.cols;const int height = left.rows;int ws = window_size;int rad = ws / 2;int num = ws * ws;int ndisp = max_disparities;const uint8* lptr = left.data;const uint8* rptr = right.data;disp.release();disp.create(cv::Size(width, height), CV_32FC1);float32* dptr = disp.ptr<float32>();if (mode == 1 /*sad*/)matchImpl<int32>(lptr, rptr, dptr, width, height, ws, ndisp, mode, ip_mode, uinqueness_ratio, lrc_threshold);else if (mode == 2 /*ncc*/)matchImpl<float32>(lptr, rptr, dptr, width, height, ws, ndisp, mode, ip_mode, uinqueness_ratio, lrc_threshold);else if (mode == 3 /*census*/)matchImpl<int16>(lptr, rptr, dptr, width, height, ws, ndisp, mode, ip_mode, uinqueness_ratio, lrc_threshold);else if (mode == 4 /*rank*/)matchImpl<int16>(lptr, rptr, dptr, width, height, ws, ndisp, mode, ip_mode, uinqueness_ratio, lrc_threshold);else{printf("Error: unsupported alogrithm.\n");return;}cv::medianBlur(disp, disp, 3);
}
// ----------------------------------------------------------------------------------------------------------------------------------

04

结果及运算效率分析

测试图像采用了MiddleBurry网站的双目图像,各个匹配算法采用了统一的参数设置:window_size = 17, max_disparities = 128~145, uniqueness_ratio = 5, lrc_threshold = 1,结果如下图,从左到右使用的匹配算法依次为SAD、NCC、Census、Rank。从中可以看出NCC的匹配效果是最好的,具有良好的抗噪性能,生成的视差图完整度最高,但同时噪声也最多,SAD次之,然后是rank和census。其中SAD的运算量最低,因此运算速度最快,只需十几秒便可以计算一张图像。


05

如何提升局部立体匹配算法性能?

匹配代价至关重要,最常用的改善方式是使用自适应权重对匹配代价进行加权,这通常适用于pixel-wise的匹配代价。还有的是使用多个不同的窗口尺寸进行匹配,从中选择置信度最高的结果作为最终结果,但这种方法的计算量会比较高,实际使用起来并不划算。随着深度学习的发展,深度学习主导的特征提取方式大大超越了传统的手工特征设计,因此一些基于卷积神经网络的度量学习开始用来替代传统的匹配代价,比较典型的有MC-CNN、HashMatch等。

代价聚合能够很好的提升匹配精度与鲁棒性,至今为止已发展出多种代价聚合方式:普通的邻域求和、基于引导滤波的代价聚合、基于双边滤波的代价聚合、基于十字交叉臂的代价聚合以及跨尺度代价聚合等等,另外SGM中的动态规划部分通常也被认为是一种代价聚合方式。

版权声明:本文为作者授权转载,由3D视觉开发者社区编辑整理发布,仅做学术分享,未经授权请勿二次传播,版权归原作者所有,图片来源于网络,若涉及侵权内容请联系删文。

本文仅做学术分享,如有侵权,请联系删文。

3D视觉精品课程推荐:

1.面向自动驾驶领域的多传感器数据融合技术

2.面向自动驾驶领域的3D点云目标检测全栈学习路线!(单模态+多模态/数据+代码)
3.彻底搞透视觉三维重建:原理剖析、代码讲解、及优化改进
4.国内首个面向工业级实战的点云处理课程
5.激光-视觉-IMU-GPS融合SLAM算法梳理和代码讲解
6.彻底搞懂视觉-惯性SLAM:基于VINS-Fusion正式开课啦
7.彻底搞懂基于LOAM框架的3D激光SLAM: 源码剖析到算法优化
8.彻底剖析室内、室外激光SLAM关键算法原理、代码和实战(cartographer+LOAM +LIO-SAM)

9.从零搭建一套结构光3D重建系统[理论+源码+实践]

10.单目深度估计方法:算法梳理与代码实现

11.自动驾驶中的深度学习模型部署实战

12.相机模型与标定(单目+双目+鱼眼)

重磅!3DCVer-学术论文写作投稿 交流群已成立

扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。

同时也可申请加入我们的细分方向交流群,目前主要有3D视觉CV&深度学习SLAM三维重建点云后处理自动驾驶、多传感器融合、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流、ORB-SLAM系列源码交流、深度估计等微信群。

一定要备注:研究方向+学校/公司+昵称,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。

▲长按加微信群或投稿

▲长按关注公众号

3D视觉从入门到精通知识星球:针对3D视觉领域的视频课程(三维重建系列三维点云系列结构光系列手眼标定相机标定、激光/视觉SLAM、自动驾驶等)、知识点汇总、入门进阶学习路线、最新paper分享、疑问解答五个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近4000星球成员为创造更好的AI世界共同进步,知识星球入口:

学习3D视觉核心技术,扫描查看介绍,3天内无条件退款

圈里有高质量教程资料、可答疑解惑、助你高效解决问题

觉得有用,麻烦给个赞和在看~  

局部立体匹配算法介绍及代码实现相关推荐

  1. 立体匹配十大概念综述---立体匹配算法介绍

    from:https://blog.csdn.net/wintergeng/article/details/51049596 一.概念 立体匹配算法主要是通过建立一个能量代价函数,通过此能量代价函数最 ...

  2. Cross-Scale Cost Aggregation for Stereo Matching立体匹配算法介绍

    最近,研究了下CVPR2014上的一篇基于多尺度代价聚合的立体匹配算法,这个作者提供了原代码,运行了下,发现效果真心不错,不开后端处理的话,时间在0.4s左右.这个算法比较牛逼的有两点: 1:结合多尺 ...

  3. 基于深度学习算法和传统立体匹配算法的双目立体视觉

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 01 立体视觉是什么? 在开始之前,我相信很多站友都会有这个疑问, ...

  4. 立体匹配算法实现之:AdaptWeight

    from: http://blog.sina.com.cn/s/blog_500afcd40100lqi1.html 我的主要研究方向是立体匹配(Stereo Matching),是计算机视觉(Com ...

  5. [双目视差] 立体匹配算法推理 - SGBM算法(一)

    文章目录 立体匹配算法推理 - SGBM算法(一) 一.SGBM与SGM的区别 二.代价计算 立体匹配算法推理 - SGBM算法(一) SGBM立体匹配算法,总体来讲包含以下6个步骤: Preproc ...

  6. 基于python的AD-census立体匹配算法实现

    文章目录 前言 一.AD-census是什么? 1.代价计算 2.代价聚合 3.视差优化 4.视差后处理 二.基于python的AD-census立体匹配算法实现 前言   AD-Census算法来自 ...

  7. 基于基于全局差错能量函数的双目图像立体匹配算法matlab仿真,并提取图像的深度信息

    目录 1.算法概述 2.仿真效果预览 3.核心MATLAB代码预览 4.完整MATLAB程序 1.算法概述 全局的能量函数公式如下: E(f)=Edata(f)+Esmooth(f) 其中,Edata ...

  8. 立体匹配算法-SAD

    目录 前言 SAD 是一种简单高效的立体匹配算法,虽然由于精度等原因很少被实际应用,但可以帮助我们理解立体匹配过程 一.SAD算法原理 SAD计算过程主要包括以下步骤: 二.代码示例 1.引入库 2. ...

  9. 双目立体视觉之立体匹配算法

    一.立体匹配简介: 双目立体视觉是指使用两个摄像机从不同的角度获取同一个场景的左右视图,然后使用双目立体匹配算法来寻找左右视图中的匹配像素点对,最后利用三角测量原理来还原三维空间物理点过程.其中双目立 ...

最新文章

  1. Wine 1.0 RC2
  2. Redis进阶-lua脚本
  3. MATLAB算法(函数)编译为C++动态库遇到的问题
  4. python爬虫多url_Python爬虫实战入门六:提高爬虫效率—并发爬取智联招聘
  5. Asp组件中级入门与精通系列之三
  6. Sqlite error- INSERT failed: datatype mismatch
  7. 表单reset无法重置hidden的解决方案
  8. printf函数讲解
  9. 解决Docker for window与VMware虚拟机同时安装,造成虚拟机网络不通以及无法启动问题...
  10. 【kuangbin专题】Manacher
  11. js对象深拷贝的简单实现
  12. 计算机乐谱吃鸡,Capo可自动识别音乐生成乐谱
  13. iphone 添加网易邮箱(126/163)踩坑(ios16)
  14. RefineNet 理解
  15. bcdedit添加linux引导,利用Bcdedit创建Linux系统引导
  16. java.io.IOException: Attempted read from closed stream.
  17. [46]python画出心形图
  18. 计算机桌面有个方框,电脑屏幕的白色方框怎么清除
  19. bzoj 3838: [Pa2013]Raper (线段树)
  20. 怎么文字扫描识别?看完这篇你就会了

热门文章

  1. 怎么在windows笔记本使用html,笔记本上快捷键用不了 笔记本电脑上音量键用不了了...
  2. 关于react-router-dom 6.0.1的基础写法 解决Error: A <Route> is only ever to be used as the child of <Routes>
  3. 冒泡php_PHP实现冒泡排序
  4. java ftp 假死_FTPClient下载文件程序假死问题
  5. 2020PMP:报考条件、报考步骤、考试内容、适合人群、考试时间、考试费用
  6. 美化你的Typora
  7. 福特汉姆大学计算机科学专业,Fordham的Computer and Information Science「福特汉姆大学计算机与信息科学系」...
  8. 怎样取消隐式推送_iPhone XS ios12系统隐式推送开启后怎么关闭
  9. JavaScript数组常用方法解析和深层次js数组扁平化
  10. 天津大学计算机组成原理,天津大学计算机学院计算机组成原理复习材料.docx