OpenCV基於點陣結構光的主動立體雙目深度圖三維重建算法

System Status: 2 IR cameras + 1 pair laser projector

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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章