OpenCV实战(23)——相机标定
- 0. 前言
- 1. 数字图像成像原理
- 2. 相机标定
- 2.1 执行相机校准
- 2.2 使用已知相机参数校准
- 2.3 使用圆网格进行校准
- 3. 完整代码
- 小结
- 系列链接
0. 前言
我们已经了解了相机如何通过在 2D
传感器平面上投射光线来拍摄 3D
场景,生成的图像准确地表示了在捕获图像的瞬间从特定视点观察场景。然而,图像形成过程消除了与其所表示场景元素的深度有关的所有信息。为了恢复场景的 3D
结构和摄像机的 3D
姿态,我们需要对相机参数进行校准,在本节中,我们将介绍如何执行相机标定,为了更好的校准相机,我们首先简单回顾图像形成原理。
1. 数字图像成像原理
回顾在图像投影关系中介绍的图像形成过程,我们学习了针孔相机模型的原理。具体而言,该模型描述了在 3D
场景中位于位置
(
X
,
Y
,
Z
)
(X, Y, Z)
(X,Y,Z) 的点与其在相机图像位置
(
x
,
y
)
(x, y)
(x,y) 之间的关系:
为了更加深入了解坐标变换,我们添加了一个位于投影中心的参考系,并且令 y
轴指向下方,以确保坐标系与将图像原点放置在图像左上角的约定兼容。最后,我们还确定了图像平面上的一个特殊点——穿过焦点的线与图像平面正交,那么点
(
u
0
,
v
0
)
(u_0, v_0)
(u0,v0) 就是这条线穿过图像平面的像素位置,该特殊点称为主点 (principal point
)。我们可以假设这个主要点位于图像平面的中心,但实际上,这个点可能会偏离中心几个像素,这取决于相机的制造精度。
在学习如何估计图像中的投影关系时,我们了解到针孔模型中相机的基本参数是其焦距和图像平面的大小。我们还了解如何将 3D
点
(
X
,
Y
,
Z
)
(X, Y, Z)
(X,Y,Z) 投影到图像平面上
(
f
X
Z
,
f
Y
Z
)
(f\frac XZ, f\frac YZ)
(fZX,fZY) 处。此外,由于我们处理的是数字图像,因此图像平面上的像素数(即其分辨率)是相机的另一个重要特性。
如果我们想将此坐标转换为像素,需要分别将 2D
图像位置除以像素宽度 (
p
x
p_x
px) 和像素高度 (
p
y
p_y
py)。通过将以物理长度单位(通常以毫米为单位)给出的焦距除以
p
x
px
px,我们可以得到以(水平)像素表示的焦距,我们将此定义为
f
x
f_x
fx;类似地,
f
y
=
f
p
y
f_y = \frac f{p_y}
fy=pyf 定义为以垂直像素单位表示的焦距。因此,完整的投影方程如下:
y
=
f
y
Y
Z
+
v
0
y=\frac {f_yY} Z+v_0
y=ZfyY+v0
其中,
(
u
0
,
v
0
)
(u_0, v_0)
(u0,v0) 是添加到结果中的主点,以便将原点移动到图像的左上角。需要注意的是,像素的物理尺寸可以通过将图像传感器的尺寸(通常以毫米为单位)除以像素数量(水平或垂直)来获得。在现代传感器中,像素一般是方形的,即它们具有相同的水平和垂直尺寸。
我们可以使用矩阵形式重写以上方程,得到最一般形式的完整射影方程:
S
[
x
y
1
]
=
[
f
x
0
u
0
0
f
y
v
0
0
0
1
]
[
r
1
r
2
r
3
t
1
r
4
r
5
r
6
t
2
r
7
r
8
r
9
t
3
]
[
X
Y
Z
1
]
S\left[ \begin{array}{ccc} x\\ y\\ 1\\\end{array}\right]=\left[ \begin{array}{ccc} f_x&0&u_0\\ 0&f_y&v_0\\ 0&0&1\\\end{array}\right]\left[ \begin{array}{ccc} r_1&r_2&r_3&t_1\\ r_4&r_5&r_6&t_2\\ r_7&r_8&r_9&t_3\\\end{array}\right]\left[ \begin{array}{ccc} X\\ Y\\ Z\\ 1\\\end{array}\right]
S
xy1
=
fx000fy0u0v01
r1r4r7r2r5r8r3r6r9t1t2t3
XYZ1
2. 相机标定
相机标定 (Camera calibration
,也称相机校准)是获取不同相机参数的过程,我们可以使用相机制造商提供的规格数据,但是对于某些任务而言(例如 3D
重建),这些规格数据不够准确。相机校准通过向相机显示已知图案并分析获得的图像来工作,然后优化过程将确定解释观察结果的最佳参数值。这是一个复杂的过程,但我们可以利用 OpenCV
的标定函数来简化此过程。
要校准相机,需要向它展示一组已知 3D
位置的场景点。然后,需要观察这些点在图像上的投影位置。有了足够数量的 3D
点和相关的 2D
图像点,就可以从投影方程中推断出准确的相机参数,显然,为了获得准确的结果,我们需要观察尽可能多的点。实现此目的的一种方法是拍摄具有许多已知 3D
点的场景照片,但在实践中,可行性并不高。更方便的方法是从不同的视点拍摄一组 3D
点的多张图像,但是这种方法除了计算相机内部参数之外,还需要计算每个相机视点的位置,这种方法具有很高的可行性。
OpenCV
建议使用棋盘模式来生成校准所需的 3D
场景点集。棋盘模式在每个正方形的角上创建点,并且由于这种模式是平坦的,我们可以假设板 (board
) 位于
Z
=
0
Z = 0
Z=0,x
轴和 y
轴根据网格对齐。在这种情况下,校准过程从不同视点向相机显示棋盘图案。以下是 6x4
校准图案图像的示例:
OpenCV
中包含可以自动检测这个棋盘图案的角点的函数。
2.1 执行相机校准
在本节中,我们将使用 OpenCV
算法来检测棋盘点并计算相机校准。
(1) 只需提供图像和所用棋盘的大小(即水平和垂直内角点的数量),cv::findChessboardCorners
函数将返回这些棋盘角在图像上的位置,如果函数未能得到相机参数模型,则函数返回 false
:
// 输出图像点向量
std::vector<cv::Point2f> imageCorners;
// 棋盘内角点数
cv::Size boardSize(7, 5);
// 获取棋盘角点
bool found = cv::findChessboardCorners(image, boardSize, imageCorners);
(2) 输出参数 imageCorners
包含检测到的图案内角点的像素坐标,可以使用额外的参数对算法进行微调。函数 drawChessboardCorners
可以在棋盘图像上绘制检测到的角点,并用线将它们按顺序连接起来:
// 绘制棋盘角点
cv::drawChessboardCorners(image,
boardSize, imageCorners,
found); // 角点是否已检测
得到结果图像如下所示:
连接点的线显示了点在检测到的图像点向量中的顺序。要执行相机校准,我们需要指定相应的 3D
点。
(3) 可以以所选择的单位(例如,以厘米或英寸为单位)指定这些点;然而,但最简单的方法是假设每个方块代表一个单位。在这种情况下,假设板的深度为
Z
=
0
Z = 0
Z=0,则第一个点的坐标为
(
0
,
0
,
0
)
(0, 0, 0)
(0,0,0),第二个点将为
(
1
,
0
,
0
)
(1, 0, 0)
(1,0,0),依此类推,直到最后一个点
(
7
,
5
,
0
)
(7, 5, 0)
(7,5,0)。此模式中共有 48
个点,不足以获得准确的校准。
(4) 要获得更多的点,需要从不同的角度展示更多相同校准图案的图像。为此,可以在相机前移动图案或在图案前移动相机,从数学角度来看,两者是完全等价的。OpenCV
校准函数假设参考系固定在校准模式上,并将计算相机相对于参考系的旋转和平移。我们将校准过程封装在 CameraCalibrator
类中,类属性如下:
class CameraCalibrator {
private:
// 输入点:这些点位于世界坐标系
// 每个方形是一个单元
std::vector<std::vector<cv::Point3f> > objectPoints;
// 图像点像素位置
std::vector<std::vector<cv::Point2f> > imagePoints;
// 输出矩阵
cv::Mat cameraMatrix;
cv::Mat distCoeffs;
// 用于指定校准是否完成的标志
int flag;
(5) 场景和图像点输入向量,实际上是 std::vector
类的向量,每个元素都是一个视图中点的向量。我们通过指定棋盘图像文件名的向量作为 addChessBoardPoints
类函数的输入来添加校准点:
// 打开棋盘文件并提取角点
int CameraCalibrator::addChessboardPoints(
const std::vector<std::string>& filelist, // 包含棋盘图像的文件名列表
cv::Size& boardSize, // 板尺寸
std::string windowName) {
(6) 在 addChessBoardPoints
函数中,我们首先必须初始化向量并设置棋盘的 3D
场景点:
// 棋盘上的点
std::vector<cv::Point2f> imageCorners;
std::vector<cv::Point3f> objectCorners;
// 3D 场景点
// 初始化在棋盘参考帧中的棋盘角点
// 角点在 3D 场景的位置 (X, Y, Z) = (i, j, 0)
for (int i=0; i<boardSize.height; i++) {
for (int j=0; j<boardSize.width; j++) {
objectCorners.push_back(cv::Point3f(i, j, 0.0f));
}
}
(7) 我们必须读取输入列表的每个图像并使用 cv::findChessboardCorners
函数找到棋盘的角点:
// 2D 图像点——棋盘图像
cv::Mat image;
int successes = 0;
// 对于所有视点
for (int i=0; i<filelist.size(); i++) {
image = cv::imread(filelist[i], 0);
// 棋盘角点
bool found = cv::findChessboardCorners(image, // 棋盘图像
boardSize, // 棋盘尺寸
imageCorners); // 角点列表
(8) 此外,为了获得更准确的图像点位置,可以使用 cv::cornerSubPix
函数,然后将图像点定位在亚像素级别的精度上。由 cv::TermCriteria
对象指定的终止标准定义了子像素坐标中的最大迭代次数和最小精度级别,只要其中任一条件得到满足就会停止角点检测过程:
// 在角点上获取亚像素精度
if (found) {
cv::cornerSubPix(image, imageCorners,
cv::Size(5, 5), // 搜索窗口的一半大小
cv::Size(-1, -1),
cv::TermCriteria(cv::TermCriteria::MAX_ITER+cv::TermCriteria::EPS,
30, // 最大迭代次数
0.1)); // 最小准确率
if (imageCorners.size()==boardSize.area()) {
// 根据视图添加图像和场景点
addPoints(imageCorners, objectCorners);
successes++;
}
}
if (windowName.length()>0 && imageCorners.size()==boardSize.area()) {
// 绘制角点
cv::drawChessboardCorners(image, boardSize, imageCorners, found);
cv::imshow(windowName, image);
cv::waitKey(100);
}
}
return successes;
}
(9) 成功检测到一组棋盘角点后,使用 addPoints
方法将这些点添加到图像和场景点的向量中。 一旦处理了足够数量的棋盘图像(因此,有大量的 3D
场景点或 2D
图像点),就可以启动校准参数的计算:
// 相机标定
double CameraCalibrator::calibrate(const cv::Size imageSize) {
// 初始化
mustInitUndistort= true;
// 输出旋转和平移
std::vector<cv::Mat> rvecs, tvecs;
// 相机标定
return calibrateCamera(objectPoints, // 3D 点
imagePoints, // 图像点
imageSize, // 图像尺寸
cameraMatrix, // 输出相机矩阵
distCoeffs, // 输出失真矩阵
rvecs, tvecs, // Rs, Ts
flag); // 设置选项
// ,CV_CALIB_USE_INTRINSIC_GUESS);
}
在实践中,10
到 20
个棋盘图像就足够了,但这些图像必须从不同深度/不同视角拍摄,函数的两个重要输出是相机矩阵和失真参数。
为了解释校准的结果,我们需要回顾投影方程,该方程描述了通过连续应用两个矩阵将 3D
点转换为 2D
点的过程。第一个矩阵包含所有的相机参数,称为相机的内在参数 (intrinsic parameters
),这个 3x3
矩阵是 cv::calibrateCamera
函数返回的输出矩阵之一。也可以使用另一个函数 cv::calibrationMatrixValues
显式返回校准矩阵给出的内在参数值。
第二个矩阵用于将输入点表示为以相机为中心的坐标,它由一个旋转向量(一个 3x3
矩阵)和一个平移向量(一个 3x1
矩阵)组成。在我们的校准示例中,参考系放置在棋盘上,因此,必须为每个视图计算刚性变换(由从
r
1
r_1
r1 到
r
9
r_9
r9 的矩阵项表示的旋转分量和由
t
1
t_1
t1 、
t
2
t_2
t2 和
t
3
t_3
t3 表示的平移分量组成)。这些值由 cv::calibrateCamera
函数的输出参数列表给出。旋转和平移分量通常被称为校准的外部参数 (extrinsic parameters
),它们对于每个视图都是不同的。
对于给定的相机或镜头系统,内在参数保持不变。cv::calibrateCamera
提供的校准结果是通过优化过程获得的,该过程旨在计算内在和外在参数,以最小化根据 3D
场景点的投影计算的预测图像点位置与在图像上观察到的实际图像点位置之间的差异。校准期间指定的所有点的差异之和称为重投影误差。
以上测试相机从基于 48
个棋盘图像的校准中获得的内在参数为
f
x
=
409.272
f_x=409.272
fx=409.272 像素、
f
y
=
408.706
f_y=408.706
fy=408.706 像素、
u
0
=
237.248
u_0=237.248
u0=237.248 像素和
v
0
=
171.287
v0=171.287
v0=171.287 像素。校准图像大小为 536x356
像素。从校准结果中,可以看到,主点靠近图像的中心,但偏离了几个像素。查看拍摄校准图像所用相机的制造商规格,这款相机的传感器尺寸为 23.5mmx15.7mm
,因此像素尺寸为 0.0438mm
。计算出的焦距以像素表示,因此将结果乘以像素大小得出的近似焦距为 17.8mm
,这与我们实际使用的镜头焦距一致。
接下来,我们考虑失真参数。在我们提到在针孔相机模型中可以忽略镜头的影响,但这是以用于捕获图像的镜头不会引入光学畸变为前提的。对于质量较低或焦距很短的镜头,并不能简单的忽略镜头的影响。可以看到,在示例中图像中显示的棋盘图案明显扭曲,且距离图像中心越远,这种失真变得越明显,这种畸变称为径向畸变 (radial distortion
)。
可以通过引入适当的改进模型来补偿这些畸变。通过使用一组数学方程来表示由镜头引入的失真,逆向应用这些方程可以消除图像上可见的失真。可以在校准阶段使用其他相机参数获得校正失真的准确转换参数。完成此操作后,来自新校准相机的图像不会再失真。因此,我们需要在校准类 CameraCalibrator
中添加一个额外的方法:
// 移除图像失真
cv::Mat CameraCalibrator::remap(const cv::Mat &image, cv::Size &outputSize) {
cv::Mat undistorted;
if (outputSize.height == -1)
outputSize = image.size();
if (mustInitUndistort) { // 每次校准调用一次
cv::initUndistortRectifyMap(
cameraMatrix, // 计算摄像机矩阵
distCoeffs, // 计算失真矩阵
cv::Mat(), // 可选矫正
cv::Mat(), // 生成不失真的相机矩阵
outputSize, // 未失真尺寸
CV_32FC1, // 输出图类型
map1, map2); // x 和 y 映射函数
mustInitUndistort= false;
}
// 应用映射
cv::remap(image, undistorted, map1, map2,
cv::INTER_LINEAR); // 插值类型
return undistorted;
}
运行此代码可以得到以下结果:
如上图所示,一旦图像没有失真,我们就可以得到规则的透视图像。
为了纠正失真,OpenCV
使用多项式函数将图像点移动到未失真的位置。默认情况下,使用 5
个系数;也可以使用由 8
个系数组成的模型。一旦得到这些系数,就可以根据 cv::initUndistortRectifyMap
函数计算两个 cv::Mat
映射函数(一个用于 x
坐标,一个用于 y
坐标),给出失真图像上图像点在新图像中的未失真位置。cv::remap
函数将输入图像的所有点重新映射到新图像,由于映射为非线性变换,输入图像的某些像素可能映射在输出图像的边界之外,我们可以扩大输出图像的大小以减少这种像素损失。
在相机校准方面有更多可用选项。接下来,我们将对其进行讲解。
2.2 使用已知相机参数校准
当已知相机内在参数的估计值时,可以直接将它们输入到 cv::calibrateCamera
函数,然后它们将用作优化过程中的初始值。只需添加 CALIB_USE_INTRINSIC_GUESS
标志并在校准矩阵参数中输入这些值;也可以为主点 (CALIB_FIX_PRINCIPAL_POINT
) 强加一个固定值,通常可以假设它是中心像素;还可以对
f
x
f_x
fx 和
f
y
f_y
fy (CALIB_FIX_RATIO
) 的焦距施加固定比率,例如假设像素为方形。
2.3 使用圆网格进行校准
OpenCV
还提供了使用实心圆网格校准相机的方案,作为棋盘图案的替代方法。在这种情况下,圆心用作校准点。所用的函数与我们用来定位棋盘角点的函数非常相似:
cv::Size boardSize(7, 7);
std::vector<cv::Point2f> centers;
bool found = cv::findCirclesGrid(image, boardSize, centers);
3. 完整代码
头文件 (CameraCalibrator.h
) 完整代码如下所示:
#if !defined CAMERACALIBRATOR_H
#define CAMERACALIBRATOR_H
#include <vector>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>
class CameraCalibrator {
private:
// 输入点:这些点位于世界坐标系
// 每个方形是一个单元
std::vector<std::vector<cv::Point3f> > objectPoints;
// 图像点像素位置
std::vector<std::vector<cv::Point2f> > imagePoints;
// 输出矩阵
cv::Mat cameraMatrix;
cv::Mat distCoeffs;
// 用于指定校准是否完成的标志
int flag;
// 用于矫正图像失真
cv::Mat map1, map2;
bool mustInitUndistort;
public:
CameraCalibrator() : flag(0), mustInitUndistort(true) {}
// 打开棋盘文件并提取角点
int addChessboardPoints(const std::vector<std::string>& filelist, cv::Size&boardSize, std::string windowName="");
// 添加场景点及其对应图像点
void addPoints(const std::vector<cv::Point2f>& imageCorners, const std::vector<cv::Point3f>& objectCorners);
// 相机标定
double calibrate(const cv::Size imageSize);
// 设定标定标志
void setCalibrationFlag(bool radial8CoeffEnabled=false, bool tangentialParamEnabled=false);
// 移除图像失真
cv::Mat remap(const cv::Mat& image, cv::Size& outputSize);
cv::Mat getCameraMatrix() {return cameraMatrix;}
cv::Mat getDistCoeffs() {return distCoeffs;}
};
#endif
主函数文件 (fastCorners.cpp
) 完整代码如下所示:
#include <iostream>
#include <iomanip>
#include <vector>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
#include "CameraCalibrator.h"
// 打开棋盘文件并提取角点
int CameraCalibrator::addChessboardPoints(
const std::vector<std::string>& filelist, // 包含棋盘图像的文件名列表
cv::Size& boardSize, // 板尺寸
std::string windowName) {
// 棋盘上的点
std::vector<cv::Point2f> imageCorners;
std::vector<cv::Point3f> objectCorners;
// 3D 场景点
// 初始化在棋盘参考帧中的棋盘角点
// 角点在 3D 场景的位置 (X, Y, Z) = (i, j, 0)
for (int i=0; i<boardSize.height; i++) {
for (int j=0; j<boardSize.width; j++) {
objectCorners.push_back(cv::Point3f(i, j, 0.0f));
}
}
// 2D 图像点——棋盘图像
cv::Mat image;
int successes = 0;
// 对于所有视点
for (int i=0; i<filelist.size(); i++) {
image = cv::imread(filelist[i], 0);
// 棋盘角点
bool found = cv::findChessboardCorners(image, // 棋盘图像
boardSize, // 棋盘尺寸
imageCorners); // 角点列表
// 在角点上获取亚像素精度
if (found) {
cv::cornerSubPix(image, imageCorners,
cv::Size(5, 5), // 搜索窗口的一半大小
cv::Size(-1, -1),
cv::TermCriteria(cv::TermCriteria::MAX_ITER+cv::TermCriteria::EPS,
30, // 最大迭代次数
0.1)); // 最小准确率
if (imageCorners.size()==boardSize.area()) {
// 根据视图添加图像和场景点
addPoints(imageCorners, objectCorners);
successes++;
}
}
if (windowName.length()>0 && imageCorners.size()==boardSize.area()) {
// 绘制角点
cv::drawChessboardCorners(image, boardSize, imageCorners, found);
cv::imshow(windowName, image);
cv::waitKey(100);
}
}
return successes;
}
// 添加场景点和相应的图像点
void CameraCalibrator::addPoints(const std::vector<cv::Point2f>& imageCorners, const std::vector<cv::Point3f>& objectCorners) {
// 2D 图像点
imagePoints.push_back(imageCorners);
// 对应 3D 场景点
objectPoints.push_back(objectCorners);
}
// 相机标定
double CameraCalibrator::calibrate(const cv::Size imageSize) {
// 初始化
mustInitUndistort= true;
// 输出旋转和平移
std::vector<cv::Mat> rvecs, tvecs;
// 相机标定
return calibrateCamera(objectPoints, // 3D 点
imagePoints, // 图像点
imageSize, // 图像尺寸
cameraMatrix, // 输出相机矩阵
distCoeffs, // 输出失真矩阵
rvecs, tvecs, // Rs, Ts
flag); // 设置选项
// ,CV_CALIB_USE_INTRINSIC_GUESS);
}
// 移除图像失真
cv::Mat CameraCalibrator::remap(const cv::Mat &image, cv::Size &outputSize) {
cv::Mat undistorted;
if (outputSize.height == -1)
outputSize = image.size();
if (mustInitUndistort) { // 每次校准调用一次
cv::initUndistortRectifyMap(
cameraMatrix, // 计算摄像机矩阵
distCoeffs, // 计算失真矩阵
cv::Mat(), // 可选矫正
cv::Mat(), // 生成不失真的相机矩阵
outputSize, // 未失真尺寸
CV_32FC1, // 输出图类型
map1, map2); // x 和 y 映射函数
mustInitUndistort= false;
}
// 应用映射
cv::remap(image, undistorted, map1, map2,
cv::INTER_LINEAR); // 插值类型
return undistorted;
}
// 设置标定选项
void CameraCalibrator::setCalibrationFlag(bool radial8CoeffEnabled, bool tangentialParamEnabled) {
// 设置用于 cv::calibrateCamera() 函数的标志
flag = 0;
if (!tangentialParamEnabled) flag += cv::CALIB_ZERO_TANGENT_DIST;
if (radial8CoeffEnabled) flag += cv::CALIB_RATIONAL_MODEL;
}
int main() {
cv::Mat image;
std::vector<std::string> filelist;
// 生成棋盘文件列表
for (int i=1; i<=27; i++) {
std::stringstream str;
str << "chessboards/chessboard" << std::setw(2) << std::setfill('0') << i << ".jpg";
std::cout << str.str() << std::endl;
filelist.push_back(str.str());
image= cv::imread(str.str(),0);
}
// 创建标定器对象
CameraCalibrator cameraCalibrator;
// 在棋盘中添加角点
cv::Size boardSize(7,5);
cameraCalibrator.addChessboardPoints(
filelist, // 棋盘文件列表
boardSize, "Detected points"); // 棋盘尺寸
// 相机标定
cameraCalibrator.setCalibrationFlag(true, true);
cameraCalibrator.calibrate(image.size());
// 矫正图像失真
image = cv::imread(filelist[14],0);
cv::Size newSize(static_cast<int>(image.cols*1.5), static_cast<int>(image.rows*1.5));
cv::Mat uImage= cameraCalibrator.remap(image, newSize);
// 相机矩阵
cv::Mat cameraMatrix= cameraCalibrator.getCameraMatrix();
std::cout << " Camera intrinsic: " << cameraMatrix.rows << "x" << cameraMatrix.cols << std::endl;
std::cout << cameraMatrix.at<double>(0,0) << " " << cameraMatrix.at<double>(0,1) << " " << cameraMatrix.at<double>(0,2) << std::endl;
std::cout << cameraMatrix.at<double>(1,0) << " " << cameraMatrix.at<double>(1,1) << " " << cameraMatrix.at<double>(1,2) << std::endl;
std::cout << cameraMatrix.at<double>(2,0) << " " << cameraMatrix.at<double>(2,1) << " " << cameraMatrix.at<double>(2,2) << std::endl;
cv::namedWindow("Original Image");
cv::imshow("Original Image", image);
cv::namedWindow("Undistorted Image");
cv::imshow("Undistorted Image", uImage);
// 存储计算出的矩阵
cv::FileStorage fs("calib.xml", cv::FileStorage::WRITE);
fs << "Intrinsic" << cameraMatrix;
fs << "Distortion" << cameraCalibrator.getDistCoeffs();
cv::waitKey();
return 0;
}
小结
相机标定,也称相机校准,是恢复场景的 3D
结构和摄像机的 3D
姿态的关键步骤,在本节中,我们介绍了相机标定的基本原理,并从零开始实现了相机标定算法。
系列链接
OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解
OpenCV实战(17)——FAST特征点检测
OpenCV实战(18)——特征匹配
OpenCV实战(19)——特征描述符
OpenCV实战(20)——图像投影关系
OpenCV实战(21)——基于随机样本一致匹配图像
OpenCV实战(22)——单应性及其应用