相机成像模型

单目相机模型

dx和dy对应的是1个像素对应长度和高度 由此可以得到空间某一点到图像中某一像素点的关系

坐标系

注意:进行投影变换后之后的成像平面是物理坐标系,单位是米厘米或者毫米 通过K就能计算像素中u和v的值

相机畸变

模型投影函数

rvec 和 tvec可以先设置为单位1 畸变系数矩阵就是之前的k1k2k3p1p2

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{

    //输入前文计算得到的内参矩阵和畸变矩阵,建议现场标定  
    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);
    //代码清单10-10中计算的第一张图像相机坐标系与世界坐标系之间的关系
    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);
    return 0;
}

单目相机标定

标定原理

可以对世界坐标系转换成相机坐标系下的坐标 联合以上所有函数,可以得到

z_{w}\begin{bmatrix}u\\v\\1\end{bmatrix} = K\begin{bmatrix}R & t\end{bmatrix}\begin{bmatrix}x_{w}\\y_{w}\\z_{w}\\1\end{bmatrix}

其中K是相机内参矩阵,R是世界坐标系旋转矩阵,t是平移量 通过世界坐标系中的点,也有图像中像素的坐标,就可以求出内参矩阵和外参矩阵

标定板角点提取

圆形标定板中心提取

角点位置优化

通常用(5×5)的范围

绘制内焦点提取结果

相机标定函数

第二个参数是通过角点检测得到的二维坐标 可以人为先给出内参矩阵,减少迭代次数 示例

#include <opencv2\opencv.hpp>
#include <fstream> 
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
    //读取所有图像
    vector<Mat> imgs;
    string imageName;
    ifstream fin("calibdata.txt");
    while (getline(fin, imageName))
    {
        Mat img = imread(imageName);
        imgs.push_back(img);
    }

    Size board_size = Size(9, 6);  //方格标定板内角点数目(行,列)
    vector<vector<Point2f>> imgsPoints;
    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));  //细化方格标定板角点坐标
        drawChessboardCorners(img1, board_size, img1_points, true);
        imshow("img1", img1);
        waitKey(0);
        imgsPoints.push_back(img1_points);
    }

    //生成棋盘格每个内角点的空间三维坐标
    Size squareSize = Size(10, 10);  //棋盘格每个方格的真实尺寸
    vector<vector<Point3f>> objectPoints;
    for (int i = 0; i < imgsPoints.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 < imgsPoints.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, imgsPoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs, 0);
    cout << "相机的内参矩阵=" << endl << cameraMatrix << endl;
    cout << "相机畸变系数" << distCoeffs << endl;
    waitKey(0);
    return 0;
}

图像校正

示例

#include <opencv2\opencv.hpp>
#include <iostream>
#include <fstream> 
#include <vector>

using namespace std;
using namespace cv;

//使用initUndistortRectifyMap()函数和remap()函数校正图像
void initUndistAndRemap(vector<Mat> imgs,  //所有原图像向量
    Mat cameraMatrix,  //计算得到的相机内参
    Mat distCoeffs,    //计算得到的相机畸变系数
    Size imageSize,    //图像的尺寸
    vector<Mat>& undistImgs)  //校正后的输出图像
{
    //计算映射坐标矩阵
    Mat R = Mat::eye(3, 3, CV_32F);
    Mat mapx = Mat(imageSize, CV_32FC1);
    Mat mapy = Mat(imageSize, CV_32FC1);
    initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cameraMatrix, imageSize, CV_32FC1, mapx, mapy);

    //校正图像
    for (int i = 0; i < imgs.size(); i++)
    {
        Mat undistImg;
        remap(imgs[i], undistImg, mapx, mapy, INTER_LINEAR);
        undistImgs.push_back(undistImg);
    }
}

//用undistort()函数直接计算校正图像
void undist(vector<Mat> imgs,   //所有原图像向量
    Mat cameraMatrix,   //计算得到的相机内参
    Mat distCoeffs,     //计算得到的相机畸变系数
    vector<Mat>& undistImgs)  //校正后的输出图像
{
    for (int i = 0; i < imgs.size(); i++)
    {
        Mat undistImg;
        undistort(imgs[i], undistImg, cameraMatrix, distCoeffs);
        undistImgs.push_back(undistImg);
    }
}

int main()
{
    //读取所有图像
    vector<Mat> imgs;
    string imageName;
    ifstream fin("calibdata.txt");
    while (getline(fin, imageName))
    {
        Mat img = imread(imageName);
        imgs.push_back(img);
    }

    //输入前文计算得到的内参矩阵
    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);
    vector<Mat> undistImgs;
    Size imageSize;
    imageSize.width = imgs[0].cols;
    imageSize.height = imgs[0].rows;

    //使用initUndistortRectifyMap()函数和remap()函数校正图像
    initUndistAndRemap(imgs, cameraMatrix, distCoeffs, imageSize, undistImgs);

    //用undistort()函数直接计算校正图像,下一行代码取消注释即可
    //undist(imgs, cameraMatrix, distCoeffs, undistImgs);

    //显示校正前后的图像
    for (int i = 0; i < imgs.size(); i++)
    {
        string windowNumber = to_string(i);
        imshow("未校正图像" + windowNumber, imgs[i]);
        imshow("校正后图像" + windowNumber, undistImgs[i]);
    }

    waitKey(0);
    return 0;
}

单目相机位姿估计

位姿估计函数

世界坐标系可以理解为前一时刻的相机坐标系

同样的,位姿估计函数同样也有ransac函数 inliers为强制性认为规定的匹配的点,默认情况下不输入

示例(pnp和pnpAndRansac)

#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace cv;

int main()
{
    //读取所有图像
    Mat img = imread("left01.jpg");
    Mat gray;
    cvtColor(img, gray, COLOR_BGR2GRAY);
    vector<Point2f> imgPoints;
    Size boardSize = Size(9, 6);
    findChessboardCorners(gray, boardSize, imgPoints);  //计算方格标定板角点
    find4QuadCornerSubpix(gray, imgPoints, Size(5, 5));  //细化方格标定板角点坐标

    //生成棋盘格每个内角点的空间三维坐标
    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);
        }
    }

    //输入前文计算得到的内参矩阵和畸变矩阵
    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);

    //用PnP算法计算旋转和平移量
    Mat rvec, tvec;
    solvePnP(PointSets, imgPoints, cameraMatrix, distCoeffs, rvec, tvec);
    cout << "世界坐标系变换到相机坐标系的旋转向量:" << rvec << endl;
    //旋转向量转换旋转矩阵
    Mat R;
    Rodrigues(rvec, R);//有旋转向量变换为旋转矩阵,第一个参数输入,第二个参数输出
    cout << "旋转向量转换成旋转矩阵:" << endl << R << endl;

    //用PnP+Ransac算法计算旋转向量和平移向量
    Mat rvecRansac, tvecRansac;
    solvePnPRansac(PointSets, imgPoints, cameraMatrix, distCoeffs, rvecRansac, tvecRansac);
    Mat RRansac;
    Rodrigues(rvecRansac, RRansac);
    cout << "旋转向量转换成旋转矩阵:" << endl << RRansac << endl;
    waitKey(0);
    return 0;
}