• 作者:老汪软件技巧
  • 发表时间:2023-12-31 10:00
  • 浏览量:

文章目录

最近在使用对鱼眼相机图像去畸变时发现一个问题,基于针孔模型去畸变时所使用的参数和之前十四讲以及视觉SLAM中的畸变系数有一点不一样。 1.十四讲中的去畸变公式

首先是十四讲或者视觉SLAM中的方法,针孔模型的畸变系数为[k1, k2, p1, p2],使用以下去畸变公式计算:

OpenCV中initUndistortRectifyMap ()函数与十四讲中去畸变公式的区别探究

2. 中的去畸变公式

在中可以通过Map()函数获得原始图像和矫正图像之间的映射表,然后remap()函数根据映射表对整个图像进行映射处理实现去畸变。

 cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat(), K, imageSize, CV_16SC2, map1, map2);
 cv::remap(raw_image, undistortImg, map1, map2, cv::INTER_LINEAR, cv::BORDER_CONSTANT);

具体实现可以见文章《对鱼眼相机图像进行去畸变处理》

Map()函数的声明如下:

void cv::initUndistortRectifyMap	
(	InputArray 	cameraMatrix,     // 原相机内参矩阵
        InputArray 	distCoeffs,       // 原相机畸变参数
        InputArray 	R,                // 可选的修正变换矩阵 
        InputArray 	newCameraMatrix,  // 新相机内参矩阵
        Size 	        size,             // 去畸变后图像的尺寸
        int 	        m1type,           // 第一个输出的映射(map1)的类型,CV_32FC1 or CV_16SC2
        OutputArray 	map1,             // 第一个输出映射
        OutputArray 	map2              // 第二个输出映射
)

有意思的是,这里的相机畸变参数是可选的,可以是4个参数k1, k2, p1, p2,可以是5个参数k1, k2, p1, p2, k3,也可以是8个参数k1, k2, p1, p2, k3, k4, k5, k6。

后来检索了一下Map()函数中的畸变公式,如下:

OpenCV中initUndistortRectifyMap ()函数与十四讲中去畸变公式的区别探究

推导过程的核心是:

当k3, k4, k5, k6以及s1, s2, s3, s4均为0的时候该去畸变公式和十四讲中的公式就一样了,即十四讲中的去畸变公式是该公式的一个简略版。

3. 4个参数和8个参数之间的区别

已经说过,Map()函数中的去畸变参数可以是4个参数k1, k2, p1, p2,可以是5个参数k1, k2, p1, p2, k3,也可以是8个参数k1, k2, p1, p2, k3, k4, k5, k6。

对于普通的广角相机图像,径向畸变和切向畸变一般都比较小,所以仅使用k1, k2, p1, p2就可以完成去畸变过程,对应十四讲中的去畸变公式。

对于鱼眼相机,一般会存在比较大的径向畸变,所以需要更高阶的径向畸变系数k3, k4, k5, k6,至于为什么是1+k1r2+k2r4+k3r61+k4r2+k5r4+k6r6\frac{1+k_1r^2+k_2r^4+k_3r^6}{1+k_4r^2+k_5r^4+k_6r^6}1+k4​r2+k5​r4+k6​r61+k1​r2+k2​r4+k3​r6​这种比值形式,暂时为找到公式的设计原理,应该是基于对径向畸变的某种考量进行的设计。

根据标定工具和相机模型的不同,获取的鱼眼相机畸变系数可能有多种形式,需要知道的是都可以在去畸变函数中使用。而且有时通过标定得到完整的8个去畸变参数k1, k2, p1, p2, k3, k4, k5, k6,这就使得在调用函数去畸变事需要使用完整的参数,只使用k1, k2, p1, p2会得到失败的结果。

4.Map()函数源码

void cv::initUndistortRectifyMap( InputArray _cameraMatrix, InputArray _distCoeffs,
                              InputArray _matR, InputArray _newCameraMatrix,
                              Size size, int m1type, OutputArray _map1, OutputArray _map2 )
{
    //相机内参、畸变矩阵
    Mat cameraMatrix = _cameraMatrix.getMat(), distCoeffs = _distCoeffs.getMat();
    //旋转矩阵、摄像机参数矩阵
    Mat matR = _matR.getMat(), newCameraMatrix = _newCameraMatrix.getMat();
 
    if( m1type <= 0 )
        m1type = CV_16SC2;
    CV_Assert( m1type == CV_16SC2 || m1type == CV_32FC1 || m1type == CV_32FC2 );
    _map1.create( size, m1type );
    Mat map1 = _map1.getMat(), map2;
    if( m1type != CV_32FC2 )
    {
        _map2.create( size, m1type == CV_16SC2 ? CV_16UC1 : CV_32FC1 );
        map2 = _map2.getMat();
    }
    else
        _map2.release();
 
    Mat_ R = Mat_::eye(3, 3);
    //A为相机内参
    Mat_ A = Mat_(cameraMatrix), Ar;
 
    //Ar 为摄像机坐标参数
    if( newCameraMatrix.data )
        Ar = Mat_(newCameraMatrix);
    else
        Ar = getDefaultNewCameraMatrix( A, size, true );
    //R  为旋转矩阵
    if( matR.data )
        R = Mat_(matR);
 
    //distCoeffs为畸变矩阵
    if( distCoeffs.data )
        distCoeffs = Mat_(distCoeffs);
    else
    {
        distCoeffs.create(8, 1, CV_64F);
        distCoeffs = 0.;
    }
 
    CV_Assert( A.size() == Size(3,3) && A.size() == R.size() );
    CV_Assert( Ar.size() == Size(3,3) || Ar.size() == Size(4, 3));
 
    //摄像机坐标系第四列参数  旋转向量转为旋转矩阵
    Mat_ iR = (Ar.colRange(0,3)*R).inv(DECOMP_LU);
    //ir IR矩阵的指针
    const double* ir = &iR(0,0);
    //获取相机的内参 u0  v0 为主坐标点   fx fy 为焦距
    double u0 = A(0, 2),  v0 = A(1, 2);
    double fx = A(0, 0),  fy = A(1, 1);
 
    CV_Assert( distCoeffs.size() == Size(1, 4) || distCoeffs.size() == Size(4, 1) ||
               distCoeffs.size() == Size(1, 5) || distCoeffs.size() == Size(5, 1) ||
               distCoeffs.size() == Size(1, 8) || distCoeffs.size() == Size(8, 1));
 
    if( distCoeffs.rows != 1 && !distCoeffs.isContinuous() )
        distCoeffs = distCoeffs.t();
    
    //畸变参数计算
    double k1 = ((double*)distCoeffs.data)[0];
    double k2 = ((double*)distCoeffs.data)[1];
    double p1 = ((double*)distCoeffs.data)[2];
    double p2 = ((double*)distCoeffs.data)[3];
    double k3 = distCoeffs.cols + distCoeffs.rows - 1 >= 5 ? ((double*)distCoeffs.data)[4] : 0.;
    double k4 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? ((double*)distCoeffs.data)[5] : 0.;
    double k5 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? ((double*)distCoeffs.data)[6] : 0.;
    double k6 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? ((double*)distCoeffs.data)[7] : 0.;
    //图像高度
    for( int i = 0; i < size.height; i++ )
    {
        //映射矩阵map1 
        float* m1f = (float*)(map1.data + map1.step*i);
        //映射矩阵map2
        float* m2f = (float*)(map2.data + map2.step*i);
        short* m1 = (short*)m1f;
        ushort* m2 = (ushort*)m2f;
        //摄像机参数矩阵最后一列向量转换成的3*3矩阵参数
        double _x = i*ir[1] + ir[2];
        double _y = i*ir[4] + ir[5];
        double _w = i*ir[7] + ir[8];
        //图像宽度
        for( int j = 0; j < size.width; j++, _x += ir[0], _y += ir[3], _w += ir[6] )
        {
            //获取摄像机坐标系第四列参数
            double w = 1./_w, x = _x*w, y = _y*w;
            double x2 = x*x, y2 = y*y;
            double r2 = x2 + y2, _2xy = 2*x*y;
            double kr = (1 + ((k3*r2 + k2)*r2 + k1)*r2)/(1 + ((k6*r2 + k5)*r2 + k4)*r2);
            double u = fx*(x*kr + p1*_2xy + p2*(r2 + 2*x2)) + u0;
            double v = fy*(y*kr + p1*(r2 + 2*y2) + p2*_2xy) + v0;
            if( m1type == CV_16SC2 )
            {
                int iu = saturate_cast(u*INTER_TAB_SIZE);
                int iv = saturate_cast(v*INTER_TAB_SIZE);
                m1[j*2] = (short)(iu >> INTER_BITS);
                m1[j*2+1] = (short)(iv >> INTER_BITS);
                m2[j] = (ushort)((iv & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (iu & (INTER_TAB_SIZE-1)));
            }
            else if( m1type == CV_32FC1 )
            {
                m1f[j] = (float)u;
                m2f[j] = (float)v;
            }
            else
            {
                m1f[j*2] = (float)u;
                m1f[j*2+1] = (float)v;
            }
        }
    }
}