和上一篇一样,首先简单介绍一下使用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(0)); //输入的左侧图像
img2_filename = samples::findFile(parser.get(1)); //输入的右侧图像
if (parser.has("algorithm"))
{
std::string _alg = parser.get("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("max-disparity"); //最大视差,数值要整除16。STEREO_VAR归一化([0,1])
SADWindowSize = parser.get("blocksize"); //SAD窗口大小,数值为奇数。匹配窗口大小
scale = parser.get("scale"); //缩放比例
no_display = parser.has("no-display"); //显示结果
if( parser.has("i") )
intrinsic_filename = parser.get("i"); //输入内部矩阵
if( parser.has("e") )
extrinsic_filename = parser.get("e"); //输入外部矩阵
if( parser.has("o") )
disparity_filename = parser.get("o"); //输出差异图像
if( parser.has("p") )
point_cloud_filename = parser.get("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问题很多……后面还得一点一点克服。