用到的工具和平台
Opencv2.4.11
VS2010
Matlab
参考论文和Blog
基于双目立体视觉的物体深度信息提取系统研究_刘维(硕士论文)
Learning OpenCV十一、十二章
摄像头的连接
技术参数(最终效果)
———————————————------———————————————————
本文主要是讲实现,并不详细讲述数学推理。
测距原理
测距原理是利用三角形原理(当然这个是对于平面模型而言,立体模型则用矩阵来表示),原理如下图所示:
立体模型用下面的矩阵关系来表示:
立体标定
标定的原因:第一个是确定摄像头的坐标原点,焦距,像素和物理尺寸的比例;第二个是消除透镜畸变的影响。
在opencv中使用两个矩阵来表示
内参数矩阵(intrinstic_matrix):
畸变参数
标定的方法和原理
一般使用棋盘标定发,详细原理可以参考张正友博士的论文
这里简单介绍一下原理,如下图所示:
现实世界的坐标点Q和它投影到图像对应点的坐标q,使用齐次坐标来表示如下
则物体平面到图像平面的映射可以用下面的单应性矩阵H来表示:
Q是已知的真实棋盘的坐标,而图像点q可以通过角点查找的方法来查找,这样根据线性代数的知识,给定足够多的点,就可以得到许多条等式,然后就可以求解未知的内参数。因为市面上的棋盘板很贵,所以建议用a4纸打印然后粘贴到刚性平面上。
立体校正
为了使两个摄像头在数学上对准(而不是物理对准)到同一个观察平面上,定义了一个旋转矩阵R和平移矩阵T,这个两个矩阵可以通过像求取单应性矩阵一样,通过已知的匹配点来计算。
下面这个图可以看出校正的作用
立体匹配
SAD匹配算法
方法就是以左目图像的源匹配点为中心,定义一个窗口D,其大小为(2m+1) (2n+1),统计其窗口的灰度值的和,然后在右图像中逐步计算其左右窗口的灰度和的差值,最后搜索到的差值最小的区域的中心像素即为匹配点。
伪代码
标定nImages=10
squareWidth=200 //棋盘的正方形大小200mm
squareWidth=200
//初始化棋盘角点的世界坐标值
for( i = 0; i < nImages; i++ )
{
n = 0;
for( j = 0; j < boardSize.height; j++ )
for( k = 0; k < boardSize.width; k++ )
objectPoints[i][n++] = cv::Point3f(j*squareWidth, k*squareWidth, 0);
}
//灰度化
cvtColor(left_frame_rgb,left_frame_gray,CV_BGR2GRAY);
//二值化
threshold(left_frame_gray,left_frame_gray,Thresholdness,255,CV_THRESH_BINARY);
//查找角点,角点坐标保存在left_corners
bool left_found =findChessboardCorners(left_frame_gray,ChessBoardSize,left_corners);
//画出角点
drawChessboardCorners(left_frame_match, ChessBoardSize, cv::Mat(left_corners), left_found);从上面的棋盘标定可以获得两个重要的数据点矩阵(数据类型是cv::Mat)objectPoints left_corners
//双目标定由于opencv提供的立体标定函数稳定性不太好,而且精度也不准,所以本实验才用了matlab的立体标定,具体可以参考
stereoCalibrate(
objectPoints, //棋盘实际坐标
imagePoints1, //左图像对应的棋盘角点坐标
imagePoints2, //<span style="font-family: Arial, Helvetica, sans-serif;">左图像对应的棋盘角点坐标</span>
cameraParams1.cameraMatrix, //做摄像头内参数
cameraParams1.distortionCoefficients, //左摄像头畸变参数
cameraParams2.cameraMatrix, //右摄像头内参数
cameraParams2.distortionCoefficients, //右摄像头畸变参数
imageSize, //图像大小
rotation, //旋转矩阵
translation, //平移集中
essential, //本征矩阵
foundational, //基础集中
cv::TermCriteria(cv::TermCriteria::COUNT+cv::TermCriteria::EPS, 30, 1e-6),
stereoParams.flags +
CV_CALIB_FIX_K3 + CV_CALIB_FIX_K4 + CV_CALIB_FIX_K5
);
立体校正<pre name="code" class="cpp">stereoRectify(cameraParams1.cameraMatrix,cameraParams1.distortionCoefficients,cameraParams2.cameraMatrix,cameraParams2.distortionCoefficients,imageSize,rotation,translation,R1,R2, P1, P2, Q, 1024,alpha, imageSize,&roi1, &roi2);//生成立体校正需要的映射矩阵initUndistortRectifyMap(cameraParams1.cameraMatrix,cameraParams1.distortionCoefficients,R1, P1, imageSize,CV_16SC2,remapMatrixs.mX1, remapMatrixs.mY1);initUndistortRectifyMap(stereoParams.cameraParams2.cameraMatrix,stereoParams.cameraParams2.distortionCoefficients,R2, P2, stereoParams.imageSize,CV_16SC2,remapMatrixs.mX2, remapMatrixs.mY2);
立体匹配<pre name="code" class="cpp">cvtColor(frameLeft, img1proc, CV_BGR2GRAY);cvtColor(frameRight, img2proc, CV_BGR2GRAY);remap(img1proc, img1remap, remapMatrixs.mX1, remapMatrixs.mY1, cv::INTER_LINEAR);remap(img2proc, img2remap, remapMatrixs.mX2, remapMatrixs.mY2, cv::INTER_LINEAR);//拓展原始图片cv::Mat img1border, img2border;copyMakeBorder(img1remap, img1border, 0, 0, m_BM.state->numberOfDisparities, 0, IPL_BORDER_REPLICATE);copyMakeBorder(img2remap, img2border, 0, 0, m_BM.state->numberOfDisparities, 0, IPL_BORDER_REPLICATE);// 计算视图差cv::Mat dispBorder;m_BM(img1border, img2border, dispBorder);//裁剪图片cv::Mat m_Calib_Mat_Mask_Roi = cv::Mat::zeros(stereoParams.imageSize.height, stereoParams.imageSize.width, CV_8UC1);cv::Rect Roi(0,0,640,480);cv::rectangle(m_Calib_Mat_Mask_Roi, Roi, cv::Scalar(255), -1);cv::Mat disp;disp = dispBorder.colRange(m_BM.state->numberOfDisparities, img1border.cols);cv::Mat disparity;disp.copyTo(disparity, m_Calib_Mat_Mask_Roi);if (disparity.depth() != CV_8U){ disparity.convertTo(disp8u, CV_8U, 255/(m_BM.state->numberOfDisparities*16.));} //将二维的视差图转换为三维的坐标图reprojectImageTo3D(disp8u,out_3d,remapMatrixs.Q);