OpenCV 4.11.0
开源计算机视觉库
加载中…
搜索中…
无匹配项
全向相机标定

本模块包含全向相机的标定、校正和立体重建。相机模型在以下论文中描述

C. Mei 和 P. Rives,基于平面网格的全向相机单视点标定,ICRA 2007。

该模型能够模拟具有非常大视场的反射式相机和鱼眼相机。

标定部分的实现基于 Li 的标定工具箱

B. Li,L. Heng,K. Kevin 和 M. Pollefeys,“基于特征描述符的标定图案的多相机系统标定工具箱”,IROS 2013。

本教程将介绍全向相机标定模块的以下部分

  • 单相机标定。
  • 立体相机对标定。
  • 校正图像以去除大的畸变。
  • 从具有大视场的两幅立体图像重建 3D。
  • 与 opencv/calib3d/ 中的鱼眼模型比较

单相机标定

相机标定的第一步是获取标定图案并拍摄一些照片。OpenCV 支持几种类型的图案,例如棋盘格和圆形网格。还可以使用名为随机图案的新图案,有关更多详细信息,请参阅 opencv_contrib/modules/ccalib。

下一步是从标定图案中提取角点。对于棋盘格,使用 OpenCV 函数 cv::findChessboardCorners;对于圆形网格,使用 cv::findCirclesGrid;对于随机图案,使用 opencv_contrib/modules/ccalib/src/randomPattern.hpp 中的 randomPatternCornerFinder 类。将图像中角点的坐标保存到类似于 imagePoints 的变量中。imagePoints 的类型可以是 std::vector<std::vector<cv::Vec2f>>,第一个向量存储每一帧的角点,第二个向量存储单个帧中的角点。该类型也可以是 std::vector<cv::Mat>,其中 cv::MatCV_32FC2 类型。

此外,还需要世界(图案)坐标系中的对应 3D 点。如果您知道图案的物理尺寸,您可以自己计算它们。将 3D 点保存在 objectPoints 中,类似于 imagePoints,它可以是 std::vector<std::vector<Vec3f>>std::vector<cv::Mat>,其中 cv::Mat 的类型为 CV_32FC3。请注意,objectPointsimagePoints 的大小必须相同,因为它们是相互对应的。

您应该输入的另一个参数是图像的大小。文件 opencv_contrib/modules/ccalib/tutorial/data/omni_calib_data.xml 存储了 objectPoints、imagePoints 和 imageSize 的示例。使用以下代码加载它们:

cv::FileStorage fs("omni_calib_data.xml", cv::FileStorage::READ);
std::vector<cv::Mat> objectPoints, imagePoints;
cv::Size imgSize;
fs["objectPoints"] >> objectPoints;
fs["imagePoints"] >> imagePoints;
fs["imageSize"] >> imgSize;

然后定义一些变量来存储输出参数并运行标定函数,例如:

cv::Mat K, xi, D, idx;
int flags = 0;
cv::TermCriteria critia(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 200, 0.0001);
std::vector<cv::Mat> rvecs, tvecs;
double rms = cv::omnidir::calibrate(objectPoints, imagePoints, imgSize, K, xi, D, rvecs, tvecs, flags, critia, idx);

KxiD 是内参,rvecstvecs 是存储图案位姿的外参。它们都具有 CV_64F 的深度。xi 是 Mei 模型的单值变量。idx 是一个 CV_32S Mat,存储在标定中实际使用的图像索引。这是因为某些图像在初始化步骤中失败,因此未在最终优化中使用。返回值 *rms* 是重投影误差的均方根。

标定支持一些特性,flags 是枚举一些特性,包括:

您可以指定 flags 在标定过程中固定参数。使用“加号”运算符设置多个特性。例如,CALIB_FIX_SKEW+CALIB_FIX_K1 表示固定倾斜度和 K1。

criteria 是优化过程中的停止条件,例如将其设置为 cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 200, 0.0001),这意味着使用 200 次迭代,并在相对变化小于 0.0001 时停止。

立体标定

立体标定是同时标定两个相机。输出参数包括两个相机的相机参数及其相对位姿。为了恢复相对位姿,两个相机必须同时观察相同的图案,因此两个相机的 objectPoints 是相同的。

现在如上所述检测两个相机的图像角点以获取 imagePoints1imagePoints2。然后计算共享的 objectPoints

立体标定数据的示例存储在 opencv_contrib/modules/ccalib/tutorial/data/omni_stereocalib_data.xml 中。通过以下方式加载数据:

cv::FileStorage fs("omni_stereocalib_data.xml", cv::FileStorage::READ);
std::vector<cv::Mat> objectPoints, imagePoints1, imagePoints2;
cv::Size imgSize1, imgSize2;
fs["objectPoints"] >> objectPoints;
fs["imagePoints1"] >> imagePoints1;
fs["imagePoints2"] >> imagePoints2;
fs["imageSize1"] >> imgSize1;
fs["imageSize2"] >> imgSize2;

然后进行立体标定:

cv::Mat K1, K2, xi1, xi2, D1, D2;
int flags = 0;
cv::TermCriteria critia(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 200, 0.0001);
std::vector<cv::Mat> rvecsL, tvecsL;
cv::Mat rvec, tvec;
double rms = cv::omnidir::stereoCalibrate(objectPoints, imagePoints1, imagePoints2, imgSize1, imgSize2, K1, xi1, D1, K2, xi2, D2, rvec, tvec, rvecsL, tvecsL, flags, critia, idx);

这里rvectvec表示第一个相机和第二个相机之间的变换。rvecsLtvecsL表示标定板和第一个相机之间的变换。

图像校正

全向图像具有非常大的畸变,与人眼视觉不兼容。为了获得更好的视觉效果,如果已知相机参数,可以应用校正。这是一个水平视场角为360度的全向图像示例。

图像

校正后,会生成类似透视的视图。以下是如何在此模块中运行图像校正的一个示例

cv::omnidir::undistortImage(distorted, undistorted, K, D, xi, int flags, Knew, new_size)

变量distortedundistorted分别表示原始图像和校正后的图像。KDxi是相机参数。KNewnew_size是校正后图像的相机矩阵和图像大小。flags是校正类型,可以是:

  • RECTIFY_PERSPECTIVE:校正为透视图像,这会损失一部分视场。
  • RECTIFY_CYLINDRICAL:校正为圆柱图像,保留所有视场。
  • RECTIFY_STEREOGRAPHIC:校正为立体投影图像,可能会损失少量视场。
  • RECTIFY_LONGLATI:校正为经纬度地图,类似于地球的世界地图。这种校正可用于立体重建,但可能不利于观察。此地图在论文中有所描述:Li S. Binocular spherical stereo[J]. Intelligent Transportation Systems, IEEE Transactions on, 2008, 9(4): 589-600.

以下四幅图像是上面描述的四种校正图像

图像
图像
图像
图像

可以观察到,透视校正图像只保留了很少一部分视场,而且看起来不好。圆柱校正保留了所有视场,但场景在下半部分中间看起来不自然。立体投影在下半部分中间的畸变小于圆柱校正,但其他地方的畸变更大,并且它不能保留所有视场。对于畸变非常大的图像,经纬度校正不会给出好的结果,但它可以使极线约束在一条线上,以便可以在全向图像中应用立体匹配。

注意:为了获得更好的结果,应仔细选择Knew,它与您的相机有关。一般来说,较短的焦距导致较小的视场,反之亦然。以下是推荐的设置。

对于RECTIFY_PERSPECTIVE

Knew = Matx33f(new_size.width/4, 0, new_size.width/2,
0, new_size.height/4, new_size.height/2,
0, 0, 1);

0, 0, 1);

对于RECTIFY_CYLINDRICAL, RECTIFY_STEREOGRAPHIC, RECTIFY_LONGLATI
Knew = Matx33f(new_size.width/3.1415, 0, 0,
0, 0, 1);

0, new_size.height/3.1415, 0,

0, 0, 1);

您可能需要更改(u0, v0)以获得更好的视图。

立体重建

立体重建是从已校准的立体相机对重建3D点。这是计算机视觉中的一个基本问题。然而,对于全向相机来说,由于较大的畸变使其略微困难,因此它并不十分流行。传统方法将图像校正为透视图像,并在透视图像中进行立体重建。然而,上一节显示将图像校正为透视图像会损失太多的视场,这浪费了全向相机的优势,即大的视场。

立体重建的第一步是立体校正,使极线成为水平线。在这里,我们使用经纬度校正来保留所有视场,或者使用可用的透视校正,但不推荐。第二步是立体匹配以获得视差图。最后,可以从视差图生成3D点。

图像

全向相机的立体重建API是omnidir::stereoReconstruct。这里我们用一个例子来展示它是如何工作的。

首先,如上所述校准一对立体相机,并获得诸如K1D1xi1K2D2xi2rvectvec之类的参数。然后分别从第一台和第二台相机读取两幅图像,例如image1image2,如下所示。
其次,运行omnidir::stereoReconstruct,例如:
cv::Size imgSize = img1.size();
int numDisparities = 16*5;
int SADWindowSize = 5;
cv::Mat disMap;
int flag = cv::omnidir::RECTIFY_LONGLATI;
int pointType = omnidir::XYZRGB;
// theta的范围是(0, pi),phi的范围是(0, pi)
cv::Matx33d KNew(imgSize.width / 3.1415, 0, 0, 0, imgSize.height / 3.1415, 0, 0, 0, 1);

Mat imageRec1, imageRec2, pointCloud;

cv::omnidir::stereoReconstruct(img1, img2, K1, D1, xi1, K2, D2, xi2, R, T, flag, numDisparities, SADWindowSize, disMap, imageRec1, imageRec2, imgSize, KNew, pointCloud);

这里的变量flag指示校正类型,只有RECTIFY_LONGLATI(推荐)和RECTIFY_PERSPECTIVE才有意义。numDisparities是最大视差值,SADWindowSizecv::StereoSGBM的窗口大小。pointType是一个标志,用于定义点云的类型,omnidir::XYZRGB每个点是一个6维向量,前三个元素是xyz坐标,后三个元素是rgb颜色信息。另一种类型omnidir::XYZ表示每个点是3维的,只有xyz坐标。

图像

此外,imageRec1imagerec2是第一幅和第二幅图像的校正版本。它们的极线具有相同的y坐标,因此立体匹配变得容易。以下是一个例子: