和上一篇一样,首先简单介绍一下使用OpenCV的范例代码来进行双目标定。我所使用的版本是3.x,4.x差别不大。
一、stereo_calib.cpp的使用
需要说明的是各个参数,标定板棋盘格上黑白方块交点的横向个数为w,纵向个数为h。s为每个格子的宽度,这个宽度很容易在ps或者word等软件中看到,推荐ps可以切换单位为mm。为了减少标定图像提高标定准确度,3D打印了一个相机固定架和L形的标定板“固定架”,标定板设计时为w=13,h=8。修改代码大约349行左右设置默认参数行为:
cv::CommandLineParser parser(argc, argv,
"{w|13|}{h|8|}{s|4.233333333|}{nr||}{help||}{@input|stereo_calib.xml|}");
并在stereo_calib.xml中输入图像对名称——不一定图像越多越准确,很多标定结果问题都是因为某些“不够好的”图片引起的,所以“够用”就好。并且,为了便于观察识别角点位置的准确程度和顺序的正确性,稍微修改一下程序第370行左右:
StereoCalib(imagelist, boardSize, squareSize, true, true, showRectified);
即displayCorners参数为true来显示角点识别顺序和位置。如果观察时间太短,可以修改124行左右:
char c = (char)waitKey(1000);
单位为毫秒,代码中默认500。
二、标定结果
可以看到我只用了4组图片,效果么,马马虎虎吧——用一把直尺测量的结果和鼠标选定点的结果一致,精度也就在1mm左右。
现在,我们需要intrinsics.yml和extrinsics.yml来进行双目测距了:
三:stereo_match.cpp的代码
羊头挂好了,上狗肉:
Dim img1 As Mat = ImRead(My.Application.Info.DirectoryPath &
"\image\left00.jpg", ImreadModes.Color) Dim img2 As Mat =
ImRead(My.Application.Info.DirectoryPath & "\image\right00.jpg",
ImreadModes.Color) Dim img_size As Size = img1.Size() Dim roi1, roi2 As New
Rect Dim Q As New Mat Dim fs As FileStorage = New FileStorage("intrinsics.yml",
FileStorage.Mode.Read) Dim M1, D1, M2, D2 As Mat M1 = fs.Item("M1") D1 =
fs.Item("D1") M2 = fs.Item("M2") D2 = fs.Item("D2") fs.Open("extrinsics.yml",
FileStorage.Mode.Read) Dim R, T, R1, P1, R2, P2 As New Mat R = fs.Item("R") T =
fs.Item("T") StereoRectify(M1, D1, M2, D2, img_size, R, T, R1, R2, P1, P2, Q,
StereoRectificationFlags.ZeroDisparity, -1, img_size, roi1, roi2) Dim map11,
map12, map21, map22 As New Mat InitUndistortRectifyMap(M1, D1, R1, P1,
img_size, CV_16SC2, map11, map12) InitUndistortRectifyMap(M2, D2, R2, P2,
img_size, CV_16SC2, map21, map22) Dim img1r, img2r As New Mat Remap(img1,
img1r, map11, map12, InterpolationFlags.Linear) Remap(img2, img2r, map21,
map22, InterpolationFlags.Linear) img1 = img1r img2 = img2r
pnlLeft.BackgroundImage = ToBitmap(img1) pnlRight.BackgroundImage =
ToBitmap(img2)
分别选择粉红色版右上角,可以看到输出值倒数第二个为290mm。在代码中首先读取两个yml文件,而后矫正图像并显示,之后计算两点对应的空间点坐标时,使用TriangulatePoints函数即可,得到一个points4D值,其中第三个与Z坐标对应。如果你的需求是得到点云并且希望使用OPENCV的图像匹配算法,可以参照stereo_match.cpp的代码,使用BM,SGBM等算法来得到它,其参数设置方法与之前一致,参数意义大约如下(有错误的地方欢迎指正):
img1_filename = samples::findFile(parser.get<std::string>(0)); //输入的左侧图像
img2_filename = samples::findFile(parser.get<std::string>(1)); //输入的右侧图像 if
(parser.has("algorithm")) { std::string _alg =
parser.get<std::string>("algorithm"); //使用的算法 alg = _alg == "bm" ? STEREO_BM :
_alg == "sgbm" ? STEREO_SGBM : _alg == "hh" ? STEREO_HH : _alg == "var" ?
STEREO_VAR : _alg == "sgbm3way" ? STEREO_3WAY : -1; } numberOfDisparities =
parser.get<int>("max-disparity"); //最大视差,数值要整除16。STEREO_VAR归一化([0,1])
SADWindowSize = parser.get<int>("blocksize"); //SAD窗口大小,数值为奇数。匹配窗口大小 scale =
parser.get<float>("scale"); //缩放比例 no_display = parser.has("no-display");
//显示结果 if( parser.has("i") ) intrinsic_filename = parser.get<std::string>("i");
//输入内部矩阵 if( parser.has("e") ) extrinsic_filename =
parser.get<std::string>("e"); //输入外部矩阵 if( parser.has("o") ) disparity_filename
= parser.get<std::string>("o"); //输出差异图像 if( parser.has("p") )
point_cloud_filename = parser.get<std::string>("p"); //输出点云文件
cv::CommandLineParser parser(argc, argv,
"{@arg1|left00.jpg|}{@arg2|right00.jpg|}{help
h||}{algorithm|sgbm|}{max-disparity|64|}{blocksize|5|}{no-display||}{scale|1|}{i|intrinsics.yml|}{e|extrinsics.yml|}{o||}{p||}");
其中max-disparity和blocksize需要仔细调试,各个算法之间运算速度和效果也有差异可以自行百度一下。
剩下的就是左右图像特征匹配的问题了,可以看到俩相机拍摄的时候曝光、白平衡、角度不同引起的反光@#$&%#^$OOXX问题很多……后面还得一点一点克服。