操作流程如下:(文末附上最近用opencv4.5.3重新写过的源码)

1.请配置好vs环境(此版本是vs2010+opencv2.4.3所写,具体配置自己查吧。http://blog.csdn.net/garfielder007/article/details/50197181)

2.运行界面如下图(很简陋):

                                           

3.第一步点击采集棋盘图按钮,可以在本地电脑获得两个摄像头的数据。

4.有了图片后,点击立体标定便开始处理,获得标定所需的内外参数

5.点测距按钮便可以进行实时测距(不是每次都需要点击采集棋盘格按钮,如果图片了,直接就可以点击立体标定按钮)

6.代码里要说明的两点

 a.   opencv双目测距Dlg.cpp 里CvSize board_sz = cvSize( 8, 6);//此处8,6为你的棋盘格大小,我这边是8*6的大小,你需要需要按照自己的设置

 b.  opencv双目测距Dlg.cpp 里 const float squareSize = 2.5f; //此处为你的棋盘格上一个格子的实际大小,拿个尺子量一下,我这边是2.5cm

7.后续我再详细介绍吧!

源码链接:http://pan.baidu.com/s/1kVLXrzp

  1. 参考资料:

(1)《学习OpenCV》第11章,12章

(2)opencv官网:OpenCV中文Wiki

(3)邹宇华CSDN博客邹宇华的博客_CSDN博客-生活杂烩,小波变换,学术资源领域博主

  1. 流程:
  1. 获取立体标定用图像:
  1. 程序打开左右摄像头,开始实时采集图像
  2. 放置好棋盘格图像于俩摄像头之前,注意棋盘图像尽量平行于俩摄像头,并进行左右平移移动,或者前后移动,要保证整个棋盘在两个摄像头的共同视场范围之内。
  3. 程序会对采集到的一对图像进行有效性判断,若判断结果为有效才保存图像。(当左右两幅图像同时能检测到全部角点视为有效)
  4. 程序在保存完一定对数的图像后会关闭摄像头,立体标定用图像采集完毕。

(2)立体标定:

     a.读入保存好的立体标定图像

     b.用立体标定函数cvStereoCalibrate(const cvMat *objectPoints,

                                const CvMat *imagePoints1,

                                const CvMat *imagePoints2,

                                const CVMat *npoints,

                                CvMat *cameraMatrix1,

                                CvMat *distCoeffs1,

                                CvMat *cameraMatrix2,

                                CvMat *distCoeffs2,

                                CvMat imageSize,

                                CvMat R,

                                CvMat T,

                                CvMat E,

                                CvMat F,

                                CvTermCriteria termcrit

                                int flags=CV_CALIB_FIX_INTRINSIC

                               );

   来获取立体标定参数:

第一个摄像头的内参数矩阵cameraMatrix1,畸变参数distCoeffs1

第二个摄像头的内参数矩阵cameraMatrix2,畸变系数distCoeffs2

联系左右摄像机的旋转矩阵R,和平移向量T

E和F是本征矩阵E和基础矩阵F,矩阵E包含在物理空间中两个摄像机相关的旋转和平移信息;矩阵F除了包含E的信息外还包含了两个摄像机的内参数。由于F包含了这些内参数,因此它可以在像素坐标系中将两台摄像机关联起来。本征矩阵E是单几何意义上的,与成像仪无关,它将左摄像机观测到的点P的物理坐标与右摄像机观测到的相同点的位置关联起来。基础矩阵F则是将一台摄像机平面上的点在图像坐标(像素)上的坐标和另一台摄像机的像平面上的点关联起来。

一旦有了基础矩阵,我们就能够计算极线。

void cvComputeCorrespondEpilines(

const CvMat* points,

int which_image,

const CvMat* fundamental_matrix,

CvMat* correspondent_lines

);

可根据一幅图像中的点列,计算其在另一幅图像中对应的极线。

当两个像平面是完全的行对准时,计算立体视差是最简单的。不幸的是,如前面所讨论的,由于两台摄像机几乎不可能有准确的共面和行对准的成像平面,完美的对准结构在真实的立体系统中几乎不存在的。我们要保证两个摄像机的图像行在校正后是对准的,使得立体匹配(在不同摄像机视场中发现相同点)更可靠。如下函数使用了Bouguet校正方法。

调用立体校正函数cvSteroRectify(

const CvMat *cameraMatrix1,

                    const CvMat *cameraMatrix2,

const CvMat *distCoeffs1,

                    const CvMat *distCoeffs2,

                const CvMat *imageSize,

                const CvMat *R

                const CvMat *T

                      CvMat *Rl

                      CvMat *Rr

                      CvMat *pl

                      CvMat *pr

                      CvMat *Q

                 int flags=CV_CALIB_ZERO_DISPARITY

);

对于这个函数来说,输入的是前面立体标定函数得到的第一个摄像头的内参数矩阵cameraMatrix1,畸变参数distCoeffs1,第二个摄像头的内参数矩阵cameraMatrix2,畸变系数distCoeffs2,棋盘图像大小imageSize,联系左右摄像头的旋转矩阵R和平移矩阵T,返回的是校正后的左右摄像机旋转矩阵Rl和Rr,3×4的左右投影方程Pl和pr,还有一个Q矩阵

调用校正映射函数cvInitUndistortRectifyMap(const CvMat *M,

                     const CvMat *distCoeffs,

                     const CvMat *Rrect,

                     const CvMat *Mrect,

                     CvArr*      mapx,

                     CvArr*      mapy);

如果使用cvStereoRectify()去标定立体摄像机,我们可以从CvStereoRectify()直接读出CvInitUndistortRectifyMap()的输入参数。使用左参数校正左图像,右参数校正右图像。

这个函数被调用两次,左右摄像头各调用一次,具体输入参数为上面立体校正函数所得到的参数

对于左摄像头调用,该函数为:

cvInitUndistortRectifyMap(const CvMat *cameraMatrix1

                     const CvMat *distCoeffs1,

                     const CvMat *Rl,

                     const CvMat *pl,

                     CvArr*      mapx1,

                     CvArr*      mapy1);

输入参数是3×3的左摄像机内参数矩阵cameraMatrix1,左摄像机畸变参数distCoeffs1,校正后的左摄像机矩阵Rl, 3×4的左右投影方程Pl,输出为左摄像头的x,y方向的映射关系矩阵mapx1,mapy1

对于右摄像头调用,该函数为:

cvInitUndistortRectifyMap(const CvMat *cameraMatrix2

                     const CvMat *distCoeffs2,

                     const CvMat *Rr,

                     const CvMat *pr,

                     CvArr*      mapx2,

                     CvArr*      mapy2);

输入参数是3×3的左摄像机内参数矩阵cameraMatrix2,左摄像机畸变参数distCoeffs2,校正后的左摄像机矩阵Rr, 3×4的左右投影方程Pr,输出为左摄像头的x,y方向的映射关系矩阵mapx2,mapy2

保存好Q,mapx1,mapy1,mapx2,mapy2这5个参数矩阵,供下面的匹配测距使用

立体视觉对监控系统、导航、机械人技术这些有实时性能要求的系统来说,变得尤为重要。所以要把立体匹配过程设计成能够快速运行才行。因此我们不能为每次调用cvFindStereoCorrespondenceBM()的匹配过程持续分配内部离散缓存。基于块的匹配参数和内部离散缓存保存在一个叫CvStereoBMState的结构体内。

void cvFindStereoCorrespondenceBM(

const CvArr *leftImage,

const CvArr* rightImage,

CvArr *disparityResult,

Cv StereoBMState *BMState

);

此函数输入校正图像对,能输出视差映射。

立体标定、校正及相应的示例代码可以做立体校正(标定和非标定的方法),同时计算出视差图,并且会显示左右校正后的摄像机图片,并且在上面画几条线,以看校正后效果。

匹配测距

a.读入5个参数Q,mapx1,mapy1,mapx2,mapy2这5个参数矩阵

b.打开左右摄像头

c.在双摄像头前放好标记物体,这里为4×3棋盘格的图像

d.获取一对左右图像

e.对左右图像进行校正,利用cvRemp()函数,这里需要用到读入的mapx1,mapy1对左图像进行校正,用读入到的mapx2,mapy2对右图像进行校正

f.显示校正后的左右图像

g.对左右图像进行角点检测,若能成功检测出所有角点,这里是12个角点,则分别计算左右图像上对应角点所对应的实际空间坐标,获得z值,这里可以用下面公式进行计算:

                                                            

这里Q为读入的Q矩阵,x,y为左图像上角点坐标,d为左图像上角点坐标x的值与它所对应的右图像上的角点坐标xr值的差,那么该对角点所对应的点的三维坐标就是(X/W,Y/W,Z/W),那么Z/W,就是我们关心的距离

把这些点的距离值做一个平均值来作为标记物体离双摄像头的距离

完成后继续获取一帧图像做重复处理

h.若左右图像未能检测到所有角点,就继续获取下一帧图像,

从三维重投影获得深度映射

博主的测距程序中并没有获取视差图,直接获得校正后两张图像上对应物理世界共同点的两个像素点的x偏差,然后利用公式直接计算获得距离。但在《学习Opencv》书中,提到了2个函数,可以用它们来获得深度信息。

cvPerspectiveTransform(

const CvArr *pointsXYD,

CvArr* result3DPoints,

const CvMat *Q

);

void cvReprojectImageTo3D(

CvArr *disparityImage,

CvArr* result3DImage,

CvArr* *Q

);

输出的是大小与输入相同的3通道浮点型图像。

补充:

我的程序在三个工作空间中,“c做图像提供并判断”,“c做立体标定”,“c做实时测距”,该三部分程序是用VS2010+opencv2.4.4来实现的

在第一个文件夹中“c做图像提供并判断”,程序运行后的标定图像保存在”E:\立体标定图像”文件夹中,图像路径保存在“E:\立体标定图路径.txt“文本中

在第二个文件夹中,“c做立体标定“,记得结合具体棋盘标定物体设置  

                                    

图中squareSize的值,该值为棋盘图像中一小格的大小,这里用的一小格为2.5cm,所以squareSize的值为2.5f

该程序中的输出参数保存路径为”E:\输出参数文本.txt”

最终的实时测距效果图如下: 

                                      

补充(2022/12):

对于单摄像机的参数标定,常用到的是cvCalibrateCamera2函数。可以用来计算摄像机的内参数(4个参数,fx, fy,cx,cy),畸变参数(5个参数,3个径向k1,k2,k3和2个切向p1,p2)。内参数直接与棋盘所在空间的3D几何相关(即外参数),而畸变参数则与点集如何畸变的2D集合相关。标定的每个视场图像,需要三个旋转参数和三个平移参数。

cvCalibrateCamera2(

CvMat* object_points,  //N*3矩阵,N=k(图像上的点数)*M(图像数)

CvMat* image_points, //N*2

int* point_counts,  //每个图像点的个数 M*1

CvSize image_size, 

CvMat* intrinsic_maxtrix, //内参数

CvMat* distortion_coeffs, //畸变参数k1,k2,p1,p2,k3

CvMat* rotation_vectors = NULL,//M*3

CvMat* translation_vectors = NULL,//M*3

int flags = 0

);

rotation_vectors和translation_vectors是每个放置棋盘格相对于摄像机的位置。对于rotation_vectors,不是3*3的旋转矩阵,代表棋盘格围绕摄像机坐标系统下三维空间的坐标轴的旋转,其中每个向量的长度表示逆时针旋转的角度。每个旋转向量可以通过调用cvRodrigues2()转换为3*3的旋转矩阵。

cvFindExtrinsicCameraParam2(

const CvMat* object_points,

const CvMat* image_points,

const CvMat* intrinsic_matrix,

const CvMat* distortion_coeffs,

const CvMat* rotation_vector,

const CvMat* translation_vector,

);

该函数的变量与上面函数的相应变量相同,只是内参数矩阵和畸变系数是直接提供而非计算出来的。旋转输出的格式是1*3或者3*1的旋转向量。此可以计算出观察物体相对于相机的位置。

标定摄像机通常是想做两件事情,一个是矫正畸变效应,另个是根据获得的图像重构三维场景。对于单个相机的标定而言,这里只谈校正畸变效应。

这可以通过cvUndistort2()一次性完成所有事项,也可以通过一对cvInitUndistortMap()和cvRemap()来更有效率的处理此事。

void cvInitundistortMap(

const CvMat* intrinsic_matrix,

const CvMat* distortion_coeffs,

cvArr* mapx,

cvArr* mapy

);

void cvUndistort2(

const CvArr* src,

CvArr* dst,

const cvMat* intrinsic_matrix,

const cvMat* distortion_coeffs

);

Opencv摄像机模型与标定最后提供的一次完成标定例子,即是通过获取到指定的棋盘格图像来计算摄像机内参数和畸变参数,以来矫正摄像机图像。

最近又重新用Opencv4.5.5实现了一遍,贴上源码,方便查看:

链接:https://pan.baidu.com/s/1XQUD-gvSi22DBLI2Z_kjGQ 
提取码:hnyv 
 

更多推荐