OpenCV 4.12.0
开源计算机视觉
加载中...
搜索中...
无匹配项
全方位相机标定

本模块包括全方位相机的标定、矫正和立体重建。相机模型在本文中进行了描述

C. Mei 和 P. Rives, Single view point omnidirectional camera calibration from planar grids, in ICRA 2007。

该模型能够对折反射相机和鱼眼相机进行建模,这两种相机都可能具有非常大的视野。

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

B. Li, L. Heng, K. Kevin 和 M. Pollefeys, "A Multiple-Camera System Calibration Toolbox Using A Feature Descriptor-Based Calibration Pattern", in 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_64Fxi 是 Mei 模型的一个单值变量。idx 是一个 CV_32S Mat,用于存储实际用于标定的图像的索引。这是因为某些图像在初始化步骤中失败,因此没有用于最终优化。返回值 *rms* 是重投影误差的均方根。

标定支持一些特性,*flags* 是某些特性的枚举,包括

您可以指定 flags 以在标定期间固定参数。使用“plus”运算符设置多个特性。例如,CALIB_FIX_SKEW+CALIB_FIX_K1 表示固定 skew 和 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)

变量 *distorted* 和 *undistorted* 分别是原始图像和矫正后的图像。*K*、*D*、*xi* 是相机参数。*KNew* 和 *new_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.*

以下四张图像是上述四种类型的矫正图像

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 是最大视差值,SADWindowSizecv::StereoSGBM 的窗口大小。pointType 是定义点云类型的标志,omnidir::XYZRGB 每个点是一个 6 维向量,前三个元素是 xyz 坐标,后三个元素是 rgb 颜色信息。另一种类型 omnidir::XYZ 表示每个点是 3 维的,只有 xyz 坐标。

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

可以看出,它们对齐得很好。变量 disMap 是由 cv::StereoSGBMimageRec1imageRec2 计算出的视差图。上述两张图像的视差图是

image

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