基于OpenCV的双目深度估计实现与改进

双目深度估计

一、传统方法

​ 常用的方法有SAD匹配算法,BM算法,SGBM算法,GC算法

1.1、SAD算法

​ SAD(Sum of absolute differences)是一种图像匹配算法 ,基本思想是:差的绝对值之和。此算法常用于图像块匹配,将每个像素对应数值之差的绝对值求和,据此评估两个图像块的相似度。该算法快速、但并不精确,通常用于多级处理的初步筛选。

基本流程:

(1)构造一个小窗口,类似于卷积核;

(2)用窗口覆盖左边的图像,选择出窗口覆盖区域内的所有像素点;

(3)同样用窗口覆盖右边的图像并选择出覆盖区域的像素点;

(4)左边覆盖区域减去右边覆盖区域,并求出所有像素点灰度差的绝对值之和;

(5)移动右边图像的窗口,重复(3)-(4)的处理(这里有个搜索范围,超过这个范围跳出);

(6)找到这个范围内SAD值最小的窗口,即找到了左图锚点的最佳匹配的像素块。

1.2 BM算法

![image](https://images2015.cnblogs.com/blog/451660/201601/451660-20160114151753835-1022576108.png)

​ BM 算法对图像进行双向匹配,首先通过匹配代价在右图中计算得出匹配点。然后相同的原理及计算在左图中的匹配点。比较找到的左匹配点和源匹配点是否一致,如果你是,则匹配成功。

优点是速度很快,但是效果不佳。

基于OpenCV的实现

void BM()
{
  IplImage * img1 = cvLoadImage("left.png",0);
    IplImage * img2 = cvLoadImage("right.png",0);
    CvStereoBMState* BMState=cvCreateStereoBMState();
    assert(BMState);
    BMState->preFilterSize=9;
    BMState->preFilterCap=31;
    BMState->SADWindowSize=15;
    BMState->minDisparity=0;
    BMState->numberOfDisparities=64;
    BMState->textureThreshold=10;
    BMState->uniquenessRatio=15;
    BMState->speckleWindowSize=100;
    BMState->speckleRange=32;
    BMState->disp12MaxDiff=1;

    CvMat* disp=cvCreateMat(img1->height,img1->width,CV_16S);
    CvMat* vdisp=cvCreateMat(img1->height,img1->width,CV_8U);
    int64 t=getTickCount();
    cvFindStereoCorrespondenceBM(img1,img2,disp,BMState);
    t=getTickCount()-t;
    cout<<"Time elapsed:"<<t*1000/getTickFrequency()<<endl;
    cvSave("disp.xml",disp);
    cvNormalize(disp,vdisp,0,255,CV_MINMAX);
    cvNamedWindow("BM_disparity",0);
    cvShowImage("BM_disparity",vdisp);
    cvWaitKey(0);
    //cvSaveImage("cones\\BM_disparity.png",vdisp);
    cvReleaseMat(&disp);
    cvReleaseMat(&vdisp);
    cvDestroyWindow("BM_disparity");
}

其中minDisparity是控制匹配搜索的第一个参数,代表了匹配搜苏从哪里开始,numberOfDisparities表示最大搜索视差数uniquenessRatio表示匹配功能函数,这三个参数比较重要,可以根据实验给予参数值 .

1.3 SGBM算法

​ OpenCV中的SGBM算法是一种半全局立体匹配算法,立体匹配的效果明显好于局部匹配算法,但是同时复杂度上也要远远大于局部匹配算法。算法主要是参考Stereo Processing by Semiglobal Matching and Mutual Information。 opencv中实现的SGBM算法计算匹配代价没有按照原始论文的互信息作为代价,而是按照块匹配的代价。

minDisparity:最小视差,默认为0。此参数决定左图中的像素点在右图匹配搜索的起点,int 类型;

**numDisparities:**视差搜索范围长度,其值必须为16的整数倍。最大视差 maxDisparity = minDisparity + numDisparities -1;

**blockSize:**SAD代价计算窗口大小,默认为5。窗口大小为奇数,一般在33 到2121之间;

**P1、P2:**能量函数参数,P1是相邻像素点视差增/减 1 时的惩罚系数;P2是相邻像素点视差变化值大于1时的惩罚系数。P2必须大于P1。需要指出,在动态规划时,P1和P2都是常数。

一般建议:
P1 = 8cnsgbm.SADWindowSizesgbm.SADWindowSize;P2 = 32cnsgbm.SADWindowSizesgbm.SADWindowSize;

实验总结:

  1. blockSize(SADWindowSize) 越小,也就是匹配代价计算的窗口越小,视差图噪声越大;blockSize越大,视差图越平滑;太大的size容易导致过平滑,并且误匹配增多,体现在视差图中空洞增多;
  2. 惩罚系数控制视差图的平滑度,P2>P1,P2越大则视差图越平滑;
  3. 八方向动态规划较五方向改善效果不明显,主要在图像边缘能够找到正确的匹配

1.4 GC算法

GC算法在OpenCV3中已经没有了相应的API,因为这种方法虽然效果好,但是计算量实在是太大,不实用。

二、深度学习方法

​ 深度图除了应用传统方法计算之外,还可以通过最近几年非常火热的机器学习,深度学习等技术进行实现。目前在无人驾驶领域有迫切的应用。深度估计不仅仅可以用双目进行深度估计,甚至还可以用单目图像进行深度估计。双目深度估计在三维重建,HCI,AR,自动驾驶等领域至关重要,单目深度估计也有意义。尽管有不少硬件能够直接得到深度图,但它们都有各自的缺陷。比如3D LIDAR设备非常昂贵;基于结构光的深度摄像头比如Kinect等在室外无法使用,且得到的深度图噪声比较大;而双目摄像头需要利用立体匹配算法,计算量比较大,而且对于低纹理场景效果不好。单目摄像头相比来说成本最低,设备也较普及,所以从单目摄像头估计出深度仍然是一个可选的方案,应用比其它方案更加广泛。举个SLAM的例子,对于单目SLAM来说,从单张图(或者是静止的图序列)是无法在几何上得到深度的。如果能通过算法给出一个粗略的深度估计(相当于从数据集中获得图像的深度先验),对于算法的收敛性和鲁棒性也是一个很大的提升 。

2.1 PSMNet(双目)–CVPR2018

​ PSMNet(Pyramid Stereo Matching Network)是利用双目立体图像中进行深度估计的,它提出了一个全新的金字塔匹配网络在立体匹配中利用全局环境信息。空间金字塔池化(SPP)和空洞卷积(dilated convolution)用来扩大感受野。通过这个方法,PSMNet将像素级别的特征拓展到包含不同尺度感受野的区域级别的特征,将全局和局部特征信息结合起来构成匹配代价卷以获得更加可靠的视差估计值。除此之外,本文还设计了一个结合了中间监督的堆叠沙漏3D卷积神经网络去调整匹配代价卷(类似传统算法中的代价聚合过程)。这个堆叠的沙漏3D-CNN重复进行由精到粗再由粗到精(top-down/bottom-up)的过程来提高对全局信息的利用程度。

下图是PSMNet的整体网络结构:

这里写图片描述

其中最重要的部分是空间金字塔池化模块

​ 单独从一个像素的强度(灰度或RGB值)很难判断环境关系。因此借助物体的环境信息来丰富图像特征能够有助于一致性估计,尤其对于不适定区域。在本文中,物体(例如汽车)和次级区域(车窗,轮胎等)的关系由SPP模块学习来结合多层级的环境信息。

​ 在何凯明的论文中,SPP被设计用来去除CNN中的尺寸约束。由SPP生成的不同级别的特征图被平整后送入全卷积层用于分类,在此之后SPP被用于语义分割问题。ParseNet使用全局平均池化来结合全局环境信息。PSPNet拓展了ParseNet,采用包含不同尺度和次级区域信息的分层全局池化。在PSPNet中,SPP模块使用自适应平均池化把特征压缩到四个尺度上,并紧跟一个1*1的卷积层来减少特征维度,之后低维度的特征图通过双线性插值的方法进行上采样以恢复到原始图片的尺寸。不同级别的特征图都结合成最终的SPP特征图。

​ 在本文中,我们为SPP设计了4个尺度的平均池化:6464,3232,1616,88。并与PSPNet一样,采用了1*1卷积和上采样。在简化模型测试中(ablation study),我们设计了大量的实验来展示不同级别的特征图的影响。

​ 本文的实验结果在KITT数据集上取得了最好的成绩。

###2.2 DORN(单目)-CVPR2018

​ 本文(Deep Ordinal Regression Network for Monocular Depth Estimation)是ROB2018中深度预测的冠军方案,它与传统的深度预测方法不同。传统的方法是利用视角、纹理、目标大小、目标位置、遮挡关系最为深度预测的特征线索;当前使用的基于深度卷积网络的方法大多采用的是用于图像分类的特征提取网络,这些网络由于有池化层的操作或者步长较大的卷积曹祖,导致预测的分辨率较低,虽然可以通过转置卷积、跨层连接等方式进行分辨率的方法,但是这样网络结构的复杂度和时间开销和计算成本相应增加 。本文提出的方案采用扩张卷积的方式进行多尺度融合。网络结构如下图:

这里写图片描述

这个方法中最重要的亮点是提出了多尺度融合方法。网络中的 ASPP部分采用不同扩张系数的扩张卷积操作,能够在不改变图像分辨率的前提下,有效得到不同感受野大小的卷积操作,进而得到多尺度融合特征

三、实验

​ 由于SGBM算法视差效果好速度快的特点,常常被广泛应用和改进,所以我就没有对其他算法进行重新实现,只在Codeing Test中实验了SGBM算法,并对该算法进行了改进。

3.1 SGBM算法的实现

Mat SGBM(Mat left,Mat right)
{
  Mat disp,color(disp.size(),CV_8UC3);
	int mindisparity = 0;
	int ndisparities = 64;  
	int SADWindowSize = 11; 
	//SGBM
	cv::Ptr<cv::StereoSGBM> sgbm = cv::StereoSGBM::create(mindisparity, ndisparities, SADWindowSize);
	int P1 = 8 * left.channels() * SADWindowSize* SADWindowSize;
	int P2 = 32 * left.channels() * SADWindowSize* SADWindowSize;
	sgbm->setP1(P1);
	sgbm->setP2(P2);
	sgbm->setPreFilterCap(15);
	sgbm->setUniquenessRatio(10);
	sgbm->setSpeckleRange(2);
	sgbm->setSpeckleWindowSize(100);
	sgbm->setDisp1![微信截图_20180918210933](E:\学习\深度学习\活体检测\双目摄像头\因特尔测试\display_results\微信截图_20180918210933.png)
}


3.2 灰度转伪彩色

​ 为了让实验效果更佳的明显,我对实验结果进行了彩色可视化,具体是算法试下如下:

Mat gray2Color(Mat img)
{
   
   Mat img_color(img.rows, img.cols, CV_8UC3);//构造RGB图像
   #define IMG_B(img,y,x) img.at<Vec3b>(y,x)[0]
   #define IMG_G(img,y,x) img.at<Vec3b>(y,x)[1]
   #define IMG_R(img,y,x) img.at<Vec3b>(y,x)[2]
   uchar tmp2=0;
   for (int y=0;y<img.rows;y++)//转为彩虹图的具体算法,主要思路是把灰度图对应的0~255的数值分别转换成彩虹色:红、橙、黄、绿、青、蓝。
   {
      for (int x=0;x<img.cols;x++)
      {
         tmp2 = img.at<uchar>(y,x);
         if (tmp2 <= 51)
         {
            IMG_B(img_color,y,x) = 255;
            IMG_G(img_color,y,x) = tmp2*5;
            IMG_R(img_color,y,x) = 0;
         }
         else if (tmp2 <= 102)
         {
            tmp2-=51;
            IMG_B(img_color,y,x) = 255-tmp2*5;
            IMG_G(img_color,y,x) = 255;
            IMG_R(img_color,y,x) = 0;
         }
         else if (tmp2 <= 153)
         {
            tmp2-=102;
            IMG_B(img_color,y,x) = 0;
            IMG_G(img_color,y,x) = 255;
            IMG_R(img_color,y,x) = tmp2*5;
         }
         else if (tmp2 <= 204)
         {
            tmp2-=153;
            IMG_B(img_color,y,x) = 0;
            IMG_G(img_color,y,x) = 255-uchar(128.0*tmp2/51.0+0.5);
            IMG_R(img_color,y,x) = 255;
         }
         else
         {
            tmp2-=204;
            IMG_B(img_color,y,x) = 0;
            IMG_G(img_color,y,x) = 127-uchar(127.0*tmp2/51.0+0.5);
            IMG_R(img_color,y,x) = 255;
         }
      }
   }
    return img_color;
}

3.3 实验效果

在这里插入图片描述

图1 原始图片(Right) 视差图 视差图转为伪彩色

在这里插入图片描述

图2 原始图片(Right) 视差图 视差图转为伪彩色

这里写图片描述

图3 原始图片(Right) 视差图 视差图转为伪彩色

3.2 对SGBM算法的改进

​ 从上面的可视化深度图中可以看出,深度信息中存在许多的空洞,为了填充这些缺失的空洞值,对算法SGBM进行了改进。

改进思路:

​ ① 以视差图dispImg为例。计算图像的积分图integral,并保存对应积分图中每个积分值处所有累加的像素点个数n(空洞处的像素点不计入n中,因为空洞处像素值为0,对积分值没有任何作用,反而会平滑图像)。

② 采用多层次均值滤波。首先以一个较大的初始窗口去做均值滤波(积分图实现均值滤波就不多做介绍了,可以参考我之前的一篇博客),将大区域的空洞赋值。然后下次滤波时,将窗口尺寸缩小为原来的一半,利用原来的积分图再次滤波,给较小的空洞赋值(覆盖原来的值);依次类推,直至窗口大小变为3x3,此时停止滤波,得到最终结果。

③ 多层次滤波考虑的是对于初始较大的空洞区域,需要参考更多的邻域值,如果采用较小的滤波窗口,不能够完全填充,而如果全部采用较大的窗口,则图像会被严重平滑。因此根据空洞的大小,不断调整滤波窗口。先用大窗口给所有空洞赋值,然后利用逐渐变成小窗口滤波覆盖原来的值,这样既能保证空洞能被填充上,也能保证图像不会被过度平滑。

算法实现:

//将噪声和空洞进行填充
Mat 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);
            }
        }![微信截图_20180918210755](E:\学习\深度学习\活体检测\双目摄像头\因特尔测试\display_results\微信截图_20180918210755.png)
    return depth;
}

改进效果

这里写图片描述

图4 原始图片(Right) 视差图 视差图填充之后

在这里插入图片描述

图5 原始图片(Right) 视差图 视差图填充后

在这里插入图片描述

图6 视差图彩色图 视差图填充后的彩色图

通过实验发现,改进后的算法比原来的效果要好不少,例如图6中填充后的视差彩色图的噪声明显比没有填充的视差图少很多。说明改进之后还是很有效的。

四、总结

​ 不管是传统方法还是深度学习的方法进行深度估计都有各自的优点,但是我更看好应用深度学习的方法。因为深度学习的方法可以学习到更多的特征,不仅仅是多尺度的特征还可以是多层次的特征。目前在自动驾驶领域应用的也多数是深度学习的方法进行深度估计,因为效果明显优于传统的方法。

##参考文献:

https://www.cnblogs.com/polly333/p/5130375.html
https://www.cnblogs.com/riddick/p/8486223.html?utm_source=debugrun&utm_medium=referral
Chang, Jia-Ren, and Yong-Sheng Chen. “Pyramid Stereo Matching Network.” Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2018.
Fu, Huan, et al. “Deep Ordinal Regression Network for Monocular Depth Estimation.” Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2018.

  • 14
    点赞
  • 172
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
### 回答1: opencv中的双目深度仿真代码是通过计算左右相机拍摄的图像之间的视差来估计场景中不同物体的深度信息。具体步骤包括以下几个方面: 1. 读取并加载左右相机的图像。 2. 对图像进行预处理,例如灰度化、去噪等操作。 3. 提取左右相机图像中的特征点,例如使用SIFT、SURF等算法。 4. 对特征点进行匹配,通过计算特征的相似度来确定左右图像中对应的特征点。 5. 根据特征点的匹配结果,计算左右图像之间的视差,也就是对应特征点的水平位移。 6. 根据视差信息,利用三角测量方法计算物体的深度信息。 7. 对深度信息进行归一化和可视化处理,可以使用颜色映射来表示不同深度的物体。 8. 输出双目深度图像。 需要注意的是,双目深度仿真代码的准确性和效果受多种因素的影响,如摄像机的标定参数、图像质量、特征点提取和匹配的准确性等,因此在实际应用中需要综合考虑这些因素,并进行相应的优化和改进。 ### 回答2: OpenCV是一个开源的计算机视觉库,能够帮助开发者进行图像处理和计算机视觉相关任务。双目深度仿真是其中一个重要的功能,可用于测量物体的距离和建立三维模型。 双目深度仿真的代码步骤大致如下: 1. 首先,需要加载左右两个相机的标定参数,即相机的内参和外参。这些参数对于双目视觉系统至关重要,用于计算物体的深度和真实世界坐标。 2. 接下来,通过相机标定参数,对左右相机的图像进行畸变校正,将图像中的畸变修正为真实的物体形状。这一步骤可以使用OpenCV中的`undistort()`函数来实现。 3. 然后,利用视差映射算法(例如SAD匹配算法),对校正后的图像进行匹配,寻找左右图像中对应的特征点。这一步骤可以使用OpenCV中的`StereoBM`或`StereoSGBM`类来实现。 4. 匹配后,可以根据匹配的特征点计算视差图。视差图将左图像上的像素与右图像上的对应像素之间的水平偏移量映射到图像中的每个像素。 5. 最后,利用视差图和相机标定参数,可以计算出每个像素的深度值。这一步骤可以使用OpenCV中的`reprojectImageTo3D()`函数来实现。 总结来说,双目深度仿真的代码主要包括加载相机参数、畸变校正、特征点匹配、视差计算和深度计算几个步骤。通过这些步骤,我们可以得到校正后的图像、视差图和深度图,从而实现双目深度仿真。 ### 回答3: OpenCV是一个开源的计算机视觉库,它包含了许多用于图像处理和计算机视觉的函数和工具。其中之一是双目深度仿真,它用于估算图像中不同物体或场景的距离。 双目深度仿真的工作原理是基于双目立体视觉的原理。对于双目立体视觉系统,它包括两个摄像机,分别位于一定距离的位置上,模拟人眼观察物体的方式。通过对两个摄像机拍摄到的图像进行比对和分析,可以估算出图像中物体的距离。 在OpenCV中,双目深度仿真的实现主要包括以下步骤: 1. 标定摄像机:首先,需要对双目摄像机进行标定,获得摄像机内外参数矩阵。这可以通过拍摄一组特定模式的图像并使用OpenCV中的标定函数进行计算。 2. 图像对齐:由于双目摄像机的拍摄角度和位置不同,需要通过图像对齐来将两张图像对齐到同一个平面上。对齐方法可以使用OpenCV中的图像配准函数进行处理。 3. 匹配点对提取:通过特征提取算法如SIFT或SURF,提取两张图像中的特征点,并使用匹配算法如k近邻匹配或FLANN进行匹配。 4. 深度估算:根据匹配点对的位置信息和摄像机的参数,可以计算出图像中物体的深度信息。一种常用的方法是triangulation(三角测量)算法。 5. 深度映射:深度信息可以以灰度图或彩色图的形式进行可视化,用于显示图像中每个像素点的深度值。这可以通过将深度值映射到灰度或彩色范围内来实现。 总结来说,OpenCV中的双目深度仿真代码主要涉及摄像机标定、图像对齐、特征提取和匹配、深度估算以及深度映射等步骤。通过这些步骤,可以实现对图像中物体深度的估算和可视化。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值