文章目录

  • 一、cv库函数
  • 1.找棋盘角点findChessboardCorners
  • 2.获得角点cornerSubPix
  • 3.绘制内角点drawChessboardCorners
  • 4.相机标定calibrateCamera
  • 5.对标定结果进行评价projectPoints
  • 6.矫正图像undistort
  • 二、程序
  • 参考


opencv实现相机标定实战 opencv相机标定函数_Scala

一、cv库函数

1.找棋盘角点findChessboardCorners

bool cv::findChessboardCorners(
	InputArray 	image,
	Size 		patternSize,
	OutputArray corners,
	int 		flags = CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE 
)

参数:

  • image:传入拍摄的棋盘图,必须是8位的灰度或者彩色图像。
  • patternSize:棋盘图内的内角点的行列数,相当于格子的Size(行-1,列-1)
  • corners:用于存储内角点的坐标位置
    一般用元素是Point2f的向量来表示:vector<Point2f> corners
  • flage:用于定义棋盘图上内角点查找的不同处理方式,有默认值。

返回值:

  • 返回非零值:如果找到所有角点并按特定顺序(每行从左到右,逐行)放置。
  • 返回0:如果函数无法找到所有角点或重新排序。

2.获得角点cornerSubPix

亚像素级角点检测:cornerSubPix()

3.绘制内角点drawChessboardCorners

void cv::drawChessboardCorners(	
	InputOutputArray 	image,
	Size 				patternSize,
	InputArray 			corners,
	bool 				patternWasFound 
)

参数:

  • image:8位灰度或者彩色图像;
  • patternSize:每张标定棋盘上内角点的行列数;
  • cornersfindChessboardCorners()输出的内角点的坐标位置
  • patternWasFound:用来指示定义的棋盘内角点是否被完整的探测到。findChessboardCorners()的返回值
    true表示别完整的探测到,函数会用直线依次连接所有的内角点,作为一个整体,false表示有未被探测到的内角点,这时候函数会以(红色)圆圈标记处检测到的内角点

4.相机标定calibrateCamera

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:为世界坐标系中的三维点。在使用时,应该输入一个三维坐标点的向量的向量,即vector<vector<Point3f>> object_points。需要依据棋盘上单个黑白矩阵的大小,计算出(初始化)每一个内角点的世界坐标。
  • imagePoints:为每一个内角点对应的图像坐标点。vector<vector<Point2f>> imagePoints形式的变量;
  • imageSize:为图像的像素尺寸大小,在计算相机的内参和畸变矩阵时需要使用到该参数;
  • cameraMatrix:为相机的内参矩阵。opencv实现相机标定实战 opencv相机标定函数_初始化_02。如Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0));
  • distCoeffs:为畸变矩阵。输入一个Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0))即可;
  • rvecs:为旋转向量;应该输入一个Mat类型的vector,即vector<Mat>rvecs;
  • tvecs:为位移向量,和rvecs一样,应该为vector<Mat> tvecs;
  • flags:为标定时所采用的算法。有如下几个参数:
  • CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,在cameraMatrix矩阵中应该有fx,fy,u0,v0的估计值。否则的话,将初始化(u0,v0)图像的中心点,使用最小二乘估算出fx,fy。
  • CV_CALIB_FIX_PRINCIPAL_POINT:在进行优化时会固定光轴点。当CV_CALIB_USE_INTRINSIC_GUESS参数被设置,光轴点将保持在中心或者某个输入的值。
  • CV_CALIB_FIX_ASPECT_RATIO:固定fx/fy的比值,只将fy作为可变量,进行优化计算。当CV_CALIB_USE_INTRINSIC_GUESS没有被设置,fx和fy将会被忽略。只有fx/fy的比值在计算中会被用到。
  • CV_CALIB_ZERO_TANGENT_DIST:设定切向畸变参数(p1,p2)为零。
  • CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6:对应的径向畸变在优化中保持不变。
  • CV_CALIB_RATIONAL_MODEL:计算k4,k5,k6三个畸变参数。如果没有设置,则只计算其它5个畸变参数。
  • criteria:是最优迭代终止条件设定。

在使用该函数进行标定运算之前,需要对棋盘上每一个内角点的空间坐标系的位置坐标进行初始化,标定的结果是生成相机的内参矩阵cameraMatrix、相机的5个畸变系数distCoeffs,另外每张图像都会生成属于自己的平移向量和旋转向量。

5.对标定结果进行评价projectPoints

void cv::projectPoints(
	InputArray 	objectPoints,
	InputArray 	rvec,
	InputArray 	tvec,
	InputArray 	cameraMatrix,
	InputArray 	distCoeffs,
	OutputArray imagePoints,
	OutputArray jacobian = noArray(),
	double 		aspectRatio = 0 
)

参数:

  • objectPoints:为相机坐标系中的三维点坐标。vector<Point3f>,即calibrateCamera()的每个objectPoints[i]
  • rvec:旋转向量。即calibrateCamera()的每个rvecs[i]
  • tvec:位移向量。即calibrateCamera()的每个tvecs[i]
  • cameraMatrix:为求得的相机的内参数矩阵。即calibrateCamera()cameraMatrix
  • distCoeffs:为求得的相机的畸变矩阵;即calibrateCamera()distCoeffs
  • imagePoints:保存重新计算得到的投影点,为每一个内角点对应的图像上的坐标点;vector<Point2f> imagePoints;
  • jacobian:是雅可比行列式;
  • aspectRatio:是跟相机传感器的感光单元有关的可选参数,如果设置为非0,则函数默认感光单元的dx/dy是固定的,会依此对雅可比矩阵进行调整;

功能:
将3D点投影到图像平面

6.矫正图像undistort

void cv::undistort(
	InputArray 	src,
	OutputArray dst,
	InputArray 	cameraMatrix,
	InputArray 	distCoeffs,
	InputArray 	newCameraMatrix = noArray() 
)
  • src:输入(失真)图像。
  • dst:输出(校正)的图像,其大小和类型与src相同。
  • cameraMatrix:相机矩阵A
  • distCoeffs:相机畸变矩阵
  • newCameraMatrix:相机矩阵。默认情况下,它与cameraMatrix相同,但是您还可以使用其他矩阵来缩放和移动图像。

二、程序

/* 
@brief 你可能需要修改一些信息
@param image_count 图像数量,这个看你自己想拍多少张,建议10
@param patternsize 标定板上每行、列的角点数。这个得看你自己的标定板的实际数目

@brief 操作流程
采集棋盘图时:按y表示确认采集此图像作为棋盘图,如果通过判定,则会显示棋盘图上角点的连线。
如果不通过,会继续让你采样。按q退出程序。按其他键无意义。
 */
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    int image_count = 10;          /* 图像数量 */
    Size image_size;               /* 图像的尺寸 */
    Size patternsize = Size(6, 9); /* 标定板上每行、列的角点数 */

    vector<vector<Point2f>> corner_all; /* 保存检测到的所有角点 */

    VideoCapture capture;
    capture.open(1);

    // 原图像
    Mat imageInput;

    for (int i = 0; i < image_count;)
    {
        capture >> imageInput;

        if (imageInput.empty())
        {
            cout << "empty image!!!\n";
            return 0;
        }

        imshow("src", imageInput);

        // 只是为了获得键盘而已
        switch (waitKey(10))
        {
        case 'q':
            return 0;
            break;
        case 'y':
            break;
        default:
            continue;
            break;
        }

        i++;
        // 用于观察检验输出
        printf("第%d张图片\t", i);

        if (i == 1) //读入第一张图片时获取图像宽高信息
        {
            // 列宽,行高
            image_size.width = imageInput.cols;
            image_size.height = imageInput.rows;
            cout << "image_size.width = " << image_size.width << endl;
            cout << "image_size.height = " << image_size.height << endl;
        }

        Mat gray;
        cvtColor(imageInput, gray, CV_RGB2GRAY);
        vector<Point2f> corner_single; /* 缓存每幅图像上检测到的角点 */

        /* 提取角点 */
        bool patternfound = findChessboardCorners(gray, patternsize, corner_single);
        // 如果找到了
        if (patternfound)
        {
            /* 亚像素精确化 */
            TermCriteria criteria = TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 40, 0.001);
            cornerSubPix(gray, corner_single, Size(5, 5), Size(-1, -1), criteria);
            corner_all.push_back(corner_single); //保存亚像素角点

            /* 在图像上显示角点位置 */
            drawChessboardCorners(gray, patternsize, corner_single, patternfound); //用于在图片中标记角点
            imshow("drawChessboardCorners", gray);                                 //显示图片
        }
        else
        {
            cout << "can not find chessboard corners!\n"; //找不到角点
            i--;
            continue;
        }

        cout << "\n******************\n";
    }

    int total = corner_all.size();
    cout << "已收集的棋盘图个数:" << total << endl;

    /***************************** 以下是摄像机标定 ************************************************/
    cout << "\n[开始标定]>>>>>>\n";

    Size square_size = Size(10, 10); /* 实际测量得到的标定板上每个棋盘格的大小 */

    /* calibrateCamera的参数 */
    vector<vector<Point3f>> object_points; /* 保存标定板上角点的三维坐标 */
    // imagePoints
    // imageSize上面已经获得
    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;                                      /* 每幅图像的平移向量 */

    /* 初始化标定板上角点的三维坐标 */
    for (int count = 0; count < image_count; count++)
    {
        vector<Point3f> single_object_points;
        // 每列
        for (int i = 0; i < patternsize.height; i++)
        {
            // 每行
            for (int j = 0; j < patternsize.width; j++)
            {
                Point3f realPoint;
                /* 假设标定板放在世界坐标系中z=0的平面上 */
                realPoint.x = i * square_size.width;
                realPoint.y = j * square_size.height;
                realPoint.z = 0;
                single_object_points.push_back(realPoint);
            }
        }
        object_points.push_back(single_object_points);
    }

    calibrateCamera(object_points, corner_all, image_size, cameraMatrix, distCoeffs, rvecs, tvecs);
    cout << "[标定完成]\n";

    /*********************************** 对标定结果进行评价 **********************************************/
    cout << "\n[开始评价标定结果]>>>>>>\n";
    //每张图片上总的角点数
    int cornerNum = patternsize.width * patternsize.height;
    double err_single = 0.0; /* 每幅图像的误差 */
    double err_total = 0.0;  /* 所有图像的误差的总和 */
    double err_mean = 0.0;   /* 所有图像的平均误差 */

    vector<Point2f> imagePoints2; /* 保存重新计算得到的投影点 */

    for (int i = 0; i < image_count; i++)
    {
        /* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */
        projectPoints(object_points[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs, imagePoints2);

        /* 计算新的投影点和旧的投影点之间的误差*/
        // 每张图像的角点
        vector<Point2f> corner_single = corner_all[i];
        // 声明原结果的角点矩阵
        Mat mat_corner_single = Mat(1, corner_single.size(), CV_32FC2);
        // 声明评测结果的角点矩阵
        Mat mat_imagePoints2 = Mat(1, imagePoints2.size(), CV_32FC2);
        // 每个角点
        for (int j = 0; j < corner_single.size(); j++)
        {
            // 给原结果的角点矩阵赋值
            mat_corner_single.at<Vec2f>(0, j) = Vec2f(corner_single[j].x, corner_single[j].y);
            // 给评测结果的角点矩阵赋值
            mat_imagePoints2.at<Vec2f>(0, j) = Vec2f(imagePoints2[j].x, imagePoints2[j].y);
        }

        // 取2范数
        err_single = norm(mat_imagePoints2, mat_corner_single, NORM_L2);

        err_total += err_single;
    }

    // 平均误差
    err_mean = err_total / cornerNum;

    cout << "[总体平均误差]:" << err_mean << "像素" << endl;
    cout << "[评价完成!]" << endl;

    cout << "cameraMatrix:\n"
         << cameraMatrix << endl;
    cout << "distCoeffs:\n"
         << distCoeffs << endl;

    /************************ 显示定标结果 ******************************/
    cout << "[矫正图像]>>>>>>" << endl;
    Mat result;

    undistort(imageInput, result, cameraMatrix, distCoeffs);
    imshow("result", result);
    waitKey();

    return 0;
}
cameraMatrix:
[413.2283193644378, 0, 336.8666037520817;
 0, 414.4252596235103, 225.1160821905549;
 0, 0, 1]
distCoeffs:
[0.0707390966157103, -0.271389263833054, -0.002877120698026549, 0.0020488715864277, 0.2587209157266493]

参考

张正友相机标定Opencv实现以及标定流程&&标定结果评价&&图像矫正流程解析(附标定程序和棋盘图)