KITTI下使用SGBM立体匹配算法获得深度图

KITTI下使用SGBM立体匹配算法获得深度图

以下内容不涉及原理,仅为工程性内容:

经典的立体匹配算法主要由:BM(Block Matching),SGBM(Semi-Global Block matching),GC。更高级的就直接用上了深度学习,这里就不在考虑了。

上述三种算法速度:BM > SGBM > GC,效果:BM < SGBM < GC;暂取折中的SGBM算法研究。

SGBM及相关程序参考博客:

  1. https://www.cnblogs.com/riddick/p/8486223.html
  2. https://blog.csdn.net/cxgincsu/article/details/74451940?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

SGBM算法参数众多,而且对于不同场景默认参数表现不好,这个问题也出现在了BM算法里面,由于接触的不多,并不清楚这个问题是不是立体匹配的通病。

我使用了KITTI截取的两张图片作为输入,但是在前几次输入时发现一个小问题,我的表现效果和博客上表现的相差极大:

失败的视差图

这是我输出的时差图,可以明显看出这个图片表述的一塌糊涂,一开始我还以为是我参数的问题,所以我调了半天的参数,但是情况并没有本质的改变。于是我向沈老师请教。

沈老师给了我一个很有用的表格:

参数表

但是效果仍然改变不大。这时候老师突然想起来:你是不是左右图颠倒了?这个算法对于左右视图顺序是有要求的。

我试了一下,果然。下图是ndisparities为64、80、128时:

正确的视差图

可以看出随着ndisparities逐渐增加,右侧的碎片数量逐渐减少。到128时基本符合我们的要求。当然,亮度有所下降,但是因为距离拉大了,这个倒是很正常。

值得注意的一点是disp这个参数,opencv解释

disp – Output disparity map. It is a 16-bit signed single-channel image of the same size as the input image. It contains disparity values scaled by 16. So, to get the floating-point disparity map, you need to divide each disp element by 16.

这个意思是:disp每个元素包含16位,其中12位整数,4位小数。如果转化为可读形式的,需要对1.0/16;

对应类型和比特的博客参考:
https://blog.csdn.net/YunLaowang/article/details/86583351

为了验证得到的时差图是否正确,我们需要手工标注验证一下:找出左图和右图中对应的一组特征点,打开画图工具得到他们的坐标,一般来说纵坐标是相同的。将横坐标相减得到时差,在时差图中找到改点(位置同左图),访问该点的元素。

当然,获得视差图仅仅是第一步,后面我们需要通过视差图来获得深度图。

获得深度图之前需要先对视差图进行预处理,我们预处理选择的是之前引用博客中作者给出的方法,直接copy来测试,然后利用视差公式求解深度。

这里有5点注意的地方:

  1. 视差图左边黑条不能去:左边黑条代表右侧相机没有采集到的位置,因此是没有深度的。

  2. 深度图的表示问题:如果仅仅使用8UC1还是没有问题的,但是万一需要使用16UC1及更高的32FC1等时,显示范围扩大到了255以上,这样在imshow基本就是一片黑,无法验证我们的猜想。有一个巧妙的思路是利用.xml文件采集图片的矩阵元素,这样可以看整体的情况,虽不是很直观,但是比一抹黑强很多。关于xml文件的使用详见博客https://blog.csdn.net/YunLaowang/article/details/86583351。另一种方法是对图像像素值进行缩放,至0-255之前,这样可以之间观察像素变化,但是缺点是由于缩放原因,像素差值不大。两种方法各有利弊,综合使用效果更好。

  3. 在对深度值缩放至0-255之间以方便进行表示时,删去空洞填充中高斯模糊部分。因为深度缩放后差距极小,很容易全部变成一种颜色。

  4. 一开始我模仿博客代码选择16UC1,但是由于不便于直接观察,因此我试图将depthMap选择我8UC1格式,但是结果显示,道路部分深度误差较大问题,在时差图上表现良好的道路在深度图中出现了明显的层次,详见下面:错误的深度图

    经过老师沟通,发现是范围出现了问题,8U表示范围在2的8次方,255,而最大深度值f*b/1=718.856*35=25159.96,远超8位depthMap所能表示的数据范围,因此被截断了,出现了奇怪的现象。修正方法详见第二点,修正后的图片如下:
    缩放的深度图
    测试代码如下:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <cmath>
//#include <Eigen/Core>
//#include <Eigen/Geometry>
using namespace std;
using namespace cv;

void insertDepth32f(cv::Mat& depth);

float fx = 718.856;
float baseline = 35;

int main(int argc, char const *argv[])
{
	cv::Mat imgR,imgL;
	imgL = cv::imread( argv[1],IMREAD_GRAYSCALE );
	imgR = cv::imread( argv[2],IMREAD_GRAYSCALE );
    int mindisparity = 0;
    int ndisparities = 128;
    int SADWindowSize = 11;
    //SGBM
    cv::Ptr<cv::StereoSGBM> sgbm = cv::StereoSGBM::create(mindisparity, ndisparities, SADWindowSize);
    int P1 = 4 * imgL.channels() * SADWindowSize* SADWindowSize;
    int P2 = 32 * imgR.channels() * SADWindowSize* SADWindowSize;
    sgbm->setP1(P1);
    sgbm->setP2(P2);
    sgbm->setPreFilterCap(63);
    sgbm->setUniquenessRatio(10);
    sgbm->setSpeckleRange(32);
    sgbm->setSpeckleWindowSize(100);
    sgbm->setDisp12MaxDiff(1);
    //sgbm->setMode(cv::StereoSGBM::MODE_HH);
    cv::Mat disp32F;
    sgbm->compute(imgL, imgR, disp32F);
    disp32F.convertTo(disp32F, CV_32F, 1.0/16);
    //float* inData = disp32F.ptr<float>(284);
    //cout << float(inData[322]) << endl;
    //insertDepth32f(disp32F);
    Mat disp8U = Mat(disp32F.rows, disp32F.cols, CV_8UC1);
    //normalize(disp8U, disp32F, 0, 255, NORM_MINMAX, CV_8UC1);
    disp32F.convertTo(disp8U, CV_8UC1);
	imshow("disparity", disp8U);
    waitKey(0);

    cv::Mat depthMap = cv::Mat::zeros(disp32F.size(), CV_32FC1);
    int height = disp32F.rows;
    int width = disp32F.cols;
    for(int k = 0;k < height; k++)
    {
        const float* inData = disp32F.ptr<float>(k);
        float* outData = depthMap.ptr<float>(k);
        for(int i = 0; i < width; i++)
        {
            if(!inData[i]) continue;
            outData[i] = float(fx *baseline / inData[i]);
        }
    }
    FileStorage fswrite("test.xml", FileStorage::WRITE);// 新建文件,覆盖掉已有文件
    fswrite << "src1" << depthMap;
    fswrite.release();
    Mat depthMap8U = Mat(depthMap.rows, depthMap.cols, CV_8UC1);
    normalize(depthMap, depthMap8U, 0, 255, NORM_MINMAX, CV_8U);
    imshow("depth:8U",depthMap8U);
    waitKey(0);
	return 0;
}

void insertDepth32f(cv::Mat& depth)
{
    const int width = depth.cols;
    const int height = depth.rows;
    float* data = (float*)depth.data;
    cv::Mat integralMap = cv::Mat::zeros(height, width, CV_64F);
    cv::Mat ptsMap = cv::Mat::zeros(height, width, CV_32S);
    double* integral = (double*)integralMap.data;
    int* ptsIntegral = (int*)ptsMap.data;
    memset(integral, 0, sizeof(double) * width * height);
    memset(ptsIntegral, 0, sizeof(int) * width * height);
    for (int i = 0; i < height; ++i)
    {
        int id1 = i * width;
        for (int j = 0; j < width; ++j)
        {
            int id2 = id1 + j;
            if (data[id2] > 1e-3)
            {
                integral[id2] = data[id2];
                ptsIntegral[id2] = 1;
            }
        }
    }
    // 积分区间
    for (int i = 0; i < height; ++i)
    {
        int id1 = i * width;
        for (int j = 1; j < width; ++j)
        {
            int id2 = id1 + j;
            integral[id2] += integral[id2 - 1];
            ptsIntegral[id2] += ptsIntegral[id2 - 1];
        }
    }
    for (int i = 1; i < height; ++i)
    {
        int id1 = i * width;
        for (int j = 0; j < width; ++j)
        {
            int id2 = id1 + j;
            integral[id2] += integral[id2 - width];
            ptsIntegral[id2] += ptsIntegral[id2 - width];
        }
    }
    int wnd;
    double dWnd = 2;
    while (dWnd > 1)
    {
        wnd = int(dWnd);
        dWnd /= 2;
        for (int i = 0; i < height; ++i)
        {
            int id1 = i * width;
            for (int j = 0; j < width; ++j)
            {
                int id2 = id1 + j;
                int left = j - wnd - 1;
                int right = j + wnd;
                int top = i - wnd - 1;
                int bot = i + wnd;
                left = max(0, left);
                right = min(right, width - 1);
                top = max(0, top);
                bot = min(bot, height - 1);
                int dx = right - left;
                int dy = (bot - top) * width;
                int idLeftTop = top * width + left;
                int idRightTop = idLeftTop + dx;
                int idLeftBot = idLeftTop + dy;
                int idRightBot = idLeftBot + dx;
                int ptsCnt = ptsIntegral[idRightBot] + ptsIntegral[idLeftTop] - (ptsIntegral[idLeftBot] + ptsIntegral[idRightTop]);
                double sumGray = integral[idRightBot] + integral[idLeftTop] - (integral[idLeftBot] + integral[idRightTop]);
                if (ptsCnt <= 0)
                {
                    continue;
                }
                data[id2] = float(sumGray / ptsCnt);
            }
        }
        int s = wnd / 2 * 2 + 1;
        if (s > 201)
        {
            s = 201;
        }
        //cv::GaussianBlur(depth, depth, cv::Size(s, s), s, s);
    }
}
  • 8
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值