相机内外参标定

相机内外参标定

码农世界 2024-05-18 前端 60 次浏览 0个评论

文章目录

  • 1.畸变产生原因
    • 1.1 径向畸变
    • 1.2 切向畸变
    • 1.3 总畸变矫正
    • 2.算法实现
      • 2.1 标定流程
      • 2.2 标定代码
      • 2.3 使用标定结果

        1.畸变产生原因

        相机畸变主要有径向畸变和切向畸变。

        1.1 径向畸变

        由于相机透镜的固有特性(凸透镜汇聚光线,凹透镜发散光线)导致成像时直线会变成曲线,所以径向畸变产生原因主要是透镜的几何形状改变了直线的形状。

        径向畸变主要包括:桶形畸变(barrel distortion)、枕形畸变(pincushion distortion)、八字胡畸变(mustache distortion)

        径向畸变矫正

        径向畸变模型可以以下低阶多项式模型来表示:

        x u n d i s t = x d i s t ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) y u n d i s t = y d i s t ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) 其中 r 2 = x d i s t 2 + y d i s t 2 , ( x d i s t , y d i s t ) 是归一后的相机坐标系的点,即坐标原点已移到主点,并且像素坐标除以焦距。 x d i s t = X Z = u − u 0 f x , y d i s t = Y Z = v − v 0 f y x_{undist}=x_{dist}(1 + k_1r^2 + k_2r^4 + k_3r^6) \\ y_{undist}=y_{dist}(1 + k_1r^2 + k_2r^4 + k_3r^6) \\ 其中r^2=x_{dist}^2 + y_{dist}^2,(x_{dist},y_{dist})是归一后的相机坐标系的点,即坐标原点已移到主点,并且像素坐标除以焦距。\\ x_{dist}=\frac{X}{Z}=\frac{u-u_0}{f_x},y_{dist} = \frac{Y}{Z} = \frac{v-v_0}{f_y} xundist​=xdist​(1+k1​r2+k2​r4+k3​r6)yundist​=ydist​(1+k1​r2+k2​r4+k3​r6)其中r2=xdist2​+ydist2​,(xdist​,ydist​)是归一后的相机坐标系的点,即坐标原点已移到主点,并且像素坐标除以焦距。xdist​=ZX​=fx​u−u0​​,ydist​=ZY​=fy​v−v0​​

        其中 k 1 、 k 2 、 k 3 k_1、k_2、k_3 k1​、k2​、k3​是径向畸变参数

        1.2 切向畸变

        切向畸变由相机senser与透镜不平行导致。

        切向畸变矫正

        切向畸变模型可以用以下低阶多项式模型来表示:

        x u n d i s t = x d i s t + [ 2 p 1 x d i s t y d i s t + p 2 ( r 2 + 2 x d i s t 2 ) ] y u n d i s t = y d i s t + [ p 1 ( r 2 + 2 y d i s t 2 ) + 2 p 2 x d i s t y d i s t ] x_{undist} = x_{dist} + [2p_1x_{dist}y_{dist} + p2(r^2 + 2x_{dist}^2)]\\ y_{undist} = y_{dist} + [p_1(r^2 + 2y_{dist}^2) + 2p_2x_{dist}y_{dist}] xundist​=xdist​+[2p1​xdist​ydist​+p2(r2+2xdist2​)]yundist​=ydist​+[p1​(r2+2ydist2​)+2p2​xdist​ydist​]

        p 1 、 p 2 为切向畸变参数 p_1、p_2为切向畸变参数 p1​、p2​为切向畸变参数

        1.3 总畸变矫正

        同时考虑径向畸变和切向畸变:

        x u n d i s t = x d i s t ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) + [ 2 p 1 x d i s t y d i s t + p 2 ( r 2 + 2 x d i s t 2 ) ] y u n d i s t = y d i s t ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) + [ p 1 ( r 2 + 2 y d i s t 2 ) + 2 p 2 x d i s t y d i s t ] x_{undist} = x_{dist}(1 + k_1r^2 + k_2r^4 + k_3r^6) + [2p_1x_{dist}y_{dist} + p_2(r^2 + 2x_{dist}^2)]\\ y_{undist} = y_{dist}(1 + k_1r^2 + k_2r^4 + k_3r^6) + [p_1(r^2 + 2y_{dist}^2) + 2p_2x_{dist}y_{dist}] xundist​=xdist​(1+k1​r2+k2​r4+k3​r6)+[2p1​xdist​ydist​+p2​(r2+2xdist2​)]yundist​=ydist​(1+k1​r2+k2​r4+k3​r6)+[p1​(r2+2ydist2​)+2p2​xdist​ydist​]

        共有 5 个畸变参数 k 1 、 k 2 、 k 3 、 p 1 、 p 2 ,这 5 个畸变参数与内参矩阵一起,都是需要进行相机标定。 共有5个畸变参数k_1、k_2、k_3、p_1、p_2,这5个畸变参数与内参矩阵一起,都是需要进行相机标定。 共有5个畸变参数k1​、k2​、k3​、p1​、p2​,这5个畸变参数与内参矩阵一起,都是需要进行相机标定。

        以上畸变方程由Brown在《Close-Range Camera Calibration》一文中所提出。

        2.算法实现

        2.1 标定流程

        1)在相机视野范围内放置棋盘格,移动棋盘格并取图像,尽量覆盖整个相机的视野范围,且取图数量不少于10张(取图越多覆盖范围越广,标定结果越准确)

        2)遍历每一张图像,重复以下算法步骤:图像二值化(非必须,可加快查找棋盘格角点算法处理速度)、查找棋盘格角点、亚像素精确化、记录角点像素坐标和角点世界坐标(角点0的世界坐标为零点,角点1的世界坐标为零点+实际棋盘格物理距离)

        3)调用opencv处理计算相机内参和畸变系数计算函数,得出结果

        2.2 标定代码

            bool success = false;
            try {
                //棋盘格行列角点数
                cv::Size patternSize(6, 4);
                // 棋盘格的边长(单位:mm)
                float squareSize = 30.0;
                cv::Size imageSize(5440,3648);
                // 存储棋盘格图像的角点坐标
                std::vector> imagePoints;
                // 存储棋盘格的世界坐标
                std::vector> objectPoints;
                // 加载棋盘格图像并提取角点
                for (int i = 0; i < m_imgList.size(); i++) {
                    // 读取图像
                    cv::Mat image = transmitQImage2cvMat(&m_imgList[i]);
                    if (image.empty()) {
                        QMessageBox::warning(Q_NULLPTR, tr("Warn"), tr("image index %1 transmitQImage2cvMat fail").arg(i));
                        continue;
                    }
                    cv::Mat binary;
                    cv::threshold(image, binary, 75, 255, cv::THRESH_BINARY);
                    // 寻找角点
                    std::vector corners;
                    bool found = cv::findChessboardCorners(binary, patternSize, corners);
                    if (found) {
                        // 亚像素精确化,使角点更准确
                        cv::Mat gray;
                        cv::cvtColor(binary, gray, cv::COLOR_BGR2GRAY);
                        cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1),
                                         cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.1));
                        // 添加角点坐标和世界坐标
                        imagePoints.push_back(corners);
                        std::vector objectCorners;
                        for (int y = 0; y < patternSize.height; y++) {
                            for (int x = 0; x < patternSize.width; x++) {
                                objectCorners.push_back(cv::Point3f(x * squareSize, y * squareSize, 0));
                            }
                        }
                        objectPoints.push_back(objectCorners);
                    } else {
                        QString err = tr("iamge index %1 find corners fail").arg(i);
                        qWarning() << err;
                        QMessageBox::warning(Q_NULLPTR, tr("Warn"), err);
                    }
                }
                // 相机内参矩阵和畸变系数
                cv::Mat cameraMatrix, distCoeffs;
                std::vector rvecs, tvecs;
                double rms = cv::calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs);
                // 打印标定结果
                qDebug() << "(cameraMatrix):";
                qDebug() << "[" <(0,0) << "  " << cameraMatrix.at(0,1) << "  " << cameraMatrix.at(0,2);
                qDebug() << cameraMatrix.at(1,0) << "  " << cameraMatrix.at(1,1) << "  " << cameraMatrix.at(1,2);
                qDebug() << cameraMatrix.at(2,0) << "  " << cameraMatrix.at(2,1) << "  " << cameraMatrix.at(2,2) << "]";
                qDebug() << "(distCoeffs):";
                qDebug() << "[" << distCoeffs.at(0,0) << distCoeffs.at(0,1) << distCoeffs.at(0,2)
                         << distCoeffs.at(0,3) << distCoeffs.at(0,4) << "]";
            }
        	catch(const cv::Exception& e) {
                qWarning() << QString::fromStdString(e.what());
            }
        

        2.3 使用标定结果

        //其中,undist为原图,dist为去畸变后的结果,m_cameraMatrix为上述标定结果的内参矩阵,m_distCoeffs为上述标定结果的畸变矫正矩阵
        //注意,dist必须初始化内存跟undist内存大小一致,否则函数调用会失败
        cv::Mat undist = take_image_from_camera();
        cv::Mat dist = undist.clone();
        cv::undistort(undist, dist, m_cameraMatrix, m_distCoeffs);
        

转载请注明来自码农世界,本文标题:《相机内外参标定》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,60人围观)参与讨论

还没有评论,来说两句吧...

Top