一、相机模型与投影
1、单目相机模型
1.1.1 单目相机成像原理
现实中的相机本质上可以看成一个:
- 小孔成像模型(Pinhole Camera Model)
- 即:光线通过一个针孔投影到成像平面上
1.1.2 小孔成像模型

设:空间三维点:P(X,Y,Z) 成像点:p(x,y) 焦距:f
根据相似三角形关系:,
可以发现:
- 距离相机越远(Z越大)
- 成像越小
1.1.3 相机坐标系
(1)世界坐标系
用于描述真实世界中的物体位置:
(2)相机坐标系
以相机光心为原点:
- Z轴:朝前
- X轴:向右
- Y轴:向下

(3)图像坐标系
单位通常为:
- mm
- 或物理长度

成像平面中心一般为原点。
(4)像素坐标系
OpenCV 最终使用的是像素坐标:(u,v)

- 左上角为原点
- u向右
- v向下
1.1.4 相机内参
相机从"物理成像平面"转换到"像素坐标"时,需要内参矩阵。
相机内参矩阵:
:水平方向焦距(单位:像素)
:垂直方向焦距(单位:像素)
,
:主点坐标(光轴中心),即相机光轴与图像平面的交点,通常接近图像中心
内参矩阵作用:将"归一化成像平面坐标"转换为"像素坐标"。
1.1.5 单目相机完整投影模型
世界坐标 → 相机坐标:
相机坐标 → 成像平面:
,
成像平面 → 像素坐标:
,
2、相机畸变
相机畸变是指:真实镜头成像时,图像发生几何变形的现象。

畸变数学表示:
3、模型投影函数
cpp
/* 用途:用于将三维空间中的点,根据相机参数投影到二维图像平面中。 */
void cv::projectPoints( InputArray objectPoints,
InputArray rvec, InputArray tvec,
InputArray cameraMatrix, InputArray distCoeffs,
OutputArray imagePoints,
OutputArray jacobian = noArray(),
double aspectRatio = 0 );
/*
objectPoints:世界坐标系中3D点的三位坐标,可以是Point3f或Point3d类型
rvec:旋转向量,表示世界坐标系到相机坐标系的旋转关系
tvec:平移向量,表示世界坐标系到相机坐标系的平移关系
cameraMatrix:相机内参矩阵,即相机标定得到的K矩阵
distCoeffs:相机畸变系数,用于描述镜头畸变,对应畸变参数[k1, k2, p1, p2, k3]
imagePoints:输出投影后的二维图像坐标点
jacobian:输出雅可比矩阵,一般用于优化计算,默认情况下不使用
aspectRatio:固定纵横比参数,默认值为0,表示不固定比例
*/
4、示例代码
cpp
/*输入计算得到的内参矩阵和畸变矩阵*/
Mat cameraMatrix = (Mat_<float>(3, 3) << 532.016297, 0, 332.172519,
0, 531.565159, 233.388075,
0, 0, 1);
Mat distCoeffs = (Mat_<float>(1, 5) << -0.285188, 0.080097, 0.001274,
-0.002415, 0.106579);
/*第一张图像相机坐标系与世界坐标系之间的关系*/
Mat rvec = (Mat_<float>(1, 3) << -1.977853, -2.002220, 0.130029);
Mat tvec = (Mat_<float>(1, 3) << -26.88155, -42.79936, 159.19703);
/*生成第一张图像中内角点的三维世界坐标*/
Size boardSize = Size(9, 6);
Size squareSize = Size(10, 10);/*棋盘格每个方格的真实尺寸*/
vector<Point3f> PointSets;
for (int j = 0; j < boardSize.height; j++)
{
for (int k = 0; k < boardSize.width; k++)
{
Point3f realPoint;
/*假设标定版为世界坐标系的z平面,即z=0*/
realPoint.x = j * squareSize.width;
realPoint.y = k * squareSize.height;
realPoint.z = 0;
PointSets.push_back(realPoint);
}
}
/*根据三维坐标和相机与世界坐标系时间的关系估计内角点像素坐标*/
vector<Point2f> imagePoints;
projectPoints(PointSets, rvec, tvec, cameraMatrix, distCoeffs, imagePoints);
for (int i = 0; i < imagePoints.size(); i++)
{
cout << to_string(i) << ": " << imagePoints[i] << endl;
}
waitKey(0);
二、单目相机标定
1、单目标定原理简介
2.1.1 标定的本质问题
单目相机标定的核心目标是:已知三维空间点与其二维图像点的对应关系,求解相机的内参与外参,以及畸变参数。 本质上是一个参数反求问题。
2.1.2 标定输入与输出
(1)输入数据(已知量)
单目标定依赖于多组 3D--2D 对应点:
- 三维点(世界坐标系)
- 标定板上角点的真实物理坐标
- 通常设定在 Z = 0 平面(棋盘格平面)
- 二维点(图像坐标系)
- 图像中检测到的角点像素坐标
(2)输出参数(未知量)
标定求解以下相机参数:
- 内参矩阵 K:
- 畸变参数:通常包括径向畸变:k1,k2,k3,切向畸变:p1,p2
- 外参(每张图像一组):旋转向量:rvec,平移向量:tvec
2.1.3 标定的数学本质
单目标定可以抽象为一个优化问题:
:第 i 张图像第 j 个角点的实际像素坐标
:通过相机模型投影得到的像素坐标
K:内参矩阵
D:畸变参数
,
:第 i 张图像外参
2、标定版角点提取

- 标定版角点提取
cpp
/* 返回值:bool类型,true表示成功检测到完整棋盘角点,false表示检测失败
用途:用于检测棋盘格标定板中的内角点位置,是相机标定流程中的关键步骤 */
bool cv::findChessboardCorners( InputArray image, Size patternSize,
OutputArray corners,
int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE );
/*
image:含有棋盘标定板的图像,图像必须是CV_8U的灰度图像或者彩色图像
patternSize:棋盘格内角点数量,Size(cols, rows),指"每行/每列内角点数",不是方格数
corners:输出检测到的角点坐标,按棋盘扫描顺序排列的2D点集
flags:检测算法标志位,用于提升检测稳定性,
常见选项包括:
CALIB_CB_ADAPTIVE_THRESH:使用自适应阈值处理图像
CALIB_CB_NORMALIZE_IMAGE:归一化图像增强对比度
CALIB_CB_FILTER_QUADS:过滤不符合四边形结构的区域
CALIB_CB_FAST_CHECK:快速初步检测(提高速度,可能降低准确性)
*/
- 圆形标定板中心提取
cpp
/* 返回值:bool类型,true表示成功检测到完整圆点阵列,false表示检测失败
用途:用于检测圆点标定板中的圆心位置,是相机标定中的重要函数之一。 */
bool cv::findCirclesGrid( InputArray image, Size patternSize,
OutputArray centers, int flags,
const Ptr<FeatureDetector> &blobDetector,
const CirclesGridFinderParameters& parameters);
/*
image:输入含有圆形网格的图像,图像必须是CV_8U的灰度图像或者彩色图像
patternSize:圆点阵列的尺寸,Size(cols, rows),表示每行和每列圆形的数目
centers:输出检测到的圆心坐标,按照圆点阵列顺序排列
flags:圆点阵列检测方式标志,常见取值:
CALIB_CB_SYMMETRIC_GRID:对称圆点阵列
CALIB_CB_ASYMMETRIC_GRID:非对称圆点阵列
CALIB_CB_CLUSTERING:使用聚类提高复杂场景鲁棒性
blobDetector:Blob检测器,用于检测图像中的圆形区域,一般使用SimpleBlobDetector
parameters:圆点阵列搜索参数,用于控制圆点搜索过程中的细节配置
*/
- 角点位置优化
cpp
/* 返回值:bool类型,true表示角点优化成功,false表示优化失败
用途:用于对棋盘格等四边形结构角点进行亚像素级精确优化。
该函数会根据角点附近的灰度变化,对原始角点位置进行进一步精细调整,
从而获得更高精度的角点坐标 */
bool cv::find4QuadCornerSubpix( InputArray img,
InputOutputArray corners,
Size region_size );
/*
img:计算处内角点的图像
corners:输入和输出角点坐标,输入为初始检测到的角点,输出为亚像素级精确角点坐标
region_size:角点搜索区域大小,表示在角点周围进行亚像素优化的邻域窗口尺寸
*/
- 绘制内角点提取结果
cpp
/* 用途:用于在图像中可视化显示棋盘格角点检测结果。
该函数会在检测到的角点位置绘制彩色圆点和连接关系,方便观察:
1、棋盘格是否检测成功
2、角点顺序是否正确
3、角点定位是否准确 */
void cv::drawChessboardCorners( InputOutputArray image, Size patternSize,
InputArray corners, bool patternWasFound );
/*
image:需要绘制角点的目标图像,必须是CV_8U的彩色图像
patternSize:棋盘格内角点数量,Size(cols, rows),表示每行和每列内角点个数
corners:检测得到的棋盘格角点坐标集合,通常来自findChessboardCorners()
patternWasFound:棋盘格是否成功检测到的标志,true表示检测成功,false表示检测失败
*/
- 相机标定函数
cpp
/* 返回值:重投影误差(RMS误差),数值越小,标定结果通常越准确
用途:用于根据多张标定板图像,计算相机的内参、畸变参数以及外参。 */
double cv::calibrateCamera( InputArrayOfArrays objectPoints,
InputArrayOfArrays imagePoints, Size imageSize,
InputOutputArray cameraMatrix, InputOutputArray distCoeffs,
OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs,
int flags = 0, TermCriteria criteria = TermCriteria(
TermCriteria::COUNT + TermCriteria::EPS, 30, DBL_EPSILON) );
/*
objectPoints:世界坐标系中的三维点集合,通常为棋盘格内角点的三维坐标
imagePoints:图像坐标系中的二维点集合,通常为棋盘格内角点在图像中的二维坐标
imageSize:输入图像尺寸,即图像宽度和高度
cameraMatrix:相机的内参矩阵,即相机标定后的K矩阵
distCoeffs:相机的畸变系数矩阵,包括径向畸变和切向畸变参数
rvecs:每张标定图像相机坐标系与世界坐标系之间对应的旋转向量
tvecs:每张标定图像相机坐标系与世界坐标系之间对应的平移向量
flags:相机标定控制标志,用于指定优化方式、固定参数等设置
criteria:迭代终止条件,用于控制优化迭代次数和精度
*/
3、示例代码
cpp
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
/*读取所有图像*/
vector<Mat> imgs;
string imageName;
ifstream fin(s_imgPath + "/calibdata.txt");
while (getline(fin, imageName))
{
string fullPath = s_imgPath + "/" + imageName;
cv::Mat img = cv::imread(fullPath);/*left01.jpg left02.jpg left03.jpg left04.jpg*/
if (img.empty())
{
qDebug() << "读取失败:" << QString::fromStdString(fullPath);
continue;
}
imgs.push_back(img);
}
Size board_size = Size(9, 6);/*方格标定板内角点数目(行,列)*/
vector<vector<Point2f>> imgPoints;
for (int i = 0; i < imgs.size(); i++)
{
Mat img1 = imgs[i];
Mat gray1;
cvtColor(img1, gray1, COLOR_BGR2GRAY);
vector<Point2f> img1_points;
findChessboardCorners(gray1, board_size, img1_points);/*计算方格标定板角点*/
find4QuadCornerSubpix(gray1, img1_points, Size(5, 5));/*细化方格标定板角点坐标*/
bool pattern = true;
drawChessboardCorners(img1, board_size, img1_points, pattern);
imshow("img1", img1);
waitKey(0);
imgPoints.push_back(img1_points);
}
/*生成棋盘格每个内角点的空间三维坐标*/
Size squareSize = Size(10, 10);/*棋盘格每个方格的真实尺寸*/
vector<vector<Point3f>> objectPoints;
for (int i = 0; i < imgPoints.size(); i++)
{
vector<Point3f> tempPointSet;
for (int j = 0; j < board_size.height; j++)
{
for (int k = 0; k < board_size.width; k++)
{
Point3f realPoint;
/*假设标定板为世界坐标系的z平面,即z=0*/
realPoint.x = j * squareSize.width;
realPoint.y = k * squareSize.height;
realPoint.z = 0;
tempPointSet.push_back(realPoint);
}
}
objectPoints.push_back(tempPointSet);
}
/*初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板*/
//vector<int> point_number;
//for (int i = 0; i < imgPoints.size(); i++)
//{
// point_number.push_back(board_size.width * board_size.height);
//}
/*图像尺寸*/
Size imageSize;
imageSize.width = imgs[0].cols;
imageSize.height = imgs[0].rows;
Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));/*摄像机内参矩阵*/
Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0));/*摄像机的5个畸变系数,k1,k2,p1,p2,k3*/
vector<Mat> rvecs;/*每幅图像的旋转向量*/
vector<Mat> tvecs;/*每幅图像的平移向量*/
calibrateCamera(objectPoints, imgPoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs, 0);
cout << "cameraMatrix: " << endl << cameraMatrix << endl;
cout << "distCoeffs: " << endl << distCoeffs << endl;
waitKey(0);
destroyAllWindows();