Contents

camera:单目相机标定

本文采用知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。

相机标定内容

相机标定的目标是估计相机的 内参矩阵外参(位姿) 以及 畸变参数 等,使我们能够在「像素坐标 ↔ 相机坐标/世界坐标」之间可靠地转换。

内参

相机内参矩阵是相机自身的属性,一般写作:

[ K= \begin{bmatrix} f_x & \gamma & u_0 \ 0 & f_y & v_0 \ 0 & 0 & 1 \end{bmatrix} ]

其中各个量含义为:

f:物理焦距,单位毫米(更偏物理概念,在几何推导中常用);
fx:x 方向的「等效焦距」,以像素为单位;
fy:y 方向的「等效焦距」,以像素为单位;
u₀, v₀:主点坐标(成像平面上光轴与成像平面交点),单位为像素;
γ:像素坐标轴的倾斜因子,理想情况下为 0(像素为正交网格)。

在多数工程场景中,γ 通常直接固定为 0,不参与优化。

外参

外参描述「相机坐标系与世界坐标系之间的刚体变换」,通常记作旋转矩阵 R 和平移向量 t,或者其等价的旋转向量 rvec:

旋转矩阵/旋转向量:描述世界坐标系相对于相机坐标系的朝向变化;
平移向量:描述在相机坐标系下,世界坐标系原点的位置。

不同库(包括 OpenCV)在「相机看世界」还是「世界看相机」上的约定略有不同,但本质都是一组 [R | t] 刚体变换。

畸变参数

常见的针孔+畸变模型会包含:

  • 径向畸变系数:k1, k2, k3(有的模型还会扩展到 k6),主要描述桶形/枕形畸变;
  • 切向畸变系数:p1, p2,主要来自镜头装配时光轴与图像平面不完全平行;
  • 在广角/鱼眼镜头、工业相机中,还可以启用更加复杂的棱镜畸变、倾斜传感器模型等(OpenCV 对应 CALIB_RATIONAL_MODELCALIB_THIN_PRISM_MODELCALIB_TILTED_MODEL 等标志位)。

径向畸变主要发生在「相机坐标 → 图像物理坐标」的映射过程中;切向畸变则更多与成像几何误差有关。通常越靠近成像边缘,畸变越明显。

相机内参和外参的解释

相机内参的标定方法

相机标定步骤

https://docs.opencv.org/3.4.0/d9/d0c/group__calib3d.html

提取角点

棋盘格角点检测

1
2
3
4
5
bool findChessboardCorners( InputArray image, 
                                Size patternSize, 
                                OutputArray corners,
                                int flags = CALIB_CB_ADAPTIVE_THRESH + 
                                CALIB_CB_NORMALIZE_IMAGE );

第一个参数是输入的棋盘格图像(可以是8位单通道或三通道图像); 第二个参数是棋盘格内部的角点的行列数(注意:不是棋盘格的行列数,如棋盘格的行列数分别为4、8,而内部角点的行列数分别是3、7,因此这里应该指定为cv::Size(3, 7)); 第三个参数是检测到的棋盘格角点,类型为std::vectorcv::Point2f。 第四个参数flag,用于指定在检测棋盘格角点的过程中所应用的一种或多种过滤方法,可以使用下面的一种或多种,如果都是用则使用OR: cv::CALIB_CB_ADAPTIVE_THRESH:使用自适应阈值将图像转化成二值图像 cv::CALIB_CB_NORMALIZE_IMAGE:归一化图像灰度系数(用直方图均衡化或者自适应阈值) cv::CALIB_CB_FILTER_QUADS:在轮廓提取阶段,使用附加条件排除错误的假设 cv::CALIB_CV_FAST_CHECK:快速检测

对粗提取的角点进行精确化

1
2
3
bool find4QuadCornerSubpix( InputArray img, 
                      InputOutputArray corners, 
                      Size region_size );

image源图像 corners,提供角点的初始坐标 region_size: 搜索窗口的一般尺寸\

亚像素检测

1
2
3
4
5
void cornerSubPix( InputArray image, 
                       InputOutputArray corners,
                       Size winSize, 
                       Size zeroZone,
                       TermCriteria criteria );

image源图像 corners,提供角点的初始坐标,返回更加精确的点 winSize,搜索窗口的一般尺寸,如果winSize=Size(5,5),则search windows为11*11 winSize,死区的一般尺寸,用来避免自相关矩阵的奇点,(-1,-1)表示没有死区 criteria,控制迭代次数和精度

棋盘格角点的绘制

1
2
3
4
void drawChessboardCorners( InputOutputArray image, 
                           Size patternSize,
                           InputArray corners, 
                           bool patternWasFound );

image为8-bit,三通道图像 patternSize,每一行每一列的角 corners,已经检测到的角 patternWasFound,findChessboardCorners的返回值

相机标定

生成 objectPoints

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void CCalibration::init3DPoints(cv::Size boardSize, cv::Size squareSize, vector<cv::Point3f> &singlePatternPoint)
{
    for (int i = 0; i < boardSize.height; i++)
    {
        for (int j = 0; j < boardSize.width; j++)
        {
            cv::Point3f tempPoint; //单个角点的三维坐标
            tempPoint.x = float(i * squareSize.width);
            tempPoint.y = float(j * squareSize.height);
            tempPoint.z = 0;
            singlePatternPoint.push_back(tempPoint);
        }
    }
}

求相机的内参外参

1
2
3
4
5
6
7
8
9
double 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:世界坐标(棋盘格平面上的 3D 点),类型为 vector<vector<Point3f>>,对于平面棋盘一般 z=0; imagePoints:对应的图像坐标,类型为 vector<vector<Point2f>>; imageSize:图像的尺寸,用于初始化相机模型; cameraMatrix:输出的内参数矩阵(K); distCoeffs:输出的畸变系数向量(k1,k2,p1,p2,k3,…); rvecs:每一张标定图像对应的旋转向量(相机相对于棋盘平面的姿态); tvecs:每一张标定图像对应的平移向量; flags,可以组合: CALIB_USE_INTRINSIC_GUESS:使用该参数时,将包含有效 fx, fy, cx, cy 估计值的 cameraMatrix 作为初始值输入,然后对其做进一步优化;如果不使用,则以图像中心作为主点初值,通过最小二乘等方式估计初始焦距。

CALIB_FIX_PRINCIPAL_POINT:在优化时固定主点坐标(cx, cy),不再更新;和 CALIB_USE_INTRINSIC_GUESS 配合使用时,主点保持为输入值。

CALIB_FIX_ASPECT_RATIO:固定 fx/fy 的比值,只将 fy 作为变量进行优化;在没有给定内参初值时,只会使用 fx/fy 的比值。

CALIB_ZERO_TANGENT_DIST:将切向畸变系数(p1, p2)固定为 0;

CALIB_FIX_K1,…,CALIB_FIX_K6:对应的径向畸变系数在优化中保持不变(常用于已有标定结果只做微调时)。

CALIB_RATIONAL_MODEL(有理模型):启用额外的径向畸变 k4, k5, k6,使标定函数使用更高阶的畸变模型(广角镜头时常用);

CALIB_THIN_PRISM_MODEL(薄棱镜畸变模型):启用棱镜畸变系数 S1–S4;

CALIB_FIX_S1_S2_S3_S4:固定薄棱镜畸变系数 S1–S4,不参与优化;

CALIB_TILTED_MODEL(倾斜传感器模型):启用倾斜畸变系数 tauX, tauY;

CALIB_FIX_TAUX_TAUY:在优化过程中固定倾斜传感器模型系数 tauX, tauY。

常用的工程实践是:从简单模型开始(只估计 k1, k2, p1, p2, k3),当发现边缘残差较大、镜头视角较广时,再逐步打开有理模型和其他复杂畸变项。

相机矫正

参考:

相机标定(4) 矫正畸变 undistort()和initUndistortRectifyMap()

initUndistortRectifyMap

计算无畸变和修正转换映射

1
2
3
4
5
6
7
8
9
CV_EXPORTS_W void initUndistortRectifyMap( InputArray cameraMatrix, InputArray distCoeffs,
                           InputArray R, InputArray newCameraMatrix,
                           Size size, int m1type, OutputArray map1, OutputArray map2 );

//! initializes maps for cv::remap() for wide-angle
CV_EXPORTS_W float initWideAngleProjMap( InputArray cameraMatrix, InputArray distCoeffs,
                                         Size imageSize, int destImageWidth,
                                         int m1type, OutputArray map1, OutputArray map2,
                                         int projType = PROJ_SPHERICAL_EQRECT, double alpha = 0);

1.cameraMatrix:输入相机矩阵 2.distCoeffs:输入参数,相机的畸变系数: (k1,k2,p1,p2[,k3[,k4,k5,k6[,s1,s2,s3,s4[,τ**x,τ**y]]]]) ,有4,5,8,12或14个元素。如果这个向量是空的,就认为是零畸变系数。 3.R:可选的修正变换矩阵,是个3*3的矩阵。通过stereoRectify计算得来的R1或R2可以放在这里。如果这个矩阵是空的,就假设为单位矩阵。在cvInitUndistortMap中,R被认为是单位矩阵。 4.newCameraMatrix:新的相机矩阵 5.size:未畸变的图像尺寸。 6.m1type:第一个输出的映射的类型,可以为 CV_32FC1, CV_32FC2或CV_16SC2,参见cv::convertMaps。 7.map1:第一个输出映射。 8.map2:第二个输出映射。

这个函数用于计算无畸变和修正转换关系,为了重映射,将结果以映射的形式表达。无畸变的图像看起来就像原始的图像,就像这个图像是用内参为newCameraMatrix的且无畸变的相机采集得到的。 在单目相机例子中,newCameraMatrix一般和cameraMatrix相等,或者可以用cv::getOptimalNewCameraMatrix来计算,获得一个更好的有尺度的控制结果。 在双目相机例子中,newCameraMatrix一般是用cv::stereoRectify计算而来的,设置为P1或P2。 此外,根据R,新的相机在坐标空间中的取向是不同的。例如,它帮助配准双目相机的两个相机方向,从而使得两个图像的极线是水平的,且y坐标相同(在双目相机的两个相机谁水平放置的情况下)。 该函数实际上为反向映射算法构建映射,供反向映射使用。也就是,对于在已经修正畸变的图像中的每个像素(u,v),该函数计算原来图像(从相机中获得的原始图像)中对应的坐标系。

函数输出得到map1和map2,然后使用remap()函数

remap

1
2
3
4
void remap( InputArray src, OutputArray dst,
                         InputArray map1, InputArray map2,
                         int interpolation, int borderMode=BORDER_CONSTANT,
                         const Scalar& borderValue=Scalar());

第一个参数:输入图像,即原图像,需要单通道8位或者浮点类型的图像 第二个参数:输出图像,即目标图像,需和原图形一样的尺寸和类型 第三个参数:它有两种可能表示的对象:(1)表示点(x,y)的第一个映射;(2)表示CV_16SC2,CV_32FC1等 第四个参数:它有两种可能表示的对象:(1)若map1表示点(x,y)时,这个参数不代表任何值;(2)表示 CV_16UC1,CV_32FC1类型的Y值 第五个参数:插值方式,有四种插值方式: (1)INTER_NEAREST——最近邻插值

(2)INTER_LINEAR——双线性插值(默认)

(3)INTER_CUBIC——双三样条插值(默认)

(4)INTER_LANCZOS4——lanczos插值(默认)

第六个参数:边界模式,默认BORDER_CONSTANT

第七个参数:边界颜色,默认Scalar()黑色

实战中的标定注意事项与建议(2024 视角)

  • 标定板与姿态多样性

    • 建议使用质量较好的棋盘格或圆点标定板(打印纸要平整、粘在硬质板上);
    • 拍摄时要覆盖尽量多的姿态:左右、上下、大角度倾斜、远近距离都要有,避免所有图片都在视野中心/同一距离。
  • 图像质量与曝光

    • 尽量避免过曝/欠曝和强反光;
    • 对于运动模糊明显的图片,建议直接丢弃,不要参与标定。
  • 分辨率与 ROI

    • 最终使用什么分辨率跑应用,就尽量用相同分辨率标定;
    • 如果只使用图像中间区域,可以在采集时就裁掉黑边和无效区域,减少无用畸变影响。
  • 标定结果评估

    • 重点看 reprojection error(重投影误差),一般来说 <0.5 像素效果较好,但也要结合分辨率和镜头质量;
    • 随机丢弃/替换部分标定图像重新标定,对比参数变化,判断结果是否稳定。
  • 广角/鱼眼镜头

    • 常规针孔模型在视角非常大(>150°)时往往不够用,可以考虑 OpenCV 中的 fisheye 模块或启用 CALIB_RATIONAL_MODELCALIB_THIN_PRISM_MODEL 等更复杂模型;
    • 对于需要精确几何的任务(SLAM、多视图重建、AR 等),建议优先选用畸变更小、成像更均匀的镜头,再通过标定精修。

代码工程

完整代码工程(包含标定图片):

camera-calibration