局部立体匹配算法介绍及代码实现

作者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);
  else
    diff = 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代价不是在任何情况下都适合用下面的唯一性检测,故设为0
  float32 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);
      else
        dptr[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的运算量最低,因此运算速度最快,只需十几秒便可以计算一张图像。

987092019da0fd25242d95f45b14fe48.png

531e59a146189cf841492fe08d3fcd32.png

9b3688e3adc19f03f8ae95f1937276fa.png

3e17eb6e2bc4c551afc73b26427633e1.png

8ddb26e619c3decaa67a8d7965e4a0ba.png

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视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。

c0fcbe33e246a427df244e482ae4e3c9.png

▲长按加微信群或投稿

63b6b9974e75a8e2f6f75a945ff3ed81.png

▲长按关注公众号

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

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

d34bdb8dcb821ac864c38f5485d2c852.png

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

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值