这几天把基于opencv C++ api将鱼眼镜头的双目标定以及测距功能实现完毕,效果还可以,至少对齐得非常棒。 这里把其流程及其关键函数在这里总结一下。
对于双目标定而言,opencv一共支持两种模型:普通针孔相机模型和鱼眼相机模型fisheye。后者是opencv3.0后才开始支持的。从使用角度讲,它俩主要差别就在于畸变系数不一样。
双目测距流程一共分为四大步:标定,对齐,匹配以及测距。这点对于普通摄像头模型和鱼眼模型都适用。下面就基于鱼眼摄像头模型来讲解各个步骤具体内容。
标定Calibration包括单目标定和双目标定,前者的输出结果主要是内参(3x3矩阵,包括fx,fy以及cx和cy)和畸变系数(1x4矩阵 K1,K2,K3,K4);后者输出的主要是是外参,即右摄像头基于左摄像头的姿态,包括R和T两个矩阵。
标定一个主要工作就是对着标定板拍图,标定板最好遍布整个图像区域,一般20~30张就足够了。 opencv目前可以对三种pattern的标定板:棋盘格,圆以及非对称圆来找角点,其API如下所示:
case Settings::CHESSBOARD:
found = findChessboardCorners( view, s.boardSize, pointBuf, chessBoardFlags);
break;
case Settings::CIRCLES_GRID:
found = findCirclesGrid( view, s.boardSize, pointBuf );
break;
case Settings::ASYMMETRIC_CIRCLES_GRID:
found = findCirclesGrid( view, s.boardSize, pointBuf, CALIB_CB_ASYMMETRIC_GRID );
角点正确找到后,就可以开始单目标定,其对应API为:
CV_EXPORTS_W double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size,
InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0,
TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON));
单目标定结束后,接下来就是双目标定:
CV_EXPORTS_W double stereoCalibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2,
InputOutputArray K1, InputOutputArray D1, InputOutputArray K2, InputOutputArray D2, Size imageSize,
OutputArray R, OutputArray T, int flags = fisheye::CALIB_FIX_INTRINSIC,
TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON));
这里需要注意的是,双目标定可以基于前面单目标定出来的内参来直接算R和T,也可以将单目内参作为一个初始值来重新迭代计算出新的内参和R以及T。
摄像头内参和外参都有了之后,就可以开始调用下面的API来分别获得左、右摄像头新的旋转矩阵R和内参投影矩阵P。
CV_EXPORTS_W void stereoRectify(InputArray K1, InputArray D1, InputArray K2, InputArray D2, const Size &imageSize, InputArray R, InputArray tvec,
OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags, const Size &newImageSize = Size(),
double balance = 0.0, double fov_scale = 1.0);
紧接着是基于新的矩阵来生成左右摄像头的映射表left_mapx, left_mapy, right_mapx以及right_mapy。
CV_EXPORTS_W void initUndistortRectifyMap(InputArray K, InputArray D, InputArray R, InputArray P,
const cv::Size& size, int m1type, OutputArray map1, OutputArray map2);
有了映射表mapx和mapy,在后面测距的时候就可以调用remap()来对新的测试图片进行校正。
匹配是相对最耗时的步骤,即使前面左右图像对齐后,只需要在行上来匹配。常用的匹配算法有SGBM,BM等等。相对来讲,SGBM兼顾了速度和准确度,因而用的比较多。
Ptr sgbm = StereoSGBM::create(0, 16, 3);
sgbm->setPreFilterCap(63);
sgbm->setBlockSize(pParas->sgbmWindowSize);
int channel_cnt = left_rectify_img.channels();
sgbm->setP1(8 * channel_cnt * pParas->sgbmWindowSize * pParas->sgbmWindowSize);
sgbm->setP2(32 * channel_cnt * pParas->sgbmWindowSize * pParas->sgbmWindowSize);
sgbm->setMinDisparity(0);
sgbm->setNumDisparities(pParas->NumDisparities);
sgbm->setUniquenessRatio(pParas->UniquenessRatio);
sgbm->setSpeckleWindowSize(101);
sgbm->setSpeckleRange(10);
sgbm->setDisp12MaxDiff(-1);
sgbm->setMode(StereoSGBM::MODE_SGBM);
opencv已经将匹配算法 封装的很好了,唯一需要注意的就是参数值得调节会带来不一样得匹配效果。常见的需要调节的参数有:
paras.sgbmWindowSize = 7;
paras.NumDisparities = 16 * 20;
paras.UniquenessRatio = 12;
匹配完成就能得到视差图disparity map。 有了视差图,每个点的Z方向上深度值获取就变得简单了。通过下面公式:
Z = B * fx / d
B是两个摄像头之间的距离,其值等于外参平移矩阵X方向上的绝对值,即abs(T.at
fx则为左摄像头内参矩阵的第一个值m_fisheye_intrinsicsL.val[0]
d则为每个像素在左右摄像头像素坐标系上X方向的差,由前面匹配步骤所得。