undistortPoints()无法处理镜头扭曲

时间:2014-04-08 00:09:55

标签: opencv image-processing computer-vision

我使用openCV函数projectPoints()来旋转,平移和投影一组3D点和solvePnp()来查找此旋转和平移。当镜头失真系数全为零但是否则失败时,这很有效。完全失败只需要很少的失真:

 distCoeffs << 0.0, 0.01, 0.0, 0.0, 0.0;  

代码如下:

#include <iostream>
#include "opencv.hpp"
using namespace std;
using namespace cv;
#define DEG2RAD (3.1415293/180.0)
#define RAD2DEG (1.0/DEG2RAD)

int main() {
    const int npoints = 10; // number of points

    // extrinsic
    const Point3f tvec(10, 20, 30);
    Point3f rvec(3, 5, 7);
    cout << "Finding extrinsic parameters (PnP)" << endl;
    cout<<"Test transformations: ";
    cout<<"Rotation: "<<rvec<<"; translation: "<<tvec<<endl;
    rvec*=DEG2RAD;

    // intrinsic
    Mat_ <double>cameraMatrix(3, 3);
    cameraMatrix << 300., 0., 200., 0, 300., 100., 0., 0., 1.;
    Mat_ <double>distCoeffs(1, 5); //  (k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6]]) of 4, 5, or 8 elements.
    //distCoeffs << 1.2, 0.2, 0., 0., 0.;  // non-zero distortion
    distCoeffs << 0.0, 0.0, 0.0, 0.0, 0.0; // zero distortion
    cout<<"distrotion coeff: "<<distCoeffs<<endl;

    cout<<"============= Running PnP..."<<endl;
    vector<Point3f> objPts(npoints);
    vector<Point2f> imagePoints(npoints);
    Mat rvec_est, tvec_est;
    randu(Mat(objPts), 0.0f, 100.0f);

    // project
    projectPoints(Mat(objPts), Mat(rvec), Mat(tvec), cameraMatrix, distCoeffs, Mat(imagePoints));

    // extrinsic
    solvePnP(objPts, imagePoints, cameraMatrix, distCoeffs, rvec_est, tvec_est);
    cout<<"Rotation: "<<rvec_est*RAD2DEG<<endl;
    cout<<"Translation "<<tvec_est<<endl;

    return 0;
}

当所有失真系数均为0时,结果为OK:

寻找外在参数(PnP)
测试变换:旋转:[3,5,7];翻译:[10,20,30]
distrotion coeff:[0,0,0,0,0]
=============运行PnP ...
轮换:[2.999999581709123; 4.999997813985293; 6.999999826089725]
翻译[9.999999792663072; 19.99999648222693; 29.99999699621362]

然而,当它们不为零时,结果完全错误:

寻找外在参数(PnP)
测试变换:旋转:[3,5,7];翻译:[10,20,30]
distrotion coeff:[1.2,0.2,0,0,0]
=============运行PnP ...
轮换:[ - 91.56479629305277; -124.3631985067845; -74.46486950666471]
翻译[-69.72473511009439; -117.7463271636532; -87.27777166027946]

由于人们问,我正在添加中间输入 - 一些3D点及其对非零失真系数的投影。我的相机矩阵是 cameraMatrix&lt;&lt; 300.,0.,200。,0,300。,100.,0.,0.,1。;

3d points [53.0283,19.9259,40.1059];二维投影[1060.34,700.59]
3d points [81.4385,43.7133,24.879];二维投影[6553.88,5344.22]
3d points [77.3105,76.2094,30.7794];二维投影[5143.32,6497.12]
3d points [70.2432,47.8447,79.219];二维投影[771.497,611.726]

另一个有趣的观察结果:当distCoeff非零时应用undistort并不真正起作用(但是当失真系数都为0时它会产生相同的2D点):

cout<<"applying undistort..."<<endl;
vector<Point2f> imagePointsUndistort(npoints);
undistortPoints(Mat(imagePoints), Mat(imagePointsUndistort), cameraMatrix, distCoeffs);
for (int i=0; i<4; i++)
    cout<<"2d original "<<imagePoints[i]<<"; 2d undistort "<<imagePointsUndistort[i]<<endl;

应用无瑕疵......
2d原始[1060.34,700.59]; 2d不失真[0,0]
2d原版[6553.88,5344.22]; 2d不失真[0,0]
2d原文[5143.32,6497.12]; 2d不失真[0,0]
2d原始[771.497,611.726]; 2d undistort [0,0]

我尝试undistort()的原因是因为如果释放已知内部参数的影响,PnP只会成为Ax = 0形式的最小方向问题。它需要分钟。 6个点用于近似线性解,可能用LMA(flags = CV_ITERATIVE)进一步改善。从技术上讲,只有6DOF,因此需要3个点,所以其他方法(flags = CV_P3P,CV_EPNP)占用的点数更少。无论如何,无论方法或点数如何,结果仍然无效,具有非零失真系数。我将尝试的最后一件事是将所有点放在3D平面上。它仍然失败:

 for (int i=0; i<npoints; i++)
        objPts[i].z=0.0f;

寻找外在参数(PnP)
测试变换:旋转:[3,5,7];翻译:[10,20,30]
distrotion coeff:[1.2,0.2,0,0,0]
=============运行PnP ...
轮换:[ - 1830.321574903016; 2542.206083947917; 2532.255948350521]
翻译[1407.918216894239; 1391.373407846455; 556.7108606094299]

2 个答案:

答案 0 :(得分:6)

如何让您的代码正常工作?

我可以使用您提供的代码重现所描述的行为,但是,以下两个选项中的任何一个都可以解决问题:

  • const Point3f tvec(10, 20, 30);替换为const Point3f tvec(10, 20, N);,其中N 低于低于0(例如-300)或更大 > 100(例如300)。

  • 通过致电solvePnP将来电替换为solvePnPRansac

为什么每项更改都会解决不良行为?

首先,考虑原始代码从solvePnP函数请求的内容。你正在使用相当小的旋转,因此为了简化解释,我将假设旋转是同一性的。然后,将摄像机定位在世界坐标X = 10,Y = 20和Z = 30处,并随机生成物点,其中世界坐标(X,Y,Z)均匀地绘制在[0,100] 3 中。 因此,相机处于对象点可能范围的中间,如下图所示:

enter image description here

这意味着可以非常靠近焦平面(即,穿过光学中心并垂直于光轴的平面)生成物点。此类对象点的摄像机图像中的投影未定义。 然而,实际上undistortPoints的非线性优化算法即使对于靠近焦平面的物点也不稳定。这种不稳定导致undistortPoints的迭代算法发散,除非系数全为零,因为在这种情况下初始值在估计期间保持严格不变。

因此,避免这种行为的两种可能的解决方案如下:

  • 避免在相机焦平面附近生成物点,即更改平移矢量物点坐标的范围。

  • 在使用solvePnPRansac进行PnP估算之前,消除太靠近相机焦平面的物点,其未失真估计发散(异常值)。


有关undistortPoints失败原因的详细信息:

注意:正如我们所知道的3D世界点,我使用以下调用来获得真正的未失真坐标,与undistortPoints的结果无关:

cv::projectPoints(obj_pts, rvec, tvec, cv::Mat_<double>::eye(3,3), cv::Mat_<double>::zeros(5,1), true_norm_pts);

以下函数是undistortPoints正在做的简化版本:

void simple_undistort_point(const cv::Mat &img_pt,
                            const cv::Mat_<double> &K,
                            const cv::Mat_<double> &D,
                            cv::Mat &norm_pt)
{
    // Define temporary variables
    double k[8]={D.at<double>(0),
                 D.at<double>(1),
                 D.at<double>(2),
                 D.at<double>(3),
                 D.at<double>(4)},
           fx, fy, ifx, ify, cx, cy;
    fx = K.at<double>(0,0);
    fy = K.at<double>(1,1);
    ifx = 1./fx;
    ify = 1./fy;
    cx = K.at<double>(0,2);
    cy = K.at<double>(1,2);
    // Cancel distortion iteratively
    const int iters = 5;
    double x, y, x0, y0;
    x0=x=(img_pt.at<double>(0)-cx)*ifx;
    y0=y=(img_pt.at<double>(1)-cy)*ify;
    for(int j = 0; j < iters; ++j)
    {
        double r2 = x*x + y*y;
        double icdist = 1/(1 + ((k[4]*r2 + k[1])*r2 + k[0])*r2);
        double deltaX = 2*k[2]*x*y + k[3]*(r2 + 2*x*x);
        double deltaY = k[2]*(r2 + 2*y*y) + 2*k[3]*x*y;
        x = (x0 - deltaX)*icdist;
        y = (y0 - deltaY)*icdist;
    }
    // Store result
    norm_pt.create(1,2,CV_64F);
    norm_pt.at<double>(0) = x;
    norm_pt.at<double>(1) = y;
}

如果您添加代码来检查xy每次迭代的变化情况,您会发现迭代优化因r2在开始时非常大而分歧。这是一个日志示例:

#0:   [2.6383300, 1.7651500]    r2=10.0766000, icdist=0.0299408, deltaX=0, deltaY=0
#1:   [0.0789937, 0.0528501]    r2=0.00903313, icdist=0.9892610, deltaX=0, deltaY=0
#2:   [2.6100000, 1.7462000]    r2=9.86128000, icdist=0.0309765, deltaX=0, deltaY=0
#3:   [0.0817263, 0.0546783]    r2=0.00966890, icdist=0.9885120, deltaX=0, deltaY=0
#4:   [2.6080200, 1.7448800]    r2=9.84637000, icdist=0.0310503, deltaX=0, deltaY=0
end:  [0.0819209, 0.0548085]
true: [0.9327440, 0.6240440]

r2很大时,r2*r2*r2很大,因此icdist非常小,因此下一次迭代以非常小的r2开始。当r2非常小时,icdist接近1,因此xy分别设置为x0y0,我们是返回大r2

那么为什么r2首先如此之大?因为这些点可能靠近焦平面产生,在这种情况下它们远离光轴(因此非常大r2)。请参阅以下日志示例:

img_pt#0=[991.4992804037340, 629.5460091483255], r2=10.07660, norm(cv_undist-true)=1.0236800
img_pt#1=[5802.666489402056, 4402.387472311543], r2=554.4490, norm(cv_undist-true)=2.1568300
img_pt#2=[5040.551339386630, 5943.173381042060], r2=639.7070, norm(cv_undist-true)=2.1998700
img_pt#3=[741.9742544382640, 572.9513930063181], r2=5.749100, norm(cv_undist-true)=0.8158670
img_pt#4=[406.9101658356062, 403.0152736214052], r2=1.495890, norm(cv_undist-true)=0.1792810
img_pt#5=[516.2079583447821, 1038.026553216831], r2=10.88760, norm(cv_undist-true)=1.0494500
img_pt#6=[1876.220394606081, 8129.280202695572], r2=747.5450, norm(cv_undist-true)=2.2472900
img_pt#7=[236.9935231831764, 329.3418854620716], r2=0.599625, norm(cv_undist-true)=0.0147487
img_pt#8=[1037.586015858139, 1346.494838992490], r2=25.05890, norm(cv_undist-true)=1.2998400
img_pt#9=[499.9808133105154, 715.6213031242644], r2=5.210870, norm(cv_undist-true)=0.7747020

您可以看到,对于大多数积分,r2非常大,除了少数(#3,#4&amp;#7),这些积分也与最佳失真准确度相关。

这个问题是由于在OpenCV中实现的特定的无失真算法,其已被选择用于其效率。其他非线性优化算法(例如Levenberg-Marquardt)会更准确但速度也更慢,并且在大多数应用中肯定会出现过度杀伤。

答案 1 :(得分:3)

让我通过opencv来源。但首先我提出的“纯”opencv函数在源代码中有效(请在下面阅读我如何得到这一点)与您的代码合并以显示它作为库1:

#include <iostream>
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;
#define DEG2RAD (3.1415293/180.0)
#define RAD2DEG (1.0/DEG2RAD)

Point2f Project(Point3f p, double R[], double t[], double k[], double fx, double fy, double cx, double cy) {
        double X = p.x, Y = p.y, Z = p.z;
        double x = R[0]*X + R[1]*Y + R[2]*Z + t[0];
        double y = R[3]*X + R[4]*Y + R[5]*Z + t[1];
        double z = R[6]*X + R[7]*Y + R[8]*Z + t[2];
        double r2, r4, r6, a1, a2, a3, cdist, icdist2;
        double xd, yd;

        z = z ? 1./z : 1;
        x *= z; y *= z;

        r2 = x*x + y*y;
        r4 = r2*r2;
        r6 = r4*r2;
        a1 = 2*x*y;
        a2 = r2 + 2*x*x;
        a3 = r2 + 2*y*y;
        cdist = 1 + k[0]*r2 + k[1]*r4 + k[4]*r6;
        icdist2 = 1./(1 + k[5]*r2 + k[6]*r4 + k[7]*r6);
        xd = x*cdist*icdist2 + k[2]*a1 + k[3]*a2;
        yd = y*cdist*icdist2 + k[2]*a3 + k[3]*a1;

        double xRet = xd*fx + cx;
        double yRet = yd*fy + cy;

        return Point2f(xRet, yRet);
}

int main() {
    const int npoints = 10; // number of points

    // extrinsic
    const Point3f tvec(10, 20, 30);
    Point3f rvec(3, 5, 7);
    cout << "Finding extrinsic parameters (PnP)" << endl;
    cout<<"Test transformations: ";
    cout<<"Rotation: "<<rvec<<"; translation: "<<tvec<<endl;
    rvec*=DEG2RAD;

    // intrinsic
    Mat_ <double>cameraMatrix(3, 3);
    cameraMatrix << 300., 0., 200., 0, 300., 100., 0., 0., 1.;
    Mat_ <double>distCoeffs(1, 5); //  (k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6]]) of 4, 5, or 8 elements.
    distCoeffs << 1.2, 0.2, 0., 0., 0.;  // non-zero distortion
    //distCoeffs << 0.0, 0.0, 0.0, 0.0, 0.0; // zero distortion
    //distCoeffs << 1.8130418031666484e+000, -1.3285019729932657e+001, -1.6921715019797313e-002, -1.3327183367510961e-001, -5.2725832482783389e+001;
    cout<<"distrotion coeff: "<<distCoeffs<<endl;

    cout<<"============= Running PnP..."<<endl;
    vector<Point3f> objPts(npoints);
    vector<Point2f> imagePoints(npoints);
    Mat rvec_est, tvec_est;
    randu(Mat(objPts), 0.0f, 100.0f);

    // project
    projectPoints(Mat(objPts), Mat(rvec), Mat(tvec), cameraMatrix, distCoeffs, Mat(imagePoints));

    std::cout << objPts << std::endl;
    std::cout << imagePoints << std::endl;

    double R[9];
    Mat matR( 3, 3, CV_64F, R);
    Mat_<double> m(1,3);
    m << (double)rvec.x, (double)rvec.y, (double)rvec.z;

    Rodrigues(m, matR);
    std::cout << matR << std::endl;
    double t[3] = {tvec.x, tvec.y, tvec.z};
    double k[8] = {1.2, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    double fx = 300, fy = 300, cx = 200, cy = 100;

    for(int i=0;i<objPts.size();i++)
        std::cout << Project(objPts[i], R, t, k, fx, fy, cx, cy) << "; ";
    std::cout << std::endl;

    // extrinsic
    solvePnP(objPts, imagePoints, cameraMatrix, distCoeffs, rvec_est, tvec_est);
    cout<<"Rotation: "<<rvec_est*RAD2DEG<<endl;
    cout<<"Translation "<<tvec_est<<endl;



    return 0;
}

R是旋转,t平移,k失真。看看'r2'计算 - 它是x * x + y * y,但x,y是刚应用平移和旋转后的位置(按z缩放)。这个r代表(如wikpedia所说)“图像中的方形距离投影通过理想的针孔模型”。我们可以说projectPoints实现没问题。

我是如何得到这个结果的:

我正在挖掘版本2.4.8。如果您转到calib3d模块中的calibration.cpp,请从

开始
void cv::projectPoints( InputArray _opoints,
                        InputArray _rvec,
                        InputArray _tvec,
                        InputArray _cameraMatrix,
                        InputArray _distCoeffs,
                        OutputArray _ipoints,
                        OutputArray _jacobian,
                        double aspectRatio )
{
    Mat opoints = _opoints.getMat();
    int npoints = opoints.checkVector(3), depth = opoints.depth();
    CV_Assert(npoints >= 0 && (depth == CV_32F || depth == CV_64F));

    CvMat dpdrot, dpdt, dpdf, dpdc, dpddist;
    CvMat *pdpdrot=0, *pdpdt=0, *pdpdf=0, *pdpdc=0, *pdpddist=0;

    _ipoints.create(npoints, 1, CV_MAKETYPE(depth, 2), -1, true);
    CvMat c_imagePoints = _ipoints.getMat();
    CvMat c_objectPoints = opoints;
    Mat cameraMatrix = _cameraMatrix.getMat();

    Mat rvec = _rvec.getMat(), tvec = _tvec.getMat();
    CvMat c_cameraMatrix = cameraMatrix;
    CvMat c_rvec = rvec, c_tvec = tvec;

    double dc0buf[5]={0};
    Mat dc0(5,1,CV_64F,dc0buf);
    Mat distCoeffs = _distCoeffs.getMat();
    if( distCoeffs.empty() )
        distCoeffs = dc0;
    CvMat c_distCoeffs = distCoeffs;
    int ndistCoeffs = distCoeffs.rows + distCoeffs.cols - 1;

    if( _jacobian.needed() )
    {
        // cut out, we dont use this part
    }

    cvProjectPoints2( &c_objectPoints, &c_rvec, &c_tvec, &c_cameraMatrix, &c_distCoeffs,
                      &c_imagePoints, pdpdrot, pdpdt, pdpdf, pdpdc, pdpddist, aspectRatio );
}

没什么特别的,对吗?根本没有内容操纵。让我们深入一点:

CV_IMPL void cvProjectPoints2( const CvMat* objectPoints,
                  const CvMat* r_vec,
                  const CvMat* t_vec,
                  const CvMat* A,
                  const CvMat* distCoeffs,
                  CvMat* imagePoints, CvMat* dpdr,
                  CvMat* dpdt, CvMat* dpdf,
                  CvMat* dpdc, CvMat* dpdk,
                  double aspectRatio )
{
    Ptr<CvMat> matM, _m;
    Ptr<CvMat> _dpdr, _dpdt, _dpdc, _dpdf, _dpdk;

    int i, j, count;
    int calc_derivatives;
    const CvPoint3D64f* M;
    CvPoint2D64f* m;
    double r[3], R[9], dRdr[27], t[3], a[9], k[8] = {0,0,0,0,0,0,0,0}, fx, fy, cx, cy;
    CvMat _r, _t, _a = cvMat( 3, 3, CV_64F, a ), _k;
    CvMat matR = cvMat( 3, 3, CV_64F, R ), _dRdr = cvMat( 3, 9, CV_64F, dRdr );


    // some code not important ...


     if( r_vec->rows == 3 && r_vec->cols == 3 )
{
    _r = cvMat( 3, 1, CV_64FC1, r );
    cvRodrigues2( r_vec, &_r );
    cvRodrigues2( &_r, &matR, &_dRdr );
    cvCopy( r_vec, &matR );
}
else
{
    _r = cvMat( r_vec->rows, r_vec->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(r_vec->type)), r );
    cvConvert( r_vec, &_r );
    cvRodrigues2( &_r, &matR, &_dRdr );
}

最后一部分很重要,因为我们使用cv :: Rodriguez从旋转矢量创建一个旋转矩阵。后来在函数中我们也创建了转换矩阵,但仍然没有数据操作。在ProjectPoints2中进一步:

    fx = a[0]; fy = a[4];
    cx = a[2]; cy = a[5];

    if( fixedAspectRatio )
        fx = fy*aspectRatio;

    if( distCoeffs )
    {
        if( !CV_IS_MAT(distCoeffs) ||
            (CV_MAT_DEPTH(distCoeffs->type) != CV_64F &&
            CV_MAT_DEPTH(distCoeffs->type) != CV_32F) ||
            (distCoeffs->rows != 1 && distCoeffs->cols != 1) ||
            (distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 4 &&
            distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 5 &&
            distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 8) )
            CV_Error( CV_StsBadArg, cvDistCoeffErr );

        _k = cvMat( distCoeffs->rows, distCoeffs->cols,
                    CV_MAKETYPE(CV_64F,CV_MAT_CN(distCoeffs->type)), k );
        cvConvert( distCoeffs, &_k );
    }

这里我们设置来自相机矩阵和主点坐标的焦距。我们还设置了包含失真系数的数组k。现在我们完成了变量设置。我们去计算:

    double X = M[i].x, Y = M[i].y, Z = M[i].z;
    double x = R[0]*X + R[1]*Y + R[2]*Z + t[0];
    double y = R[3]*X + R[4]*Y + R[5]*Z + t[1];
    double z = R[6]*X + R[7]*Y + R[8]*Z + t[2];
    double r2, r4, r6, a1, a2, a3, cdist, icdist2;
    double xd, yd;

    z = z ? 1./z : 1;
    x *= z; y *= z;

    r2 = x*x + y*y;
    r4 = r2*r2;
    r6 = r4*r2;
    a1 = 2*x*y;
    a2 = r2 + 2*x*x;
    a3 = r2 + 2*y*y;
    cdist = 1 + k[0]*r2 + k[1]*r4 + k[4]*r6;
    icdist2 = 1./(1 + k[5]*r2 + k[6]*r4 + k[7]*r6);
    xd = x*cdist*icdist2 + k[2]*a1 + k[3]*a2;
    yd = y*cdist*icdist2 + k[2]*a3 + k[3]*a1;

    m[i].x = xd*fx + cx;    // here projection
    m[i].y = yd*fy + cy;

我们的功能与我在顶部/

上提供的功能完全相同