前面多多少少記錄一些相關知識,由於相關工作還在繼續,加上網上的教程總不是十分完善。這裏做一個總結,希望自己能夠加深對這個過程的整體的理解與認識。
基本步驟
- 相機標定
- 圖片採集
- 立體校正
- 匹配算法
- 三維重構
- 點雲去噪
- 點雲顯示
相機標定
使用的Matlab標定工具箱,需要注意的點有:
- 每個相機單獨標定,之後再標定雙目相機的位姿
- 標定單目時需要選擇畸變個數,一般如果不是魚眼鏡頭,只需要標定兩個徑向畸變、兩個切向畸變。
- 圖像採集,和標定精度測試移步這裏
- Matlab標定參數用於opencv移步這裏
圖像採集
我使用的非常基礎的USB雙目相機,兩個相機之間居然還存在色差。。。videoCapture就可以了
立體校正
立體校正對於雙目的最主要的目的是爲稠密匹配算法提供便利。opencv中有具體的立體校正函數
stereoRectify(cameraMatrixL, distCoeffL, cameraMatrixR, distCoeffR,
imageSize, R, T, Rl, Rr, Pl, Pr, Q, CALIB_ZERO_DISPARITY,
0, imageSize, &validROIL, &validROIR);
//再採用映射變換計算函數initUndistortRectifyMap得出校準映射參數,該函數功能是計算畸變矯正和立體校正的映射變換
initUndistortRectifyMap(cameraMatrixL, distCoeffL, Rl, Pl(Rect(0, 0, 3, 3)),
imageSize, CV_32FC1, mapLx, mapLy);
initUndistortRectifyMap(cameraMatrixR, distCoeffR, Rr, Pr(Rect(0, 0, 3, 3)),
imageSize, CV_32FC1, mapRx, mapRy);
remap(grayImageL, rectifyImageL, mapLx, mapLy, INTER_LINEAR);
remap(grayImageR, rectifyImageR, mapRx, mapRy, INTER_LINEAR);//立體校正完成的圖片即保存在rectifyImageL/R中
匹配算法
使用SGM算法計算左目視差,其實爲了增加準確程度,也會計算右目的視差用於補償左目視差。但是爲了實時性,在這裏只計算了左目視差。我自己學習了SGM算法的源代碼,也可以使用opencv自帶的函數,具體如何使用網上教程很多很多,這裏只給出一個示例:
cv::Ptr<cv::StereoSGBM> sgbm = StereoSGBM::create(0, 16, 3);
sgbm->setPreFilterCap(63);
int SADWindowSize = 5;
int sgbmWinSize = SADWindowSize > 0 ? SADWindowSize : 3;
sgbm->setBlockSize(sgbmWinSize);
int cn = rectifyImageL.channels();
sgbm->setP1(8 * cn*sgbmWinSize*sgbmWinSize);
sgbm->setP2(32 * cn*sgbmWinSize*sgbmWinSize);
sgbm->setMinDisparity(0);
sgbm->setNumDisparities(numberOfDisparities);
sgbm->setUniquenessRatio(10);
sgbm->setSpeckleWindowSize(100);
sgbm->setSpeckleRange(1);
sgbm->setDisp12MaxDiff(1);
sgbm->setMode(cv::StereoSGBM::MODE_SGBM);
sgbm->compute(rectifyImageL,//250, 100, 250, 100;150, 100, 400, 300
rectifyImageR, disp);
三維重構
我只想說,opencv挺好用
disp.convertTo(disp8, CV_32F, 255 / (numberOfDisparities * 16.)); //1.0/16計算出的視差是CV_16S格式 CV_32F, 255 / ((numDisparities * 16 + 16)*16.) 255 / (numberOfDisparities*16.)
reprojectImageTo3D(disp8, xyz, Q, true);//Q是之前立體校正得到的參數計算3D點雲保存在xyz中
點雲去噪
在上述過程中獲得了稠密點的座標xyz,將其賦值給點雲,使用點雲濾波即可去噪。PCL點雲庫的使用移步這裏
void two_Eyes::cal_cloud(int disflag)
{
double plane[3];
mutex1.lock();
param_3D.xyz.copyTo(xyz);
plane[0] = param_3D.plane[0];
plane[1] = param_3D.plane[1];
plane[2] = param_3D.plane[2];
mutex1.unlock();
double fenmu = sqrt(powf(plane[0], 2) + powf(plane[1], 2) + 1);
double Wx, Wy, Wz;
vector<double>().swap(dis); //清楚dis中的內容
double t_c = (double)clock();
cvtColor(rectifyImageL, rectifyImageL, CV_GRAY2RGB);
int rowNumber = rectifyImageL.rows;
int colNumber = rectifyImageL.cols;
pcl::PointCloud<pcl::PointXYZRGB> cloud_a2,cloud_a3;
cloud_a2.height = rowNumber;
cloud_a2.width = colNumber;
cloud_a2.points.resize(colNumber* rowNumber);
for (unsigned int u = 0; u < rowNumber; ++u)
{
for (unsigned int v = 0; v < colNumber; ++v)
{
/*unsigned int num = rowNumber*colNumber-(u*colNumber + v)-1;*/
unsigned int num = u * colNumber + v;
cloud_a2.points[num].b = rectifyImageL.at<Vec3b>(u, v)[0];
cloud_a2.points[num].g = rectifyImageL.at<Vec3b>(u, v)[1];
cloud_a2.points[num].r = rectifyImageL.at<Vec3b>(u, v)[2];
double Xw = 0, Yw = 0, Zw = 0;
if (xyz.at<Vec3f>(u, v)[2] < 1000&& xyz.at<Vec3f>(u, v)[2]!=0) //使用OpenCV計算的結果,感覺更加準確
{
Wx= xyz.at<Vec3f>(u, v)[0];
Wy= xyz.at<Vec3f>(u, v)[1];
Wz= xyz.at<Vec3f>(u, v)[2];
cloud_a2.points[num].x = Wx/1000;
cloud_a2.points[num].y = Wy/1000;
cloud_a2.points[num].z = Wz/1000;
}
}
}
///濾波示例
//// 創建pcl::StatisticalOutlierRemoval濾波器對象
//pcl::StatisticalOutlierRemoval<pcl::PointXYZRGB> sor;
//sor.setInputCloud(cloud_a2.makeShared());
//sor.setStddevMulThresh(1.0);
////將標準差倍數設爲1,意味着超過一點的距離超出平均距離一個標準差以上,
////則該點被標記爲離羣點,將被移除。
//sor.filter(cloud_a3); //濾波結果存儲於此
mutex1.lock();
param_3D.cloud_a = cloud_a2;
mutex1.unlock();
cloud_test.close();
}
點雲顯示
可以直接使用PCL庫
我是在QT界面上使用VTK顯示的點雲,這裏涉及到更多的QT界面的基礎知識,不再贅述
結果如圖:
點雲示例:
總結
細心可以避免很多小錯誤
不怕自己做不好,就怕自己停止嘗試
由於相機編號0,1和左右不一定是對應的,所以一定要搞清楚再運行算法,別問我是咋知道的~