英孚做网络作业的网站,深圳龙岗区地图全图,简单网页模板代码,杭州品牌网站建设公司最近在做双目测距#xff0c;觉得有必要记录点东西#xff0c;所以我的第一篇博客就这么诞生啦~
双目测距属于立体视觉这一块#xff0c;我觉得应该有很多人踩过这个坑了#xff0c;但网上的资料依旧是云里雾里的#xff0c;要么是理论讲一大堆#xff0c;最后发现还不知…最近在做双目测距觉得有必要记录点东西所以我的第一篇博客就这么诞生啦~
双目测距属于立体视觉这一块我觉得应该有很多人踩过这个坑了但网上的资料依旧是云里雾里的要么是理论讲一大堆最后发现还不知道怎么做要么就是直接代码一贴让你懵逼。 所以今天我想做的是尽量给大家一个明确的阐述并且能够上手做出来。
一、 标定
首先我们要对摄像头做标定具体的公式推导在learning opencv中有详细的解释这里顺带提一句这本书虽然确实老但有些理论、算法类的东西里面还是讲的很不错的必要的时候可以去看看。
Q1为什么要做摄像头标定
A: 标定的目的是为了消除畸变以及得到内外参数矩阵内参数矩阵可以理解为焦距相关它是一个从平面到像素的转换焦距不变它就不变所以确定以后就可以重复使用而外参数矩阵反映的是摄像机坐标系与世界坐标系的转换至于畸变参数一般也包含在内参数矩阵中。从作用上来看内参数矩阵是为了得到镜头的信息并消除畸变使得到的图像更为准确外参数矩阵是为了得到相机相对于世界坐标的联系是为了最终的测距。
ps1关于畸变大家可以看到自己摄像头的拍摄的画面在看矩形物体的时候边角处会有明显的畸变现象而矫正的目的就是修复这个。
ps2我们知道双目测距的时候两个相机需要平行放置但事实上这个是很难做到的所以就需要立体校正得到两个相机之间的旋转平移矩阵也就是外参数矩阵。 Q2如何做摄像头的标定
A这里可以直接用opencv里面的sample在opencv/sources/sample/cpp里面有个calibration.cpp的文件这是单目的标定是可以直接编译使用的这里要注意几点
1.棋盘
棋盘也就是标定板是要预先打印好的你打印的棋盘的样式决定了后面参数的填写具体要求也不是很严谨清晰能用就行。之所用棋盘是因为他检测角点很方便and…你没得选。。
2. 参数
****一般设置为这个样子-w 6 -h 8 -s 2 -n 10 -o camera.yml -op -oe [list_of_views.txt] 这是几个重要参数的含义 -w board_width # 图片某一维方向上的交点个数-h board_height # 图片另一维上的交点个数[-n number_of_frames] # 标定用的图片帧数[-s square_size] # square size in some user-defined units (1 by default)[-o out_camera_params] # the output filename for intrinsic [and extrinsic] parameters[-op] # write detected feature points[-oe] # write extrinsic parameters可以发现 -w -h是棋盘的长和高也就是有几个黑白交点-s是每个格子的长度单位是cm 长和高一定要数对不然程序在识别角点的时候会识别不出来的。最终得到的yml文件就是单目标定的参数矩阵之后使用它就可以得到校正后的图像啦。
3. 需要对程序做一些修改这是我遇到的问题就是他的读取摄像头的代码在我这边没有用所以我自己重新修改了不知道大家会 不会碰到这个问题。 然后就是双目标定了同样的地方找到stereo_calib.cpp这个参数比较简单只要确定长、宽和输入的一个xml文件在之前 的文件夹里面这个文件是为了读取图片用的你需要自己用固定好的双目摄像头拍14对棋盘图片命名为 left01,right01…这样 一系列的名字另外最简单的方法就是把自己拍的照片放到相应的工程下以及stereo开头的那个xml文件也复制过去这个程序代码 并不复杂可以稍微研究一下工程向的代码确实严谨各种情况都考虑到了比起自己之前做的那个小项目不知道高到哪里去了
这里也有几个注意点坑
1.老生常谈的问题长宽一定要写对 这个不多说了都是泪。 2.代码的核心函数 static void StereoCalib(const vector imagelist, Size boardSize, bool useCalibratedtrue, bool showRectifiedtrue)注意搞清楚参数的意义因为我是用的单目标定好的摄像头拍摄的图片不需要再校正了所以第三个参数要用false这样最后的结果才能看不说了都是泪… 3.另外注意到计算rms误差的时候结束条件的几个参数是可以调整的
double rms stereoCalibrate(objectPoints, imagePoints[0], imagePoints[1], cameraMatrix[0], distCoeffs[0], cameraMatrix[1], distCoeffs[1], imageSize, R, T, E, F, TermCriteria(CV_TERMCRIT_ITERCV_TERMCRIT_EPS, 100, 1e-5), CV_CALIB_FIX_ASPECT_RATIO CV_CALIB_ZERO_TANGENT_DIST CV_CALIB_SAME_FOCAL_LENGTH CV_CALIB_RATIONAL_MODEL CV_CALIB_FIX_K3 CV_CALIB_FIX_K4 CV_CALIB_FIX_K5)
下面这段话是某度百科上的
这个函数计算了两个摄像头进行立体像对之间的转换关系。如果你有一个立体相机的相对位置并且两个摄像头的方向是固定的以及你计算了物体相对于第一照相机和第二照相机的姿态R1T1和R2T2各自这个可以通过solvepnp()做到通过这些姿态确定。你只需要知道第二相机相对于第一相机的位置和方向。
除了立体的相关信息该函数也可以两个相机的每一个做一个完整的校准。然而由于在输入数据中的高维的参数空间和噪声的可能偏离正确值。如果每个单独的相机内参数可以被精确估计例如使用calibratecamera()建议这样做然后在本征参数计算之中使CV_CALIB_FIX_INTRINSIC的功能。否则如果一旦计算出所有的参数它将会合理的限制某些参数例如传CV_CALIB_SAME_FOCAL_LENGTH and CV_CALIB_ZERO_TANGENT_DIST这通常是一个合理的假设。 Q3标定之后做什么呢 A: 写到这我发现把单目和双目的一起写确实有点乱…不过开弓没有回头箭不是因为懒
首先还是单目单目的使用很简单使用标定得到的参数进行校正就行了代码如下
void loadCameraParams(Mat cameraMatrix, Mat distCoeffs) { FileStorage fs(“camera.yml”, FileStorage::READ);//这个名字就是你之前校正得到的yml文件
fs[“camera_matrix”] cameraMatrix; fs[“distortion_coefficients”] distCoeffs; }
Mat calibrator(Mat view)//需要校正处理的图片 { vector imageList; static bool bLoadCameraParams false; static Mat cameraMatrix, distCoeffs, map1, map2; Mat rview; Size imageSize, newImageSize;
if (!view.data) return Mat();
imageSize.width view.cols; imageSize.height view.rows;
newImageSize.width imageSize.width; newImageSize.height imageSize.height;
if (bLoadCameraParams false) { loadCameraParams(cameraMatrix, distCoeffs); bLoadCameraParams true; initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(), getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, newImageSize, 0), newImageSize, CV_16SC2, map1, map2); }
//undistort( view, rview, cameraMatrix, distCoeffs, cameraMatrix ); remap(view, rview, map1, map2, INTER_LINEAR);
imshow(“左图”, rview); //int c 0xff waitKey();
rview.copyTo(view);
return view; }
** 这样最后就可以得到校正后消除畸变的图片。
** OK接下来显然就是双目啦双目校正之后的工作就比较多了我准备另开一节来说…**
二、立体匹配
这是一个很大的题目网上的资料也很多所以我想说的是我的一些理解。
这里最好的方法是从后往前说我们首先需要理解测距的原理。这个很多人看了一大堆还不明白其实只有我自己吧…相似三角形测距这种东西小学生都能搞清楚但两摄像头到底怎么做到的就是我们需要搞清楚的。
首先需要搞清楚一个非常重要的概念视差搞清楚视差后面的就简单了 老生常谈的问题我不想多说网上那些一大堆我希望给大家的是一些明了的东西 这三幅图看明白了就行其实视差确实很简单但很多人都没去理清楚第一幅图是三维世界的一个点在两个相机的成像我们可以相信的是这两个在各自相机的相对位置基本不可能是一样的而这种位置的差别也正是我们眼睛区别3D和2D的关键将右边的摄像机投影到左边怎么做呢因为他的坐标和左边相机的左边相距Tx标定测出来的外参数所以它相当于在左边的相机对三维世界内的x-tx,y,z进行投影所以这时候一个完美的形似三角形就出来这里视差就是dx-x‘ 得到视差以后再用相似三角形…也就得到了深度也就是距离啦。
结束了么并没有…这样做确实很完美但是问题来了1.当我在左边相机确定一个点的时候我怎么在右边找到这个点? 2.我左边点所在的行一点跟右边点所在的行上的像素一定完全一样么
解决第一个问题的方法就是立体匹配了。
Q1立体匹配是什么怎么进行立体匹配
A简单的回答就是立体匹配就是解决上面问题的东西啦…其实我觉得这样就是也够了有些成熟的算法未必需要钻研太深毕竟我这种实在的菜鸡还是工程导向的…学术的事日后再说
opencv中提供了很多的立体匹配算法类似于局部的BM全局的SGBM等等这些算法的比较大概是速度越快的效果越差如果不是很追究时效性并且你的校正做的不是很好的话…推荐使用SGBM算法的具体原理大家可以去百度不难。这里我想提一下的是为什么做立体匹配有用原因就是极线约束这也是个很重要的概念理解起来并不难左摄像机上的一个点对应三维空间上的一个点当我们要找这个点在右边的投影点时有必要把这个图像都遍历一边么当然不用… 如上图显然PL对应的P这个点一定在一条极线上只要在这条线上找就行了更明显的是下面这个图 最后怎么在opencv里面实现呢…机智的我又找到了sample…找到stereo_match.cpp这个文件命令行设置为left01.jpg right02.jpg --algorithmhh --blocksize5 --max-disparity256 --scale1.0 --no-display -i intrinsics.yml -e extrinsics.yml -o disparity.jpg同意给几个建议
1.参数的意义
-max-disparity 是最大视差可以理解为对比度越大整个视差的range也就越大这个要求是16的倍数
–blocksize 一般设置为5-29之间的奇数应该是局部算法的窗口大小。
另注意带上参数-i intrinsics.yml -e extrinsics.yml毕竟咱有校正参数…
2.后面有两行代码
reprojectImageTo3D(disp, xyz, Q, true);
saveXYZ(point_cloud_filename, xyz);
**这个就是得到图片的三维坐标Z也就是我们最终要求的深度啦。 第二个问题行和行是对应的么 之前我们说过双目校正的目的就是为了得到两个平行的摄像头所以当程序运行完毕以后它会把两幅图像显示出来并作出一系列的平行线这样你会看到线上的点大致是呈对应关系左边的角点对应右边的交点所以经过匹配和校正后是对应的。
三、总结
双目拖了很久一直没做最重要的原因就是…我没有两个一样的摄像头所以最后也没有贴出效果图因为两个不一样的摄像头做出来的东西画面太美我不敢看不过最终搞清楚了整个流程和原理还是比较开心的。这里面像校正和匹配的算法我只是有所理解因为以后不一定走3D这一块所以也没有过去深入如果用到在去研究其实也不晚