OpenCV  4.10.0
开源计算机视觉
正在加载...
正在搜索...
无匹配项
全景摄像机校准

本模块包括全景摄像机的校准、整平和立体重建。摄像机模型在此技术文件中进行了说明

C. Mei 和 P. Rives,2007 年国际机器人和自动化会议上所述的平面的单视点全景摄像机校准。

该模型能够对具有超大视野的对透射摄像机和鱼眼摄像机进行建模。

校准部分的实现基于 Li 的校准工具箱

B. Li、L. Heng、K. Kevin 和 M. Pollefeys, 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 度水平视野的全景图像示例。

image

校正后,生成类似透视图的视图。下面是一个在这个模块中运行图像校正的示例

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. 双目球面立体声[J]。智能交通系统,IEEE 期刊,2008 年,9(4):589-600。

以下四幅图像为以上描述的四种校正图像类型

image
image
image
image

可以观察到,透视校正图像仅保留了一小部分视野,并且不好看。柱面校正保留了所有视野,并且场景仅在下方的中间是不自然的。正射的底部中间的畸变小于柱面校正,但其它位置的畸变较大,并且不能保留所有视野。对于具有非常大的畸变的图像,经纬校正不会产生良好的效果,但可以使极线约束成一条直线,从而可以将立体匹配应用于全景图像。

注意:为了获得更好的效果,您应仔细选择 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);

对于 RECTIFY_CYLINDRICAL, RECTIFY_STEREOGRAPHIC, RECTIFY_LONGLATI

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

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

立体重建

立体重建是从校准的立体相机对重建 3D 点。这是一个计算机视觉的基本问题。不过,对于全景相机来说,它并不是很受欢迎,这是因为大幅畸变使之有些困难。传统方法将图像校正为透视图像并在透视图像中进行立体重建。不过,最后一节显示校正为透视图像会丢失过多的视野,这浪费了全景相机的优势,即大视野。

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

全景相机的立体重建的 API 为 omnidir::stereoReconstruct。我们在此使用一个示例来说明其如何工作。

首先,如上所述校准一对立体相机并获取诸如 K1D1xi1K2D2xi2rvectvec 的参数。然后分别从第一个和第二个相机读取两幅图像,例如 image1image2,如下所示。

image

其次,运行 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 是最大视差值,SADWindowSize 是 cv::StereoSGBM 的窗口大小。pointType 是用于定义点云类型的标志,omnidir::XYZRGB 每个点都是一个六维向量,前三个元素是 xyz 坐标,后三个元素是 rgb 颜色信息。另一种类型 omnidir::XYZ 表示每个点都是三维的,并且只有 xyz 坐标。

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

可以观察到它们对齐得很好。变量 disMap 是 cv::StereoSGBM 根据 imageRec1 和 imageRec2 计算出的视差图。以上两幅图像的视差图是

image

在获得视差后,我们可以为每个像素计算 3D 位置。点云存储在 pointCloud 中,这是一个 3 通道或 6 通道 cv::Mat。我们在下图中显示点云。