OpenCV基于点阵结构光的深度图三维重建算法

来源:互联网 发布:微信公众号淘宝客 编辑:程序博客网 时间:2024/06/01 10:05

System Status: 2 IR cameras(OV9281) + 1 laser projector(HEPTAGON LIMA2.0-SD-0)

Using Active Stereo method, not coded structure light( coding and decoding) or ToF.

1. OpenCV Stereo BM/SGBM

  • BM算法原理简介

Step1.使用立体相机标定参数对当前StereoCamera出图进行校准,校准后的同一目标点处于相同的水平线上,校准后的图像为left_rectify& right_rectify

Step2.图像预滤波,使用水平sobel算子对图像进行处理,产生新的滤波后图像P_new

Step3.计算SAD(sum ofabsolute difference) cost,上述代价是在SAD窗口中计算得到;

Step4.唯一性检验:视差窗口范围内最低代价是次低代价的(1+ uniquenessRatio/100)倍时,最低代价对应的视差值才是该像素点的视差,否则该像素点处视差为0

Step5.左右一致性检验:左图找到的匹配点,与匹配点在左图中的匹配点为同一点才判断合法,否则为误匹配点。

StereoBM参数说明:

预处理参数:

preFilterCap水平sobel滤波器大小,一般设置31

SADWindowSize计算代价的SAD窗口大小:一般设置9

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

numberOfDisparity视察搜索范围,其值必须是16的整数倍,最大搜索边界= numberOfDisparity + minDisparity

uniquenessRatio唯一性检测参数,最低代价/次低代价> (1 +uniquenessRatio/100),该像素为有效点;

disp12MaxDiff左右一致性检测最大容许误差阈值,默认为1

speckleWindowSize视差连通区域像素点个数大小,对于每一视差点,当连通区域的像素点个数小于speckleWindowSize时,认为该视差值无效,是噪点;

SpeckleRange视差连通条件,在计算一个视差点的连通区域时,当下一像素点的视差变化绝对值大于SpeckleRange就认为下一像素点和当前像素点是不连通的。

这里,结合BM开发经历,列出一些自己认为比较重要的参数:

numberOfDisparities -- 最重要,与需要处理场景内容的深度紧密相关,该值越大,处理的深度范围越广,但是视差误匹配会概率性增多;

SADWindowSize -- 一般选取5,7,9,11等,不同取值结果差异大;

uniquenessRatio -- 最低代价与次低代价的比率,该值越大,则该像素点所得视差值越(苛刻)可靠,视差图空洞较多;

speckleWindowSize -- 视差连通区域像素个数的数量,该值越大,面积较小离散斑点被筛除;

speckleRange -- 视差连通条件,当一个像素与相邻下一个像素视差变化超过该值时,则认为它们是不连通的。

关于BM/SGBM算法的使用与参数设置在很多博客中都有讲到,给出一个SGBM参数详细解释的博客地址点击打开链接

关于BM算法解决视差图黑边的代码:

Mat img1p, img2p;if (STEREO_BM == alg){  copyMakeBoarder(img1, img1p, 0, 0, numberOfDisparities, 0, IPL_BORDER_REPLICATE);  copyMakeBoarder(img2, img2p, 0, 0, numberOfDisparities, 0, IPL_BORDER_REPLICATE);  img1 = img1p;  img2 = img2p;}if (STEREO_BM == alg){  bm->compute(img1, img2, disp);  disp = disp.colRange(numberOfDisparities, img1p.cols);}
2. Laser Pattern

laser pattern投射到1.5m左右人体表面状态:


HEPTAGON LIMA2.0-SD-0 laser pattern:

HEPTAGON laser datasheet:


3. 空洞修复算法

  • holefilling算法流程

    Input:disp –待修复视差图Output:dstDisp -修复后视差图

Step1.找到disp中未计算深度的空点,空点集合设为Ω

Step2.遍历每一空点Ω(e),根据其邻域信息δ(e)判断其是否处于空洞中,如果δ(e)内包含一半以上的深度有效像素(validPixel),则认为其为空洞点;

Step3.使用方形滤波器对空洞点进行填补,利益滤波器与有效像素的加权值补充空洞点处深度值,得到dstDisp

Step4.根据设定的迭代次数(iteration)来,置disp =dstDisp,并重复上述步骤,直至迭代完成,输出结果修复后的dstDisp,并据此生成深度数据。

  • 滤波器及权重设置

采用类似高斯权重设置的方法设置该滤波器权重,离目标像素越远的有效像素,对该空洞点视差值填补的贡献越小。


  • filterSize滤波器大小选择

滤波器目前可选取5x5, 7x7, 9x9, 11x11.

  • validPixel有效像素点数选择

例如:使用5x5的滤波器时,需要对空点周边的24个像素值进行深度有效像素点数量的判断,通常认为,空洞点周边应被有效点所环绕,所以此时有效像素点数至少设置为滤波器包含像素一半以上才合理,可设置为validPixel =12;使用其他size滤波器时,有效像素点数设置也应大于滤波器包含像素一半。

  • iteration迭代次数选择

针对不同的滤波器大小,收敛至较好效果时的迭代次数不一样,需要根据具体场景分析设定。

Source code

void holefilling(Mat _dispSrc, Mat* _dispDst){  int64 t = getTickCount();  if (CV_8UC1 != _dispSrc.type())  {    _dispSrc.convertTo(_dispSrc, CV_8UC1);  }  Mat dispBw;  threshold(_dispSrc, dispBw, dispMin, 255, THRESH_BINARY);  dispBw.convetTo(dispBw, CV_32F, 1.0/255);  Mat dispValid;  _dispSrc.convertTo(dispValid, CV_32F);  int margin = filterSize/2;  Mat dispFilt = _dispSrc;    for (int i = margin; i < dispBw.rows; i++)  {    for (int j = margin; j < dispBw.cols; j++)    {      if (0 == dispBw.at<float>(i, j))      {        Mat filtMat = dispBw(Range(i - margin, i + margin + 1), Range(j - margin, j + margin + 1));        Scalar s = sum(filtMat);        if (s[0] > validPixel)        {          Mat tmpWeight;          multiply(filtMat, domainFilter, tmpWeight);          Scalar s1 = sum(tmpWeight);          Mat valid = dispValid(Range(i - margin, i + margin + 1), Range(j - margin, j + margin + 1));          Mat final;          multiply(tmpWeight, valid, final);          Scalar s2 = sum(final);          dispFilt.at<unsigned char>(i, j) = (unsigned char)(s2[0] / s1[0]);        }      }      else      {        dispFilt.at<unsigned char>(i, j) = (unsigned char)(dispValid.at<unsigned char>(i, j));      }    }  }  *dispDst = dispFilt;  t = getTickCount() - t;  printf("Time Elapsed t : %fms\n", t1*1000/getTickFrequency);}

4. 深度图去噪

Source code

static int depthDenoise(Mat _dispSrc, Mat* _dispDenoise){  Mat contourBw;  threshold(_dispSrc, contourBw, dispMin, 255, THRESH_BINARY);  vector<vector<Point>> contours;  findContours(contourBw, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);  double minArea = 10000*scale;  for (int i = contours.size() - 1; i >= 0; i--)  {    double area = countourArea(contours[i]);    if (area < minArea)    {      contours.erase(contours.begin() + i);    }  }  Mat contourDisp(_dispSrc.size(), CV_8UC1, Scalar(0));  drawContours(contourDisp, contours, Scalar(1), -1);  multiply(_dispSrc, contourDisp, *_dispDenoise);  return 0;}


0 0
原创粉丝点击