小工具知识网

当前位置: 首页 > 行业知识 > 单目相机标定实现--张正友标定法

行业知识

单目相机标定实现--张正友标定法

2026-10-21 19:50:26 来源:互联网转载

文章目录

    • 一:相机坐标系,像素平面坐标系,世界坐标系,归一化坐标系介绍
      • 1:概述
      • 公式
      • 二:实现
        • 1:整体流程
        • 4:求出每张图像的单应性矩阵并用LMA优化
        • 5:求解理想无畸变情况下的摄像机的内参数和外参数
        • 6:应用最小二乘求出实际的畸变系数
        • 7:综合内参,外参,畸变系数,使用极大似然法(LMA),优化估计,提升估计精度
        • 8:得出相机的内参,外参和畸变系数
        • 9:OpenCV模型
        • 三:畸变修复(去畸变)
        • 四:���结

          原文链接:地址

          个人笔记:

          本次介绍针对于单目相机标定,实现方法:张正友标定法。

          一:相机坐标系,像素平面坐标系,世界坐标系,归一化坐标系介绍

          1:概述

          如图,现实世界中有一个P点和一个相机(光心),描述这个P点的空间坐标首先得有一个坐标系,那么以光心为原点O建一个坐标系,叫相机坐标系。

          那么就可以在相机坐标系下,设 P 坐标 ( X , Y , Z ) P坐标(X,Y,Z) P坐标(X,Y,Z)和P的投影点 P ′ ( x ′ , y ′ , z ′ ) P'(x',y',z') P′(x′,y′,z′)。值得一提的是, P ′ ( x ′ , y ′ , z ′ ) P'(x',y',z') P′(x′,y′,z′)坐落在物理成像平面和像素平面。

          物理成像平面,像素平面是二维的,他们的坐标系并不一样:

          物理成像平面在 O ′ ( x ′ , y ′ ) O'(x',y') O′(x′,y′)平面上;

          像素平面的原点在那个黑灰色图的左上角(图片的左上角),横轴向右称为 u u u轴,纵轴向下称为 v v v轴。

          这样就得到了 P ′ P' P′的像素坐标 P ( u , v ) P(u,v) P(u,v),称为 P u v Puv Puv。

          所谓的归一化的成像平面,就是将三维空间点的坐标都除以Z,在相机坐标系下,P有X, Y, Z 三个量,如果把它们投影到归一化平面 Z = 1 上,就会得到P的归一化坐标P(X/Z, Y/Z, 1)。

          公式

          [ X Y Z 1 ] − > \left[\begin{array}{c} X \\ Y \\ Z \\ 1 \end{array}\right]-> ​XYZ1​ ​−>物体坐标.

          [ R T 0 1 ] − > \left[\begin{array}{cc} R & T \\ 0 & 1 \end{array}\right]-> [R0​T1​]−>外参

          [ α γ u 0 0 0 β v 0 0 0 0 1 0 ] − > \left[\begin{array}{cccc} \alpha & \gamma & u_{0} & 0 \\ 0 & \beta & v_{0} & 0 \\ 0 & 0 & 1 & 0 \end{array}\right]-> ​α00​γβ0​u0​v0​1​000​ ​−>内参

          [ u v 1 ] − > \left[\begin{array}{l} u \\ v \\ 1 \end{array}\right]-> ​uv1​ ​−>像素坐标

          其中外参 T T T是平移向量 ( t 1 , t 2 , t 3 ) T (t1,t2,t3)^T (t1,t2,t3)T.

          R R R旋转矩阵如下图:

          二:实现

          1:整体流程

          第1步,第2步,第3步 暂不介绍了(可以用halcon算子块或者OpenCV获取特征点坐标),主要介绍获取到特征点以后,优化获取参数部分。

          4:求出每张图像的单应性矩阵并用LMA优化

          如何计算单应性矩阵:

          [ x b y b w b ] = [ h 1   h 2   h 3   h 4   h 5   h 6   h 7   h 8   h 9 ] [ x a y a w a ] x b w b = h 1 x a + h 2 y a + h 3 w a h 7 x a + h 8 y a + h 9 w a = h 1 x a / w a + h 2 y a / w a + h 3 h 7 x a / w a + h 8 y a / w a + h 9 y b w b = h 4 x a + h 5 y a + h 6 w a h 7 x a + h 8 y a + h 9 w a = h 4 x a / w a + h 5 y a / w a + h 6 h 7 x a / w a + h 8 y a / w a + h 9 \begin{array}{c} \left[\begin{array}{l} x_{b} \\ y_{b} \\ w_{b} \end{array}\right]=\left[\begin{array}{ccc} \mathrm{h}_{1} & \mathrm{~h}_{2} & \mathrm{~h}_{3} \\ \mathrm{~h}_{4} & \mathrm{~h}_{5} & \mathrm{~h}_{6} \\ \mathrm{~h}_{7} & \mathrm{~h}_{8} & \mathrm{~h}_{9} \end{array}\right]\left[\begin{array}{l} x_{a} \\ y_{a} \\ w_{a} \end{array}\right] \\\\ \frac{x_{b}}{w_{b}}=\frac{h_{1} x_{a}+\mathrm{h}_{2} y_{a}+\mathrm{h}_{3} w_{a}}{h_{7} x_{a}+\mathrm{h}_{8} y_{a}+\mathrm{h}_{9} w_{a}}=\frac{h_{1} x_{a} / w_{a}+\mathrm{h}_{2} y_{a} / w_{a}+\mathrm{h}_{3}}{h_{7} x_{a} / w_{a}+\mathrm{h}_{8} y_{a} / w_{a}+\mathrm{h}_{9}} \\ \frac{y_{b}}{w_{b}}=\frac{h_{4} x_{a}+\mathrm{h}_{5} y_{a}+\mathrm{h}_{6} w_{a}}{h_{7} x_{a}+\mathrm{h}_{8} y_{a}+\mathrm{h}_{9} w_{a}}=\frac{h_{4} x_{a} / w_{a}+\mathrm{h}_{5} y_{a} / w_{a}+\mathrm{h}_{6}}{h_{7} x_{a} / w_{a}+\mathrm{h}_{8} y_{a} / w_{a}+\mathrm{h}_{9}} \end{array} ​xb​yb​wb​​ ​= ​h1​ h4​ h7​​ h2​ h5​ h8​​ h3​ h6​ h9​​ ​ ​xa​ya​wa​​ ​wb​xb​​=h7​xa​+h8​ya​+h9​wa​h1​xa​+h2​ya​+h3​wa​​=h7​xa​/wa​+h8​ya​/wa​+h9​h1​xa​/wa​+h2​ya​/wa​+h3​​wb​yb​​=h7​xa​+h8​ya​+h9​wa​h4​xa​+h5​ya​+h6​wa​​=h7​xa​/wa​+h8​ya​/wa​+h9​h4​xa​/wa​+h5​ya​/wa​+h6​​​

           令  u a = x a w a , v a = y a w a , u b = x b w b , v b = y b w b , 上式化简为  \text { 令 } u_{a}=\frac{x_{a}}{w_{a}}, v_{a}=\frac{y_{a}}{w_{a}}, u_{b}=\frac{x_{b}}{w_{b}}, v_{b}=\frac{y_{b}}{w_{b}} \text {, 上式化简为 }  令 ua​=wa​xa​​,va​=wa​ya​​,ub​=wb​xb​​,vb​=wb​yb​​, 上式化简为 

          u b = h 1 u a + h 2 v a + h 3 h 7 u a + h 8 v a + h 9 v b = h 4 u a + h 5 v a + h 6 h 7 u a + h 8 v a + h 9 \begin{array}{l} u_{b}=\frac{h_{1} u_{a}+\mathrm{h}_{2} v_{a}+\mathrm{h}_{3}}{h_{7} u_{a}+\mathrm{h}_{8} v_{a}+\mathrm{h}_{9}} \\ v_{b}=\frac{h_{4} u_{a}+\mathrm{h}_{5} v_{a}+\mathrm{h}_{6}}{h_{7} u_{a}+\mathrm{h}_{8} v_{a}+\mathrm{h}_{9}} \end{array} ub​=h7​ua​+h8​va​+h9​h1​ua​+h2​va​+h3​​vb​=h7​ua​+h8​va​+h9​h4​ua​+h5​va​+h6​​​

          h 1 u a + h 2 v a + h 3 − h 7 u a u b − h 8 v a u b − h 9 u b = 0 h 4 u a + h 5 v a + h 6 − h 7 u a v b − h 8 v a v b − h 9 v b = 0 \begin{array}{c} h_{1} u_{a}+h_{2} v_{a}+h_{3}-h_{7} u_{a} u_{b}-h_{8} v_{a} u_{b}-h_{9} u_{b}=0 \\ h_{4} u_{a}+h_{5} v_{a}+h_{6}-h_{7} u_{a} v_{b}-h_{8} v_{a} v_{b}-h_{9} v_{b}=0 \end{array} h1​ua​+h2​va​+h3​−h7​ua​ub​−h8​va​ub​−h9​ub​=0h4​ua​+h5​va​+h6​−h7​ua​vb​−h8​va​vb​−h9​vb​=0​

           可以直接设  ∥ h ∥ 2 2 = 1  ,此时仍然可以固定住尺度,且有:  \text { 可以直接设 }\|h\|_{2}^{2}=1 \text { ,此时仍然可以固定住尺度,且有: }  可以直接设 ∥h∥22​=1 ,此时仍然可以固定住尺度,且有: 

          此时系数矩阵秩为8,有线性空间解,解的自由度为1,满足齐次性,又由于限制单位长度,有唯一解,此时仍可以通过SVD分解求解出 h,从而得到单应矩阵。

          代码实现:

          //获取标准差double CameraCalibration::StdDiffer(const Eigen::VectorXd& data){    //获取平均值    double mean = data.mean();    //std::sqrt((Σ(x-_x)²) / n)    return std::sqrt((data.array() - mean).pow(2).sum() / data.rows());}// 归一化Eigen::Matrix3d CameraCalibration::Normalization (const Eigen::MatrixXd& P){    Eigen::Matrix3d T;    double cx = P.col ( 0 ).mean();    double cy = P.col ( 1 ).mean();    double stdx = StdDiffer(P.col(0));    double stdy = StdDiffer(P.col(1));;    double sqrt_2 = std::sqrt ( 2 );    double scalex = sqrt_2 / stdx;    double scaley = sqrt_2 / stdy;    T     Eigen::Matrix3d realNormMat = Normalization(srcNormal);    Eigen::Matrix3d picNormMat = Normalization(dstNormal);    int n = srcNormal.rows();    // 2. DLT    Eigen::MatrixXd input ( 2*n, 9 );    for ( int i=0; i        //转换齐次坐标        Eigen::MatrixXd singleSrcCoor(3,1),singleDstCoor(3,1);        singleSrcCoor(0,0) = srcNormal(i,0);        singleSrcCoor(1,0) = srcNormal(i,1);        singleSrcCoor(2,0) = 1;        singleDstCoor(0,0) = dstNormal(i,0);        singleDstCoor(1,0) = dstNormal(i,1);        singleDstCoor(2,0) = 1;        //坐标归一化        Eigen::MatrixXd realNorm(3,1),picNorm(3,1);        realNorm = realNormMat * singleSrcCoor;        picNorm = picNormMat * singleDstCoor;        input ( 2*i, 0 ) = realNorm ( 0, 0 );        input ( 2*i, 1 ) = realNorm ( 1, 0 );        input ( 2*i, 2 ) = 1.;        input ( 2*i, 3 ) = 0.;        input ( 2*i, 4 ) = 0.;        input ( 2*i, 5 ) = 0.;        input ( 2*i, 6 ) = -picNorm ( 0, 0 ) * realNorm ( 0, 0 );        input ( 2*i, 7 ) = -picNorm ( 0, 0 ) * realNorm ( 1, 0 );        input ( 2*i, 8 ) = -picNorm ( 0, 0 );        input ( 2*i+1, 0 ) = 0.;        input ( 2*i+1, 1 ) = 0.;        input ( 2*i+1, 2 ) = 0.;        input ( 2*i+1, 3 ) = realNorm ( 0, 0 );        input ( 2*i+1, 4 ) = realNorm ( 1, 0 );        input ( 2*i+1, 5 ) = 1.;        input ( 2*i+1, 6 ) = -picNorm ( 1, 0 ) * realNorm ( 0, 0 );        input ( 2*i+1, 7 ) = -picNorm ( 1, 0 ) * realNorm ( 1, 0 );        input ( 2*i+1, 8 ) = -picNorm ( 1, 0 );    }    // 3. SVD分解    JacobiSVDpublic:    Eigen::VectorXd  operator()(const Eigen::VectorXd& parameter,const QList        Eigen::MatrixXd inValue = otherArgs.at(0);        Eigen::MatrixXd outValue = otherArgs.at(1);        int dataCount = inValue.rows();        //保存残差值        Eigen::VectorXd residual(dataCount*2) ,residualNew(dataCount*2);        Eigen::Matrix3d HH =  parameter.reshaped            //转换齐次坐标            Eigen::VectorXd singleRealCoor(3),U(3);            singleRealCoor(0,0) = inValue(i,0);            singleRealCoor(1,0) = inValue(i,1);            singleRealCoor(2,0) = 1;            U = HH * singleRealCoor;            U /= U(2);            residual(i*2) = U(0);            residual(i*2+1) = U(1);            residualNew(i*2) = outValue(i,0);            residualNew(i*2+1) = outValue(i,1);        }        residual -= residualNew;        return residual;    }};    //求偏导1    double PartialDeriv_1(const Eigen::VectorXd& parameter,int paraIndex,const Eigen::MatrixXd &inValue,int objIndex)    {        Eigen::VectorXd para1 = parameter;        Eigen::VectorXd para2 = parameter;        para1(paraIndex) -= DERIV_STEP;        para2(paraIndex) += DERIV_STEP;        //逻辑        double obj1 = ((para1(0))*inValue(objIndex,0) + para1(1)*inValue(objIndex,1) + para1(2)) / (para1(6)*inValue(objIndex,0) + para1(7)*inValue(objIndex,1) + para1(8));        double obj2 = ((para2(0))*inValue(objIndex,0) + para2(1)*inValue(objIndex,1) + para2(2)) / (para2(6)*inValue(objIndex,0) + para2(7)*inValue(objIndex,1) + para2(8));;        return (obj2 - obj1) / (2 * DERIV_STEP);    }    //求偏导2    double PartialDeriv_2(const Eigen::VectorXd& parameter,int paraIndex,const Eigen::MatrixXd &inValue,int objIndex)    {        Eigen::VectorXd para1 = parameter;        Eigen::VectorXd para2 = parameter;        para1(paraIndex) -= DERIV_STEP;        para2(paraIndex) += DERIV_STEP;        //逻辑        double obj1 = ((para1(3))*inValue(objIndex,0) + para1(4)*inValue(objIndex,1) + para1(5)) / (para1(6)*inValue(objIndex,0) + para1(7)*inValue(objIndex,1) + para1(8));        double obj2 = ((para2(3))*inValue(objIndex,0) + para2(4)*inValue(objIndex,1) + para2(5)) / (para2(6)*inValue(objIndex,0) + para2(7)*inValue(objIndex,1) + para2(8));;        return (obj2 - obj1) / (2 * DERIV_STEP);    }public:    Eigen::MatrixXd operator()(const Eigen::VectorXd& parameter,const QList        Eigen::MatrixXd inValue = otherArgs.at(0);        int rowNum = inValue.rows();        Eigen::MatrixXd Jac(rowNum*2, parameter.rows());        for (int i = 0; i 

上一篇:揭秘JS打造万年历的绝招!

下一篇:UEFI启动Win10 U盘制作详解