OpenCV学习(5): 图像畸变校正

畸变校正


畸变

摄像机的成像过程主要是主要涉及到几个坐标系的变换(具体过程可以参考相机模型):

Created with Raphaël 2.1.2 物体 世界坐标 摄像机坐标 图像物理坐标 图像像素坐标

从摄像机成像畸变的产生于是其“天生”的,不可避免的,这主要是由于透镜成像原理导致的。其畸变的原理可以参考相机模型)。它的畸变按照原理可以分解为切向畸变和径向畸变。

[xy]=(1+k1r2+k2r4+k3r6)[xy]+[2p1xy+p2(r2+2x2)2p1(r2+2y2)+2p2xy] [ x ′ y ′ ] = ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) [ x y ] + [ 2 p 1 x y + p 2 ( r 2 + 2 x 2 ) 2 p 1 ( r 2 + 2 y 2 ) + 2 p 2 x y ]

其中, [x,y] [ x ′ , y ′ ] 为畸变后的位置, [x,y] [ x , y ] 为畸变前的位置, [ki,pi] [ k i , p i ] 为畸变系数。当然,其实畸变系数远远不止这么四个,但通常情况下可以仅考虑这四个。

校正原理

畸变校正的关键之处就是要找到畸变前后的点位置的对应关系。假定未畸变前,图像中各点的像素坐标可以通过公式得到:

{x=Xc/Zcy=Yc/Zc{u=fxx+cxv=fyy+cy { x ′ = X c / Z c y ′ = Y c / Z c { u = f x ⋅ x ′ + c x v = f y ⋅ y ′ + c y

如果不存在畸变,那么理想情况下,摄像机成像的坐标转换就可以按照上式来进行计算。在有畸变的情况下,畸变后的坐标:
[x′′y′′]=(1+k1r2+k2r4+k3r6)[xy]+[2p1xy+p2(r2+2x2)2p1(r2+2y2)+2p2xy] [ x ″ y ″ ] = ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) [ x ′ y ′ ] + [ 2 p 1 x ′ y ′ + p 2 ( r 2 + 2 x ′ 2 ) 2 p 1 ( r 2 + 2 y ′ 2 ) + 2 p 2 x ′ y ′ ]

其中 r2=x2+y2 r 2 = x ′ 2 + y ′ 2
与此同时可以得到畸变图像的各个像素的新坐标为:
{ud=fxx′′+cxvd=fyy′′+cy { u d = f x ⋅ x ″ + c x v d = f y ⋅ y ″ + c y

由此,我们得到了一个图像从摄像机坐标系,然后经过畸变,最后得到(畸变)图像的整个坐标变换过程中的各个点的映射关系。

畸变校正的目的就是要找到对应点对的像素值关系。将畸变后的位置的像素值赋给原位置。即:

f(u,v)=f(h(u,v))=f(ud,vd) f ( u , v ) = f ( h ( u , v ) ) = f ( u d , v d )

注意,由非畸变图像到畸变图像的映射关系为:
f(ud,vd)=f(u,v) f ( u d , v d ) = f ( u , v )

值得注意的是,由于存在畸变,畸变前坐标为整数,畸变后并不一定为整数。而在图像像素坐标系中,坐标都是整数,因此在这个赋值过程中往往存在取整或者插值操作。

OpenCV畸变校正源码

initUndistortRectifyMap函数计算的就是两幅图像中,从未畸变图像到畸变图像的映射关系(map_1,map_2).

/*
@param cameraMatrix 相机矩阵 [{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{1}].
@param distCoeffs 畸变系数向量 (k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6[, s_1, s_2, s_3, s_4[, \tau_x, \tau_y]]]]),默认值为0
@param R 可选的校正矩阵 (3x3). 由#stereoRectify 计算得来的 R1 或 R2 可以在此处使用。默认为单位矩阵,在 cvInitUndistortMap R 假定为单位矩阵.
@param newCameraMatrix 新的相机矩阵  [{f_x'}{0}{c_x'}{0}{f_y'}{c_y'}{0}{0}{1}]
@param size 去畸变后图像尺寸.
@param m1type 第一个输出 map的类型:CV_32FC1, CV_32FC2 or CV_16SC2
@param map1 第一个输出 map.
@param map2 第二个输出 map.
 */

#include <opencv2\opencv.hpp>

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();

    //默认第一个输出map的类型为CV_16SC2
    if( m1type <= 0 )
        m1type = CV_16SC2;
    CV_Assert( m1type == CV_16SC2 || m1type == CV_32FC1 || m1type == CV_32FC2 );
    _map1.create( size, m1type );

    // 为什么CV_16SC2就不释放_map2 ,反而要开辟空间, 2通道 难道不足以存储坐标??
    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_<double> R = Mat_<double>::eye(3, 3);
    Mat_<double> A = Mat_<double>(cameraMatrix), Ar;

    //如果新相机矩阵为空,默认为(旧)相机矩阵
    if( !newCameraMatrix.empty() )
        Ar = Mat_<double>(newCameraMatrix);
    else
        Ar = getDefaultNewCameraMatrix( A, size, true );

    //_matR默认为单位矩阵
    if( !matR.empty() )
        R = Mat_<double>(matR);

    //畸变系数向量默认为0
    if( !distCoeffs.empty() )
        distCoeffs = Mat_<double>(distCoeffs);
    else
    {
        distCoeffs.create(14, 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));

    //LU分解求新的内参矩阵Ar与校正矩阵R乘积的逆矩阵iR
    Mat_<double> iR = (Ar.colRange(0,3)*R).inv(DECOMP_LU);
    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);

    //14个畸变系数,大多用到的只有(k1,k2,p1,p2)
    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) ||
               distCoeffs.size() == Size(1, 12) || distCoeffs.size() == Size(12, 1) ||
               distCoeffs.size() == Size(1, 14) || distCoeffs.size() == Size(14, 1));

    if( distCoeffs.rows != 1 && !distCoeffs.isContinuous() )
        distCoeffs = distCoeffs.t();

    const double* const distPtr = distCoeffs.ptr<double>();
    double k1 = distPtr[0];
    double k2 = distPtr[1];
    double p1 = distPtr[2];
    double p2 = distPtr[3];
    double k3 = distCoeffs.cols + distCoeffs.rows - 1 >= 5 ? distPtr[4] : 0.;
    double k4 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? distPtr[5] : 0.;
    double k5 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? distPtr[6] : 0.;
    double k6 = distCoeffs.cols + distCoeffs.rows - 1 >= 8 ? distPtr[7] : 0.;
    double s1 = distCoeffs.cols + distCoeffs.rows - 1 >= 12 ? distPtr[8] : 0.;
    double s2 = distCoeffs.cols + distCoeffs.rows - 1 >= 12 ? distPtr[9] : 0.;
    double s3 = distCoeffs.cols + distCoeffs.rows - 1 >= 12 ? distPtr[10] : 0.;
    double s4 = distCoeffs.cols + distCoeffs.rows - 1 >= 12 ? distPtr[11] : 0.;
    double tauX = distCoeffs.cols + distCoeffs.rows - 1 >= 14 ? distPtr[12] : 0.;
    double tauY = distCoeffs.cols + distCoeffs.rows - 1 >= 14 ? distPtr[13] : 0.;

    //tauX,tauY梯形畸变,缺省默认为matTilt为单位阵
    // Matrix for trapezoidal distortion of tilted image sensor
    cv::Matx33d matTilt = cv::Matx33d::eye();
    cv::detail::computeTiltProjectionMatrix(tauX, tauY, &matTilt);

    for( int i = 0; i < size.height; i++ )
    {
        float* m1f = map1.ptr<float>(i);
        float* m2f = map2.empty() ? 0 : map2.ptr<float>(i);
        short* m1 = (short*)m1f;
        ushort* m2 = (ushort*)m2f;

        //利用逆矩阵iR将二维图像坐标[j,i](不是[i,j])转换到摄像机坐标系(_x,_y,_w)
        double _x = i*ir[1] + ir[2], _y = i*ir[4] + ir[5], _w = i*ir[7] + ir[8];

        for( int j = 0; j < size.width; j++, _x += ir[0], _y += ir[3], _w += ir[6] )
        {
            //摄像机坐标系归一化,令Z=1平面
            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 xd = (x*kr + p1*_2xy + p2*(r2 + 2*x2) + s1*r2+s2*r2*r2);
            double yd = (y*kr + p1*(r2 + 2*y2) + p2*_2xy + s3*r2+s4*r2*r2);
           //根据求取的xd,yd将三维坐标重投影到二维畸变图像坐标(u,v)
            cv::Vec3d vecTilt = matTilt*cv::Vec3d(xd, yd, 1);
            double invProj = vecTilt(2) ? 1./vecTilt(2) : 1;
            double u = fx*invProj*vecTilt(0) + u0;
            double v = fy*invProj*vecTilt(1) + v0;
            //保存u,v的值到Mapx,Mapy中
            if( m1type == CV_16SC2 )
            {
                int iu = saturate_cast<int>(u*INTER_TAB_SIZE);
                int iv = saturate_cast<int>(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;
            }
        }
    }
}
### 回答1: OpenCV图像畸变校正是一种图像处理方法,用于纠正图像中由于摄像头镜头畸变引起的失真。 畸变分为径向畸变和切向畸变两种。径向畸变是由于镜头的曲率不均匀而引起的,主要表现为图像中心附近的特征点扩散或收缩。切向畸变是由于镜头与图像平面不平行而引起的,主要表现为图像中心附近的特征点偏移。 为了校正图像畸变,首先需要获取相机的内部参数矩阵。这些参数包括焦距、主点坐标和畸变系数。然后,可以通过使用这些参数计算出畸变校正的映射函数。 畸变校正的映射函数是根据径向畸变和切向畸变的模型进行计算的。对于径向畸变,可以使用二项式模型进行建模。对于切向畸变,可以使用仿射变换进行建模。 一旦得到畸变校正的映射函数,就可以将图像中的每个像素坐标映射到校正后的图像中的对应位置。根据映射函数的计算结果,可以通过插值算法获得对应位置的像素值。 OpenCV提供了用于畸变校正的函数cv::undistort()。这个函数接受输入图像、相机内部参数矩阵和畸变系数作为输入,并返回校正后的图像。 通过使用OpenCV畸变校正函数,我们可以将镜头畸变引起的图像失真校正,得到更加真实和准确的图像。这对于计算机视觉和图像处理任务非常重要,例如物体检测、目标跟踪和三维重构等。 ### 回答2: OpenCV是一个开源的计算机视觉库,提供了一系列图像处理和计算机视觉算法。其中一个功能是图像畸变校正,可以通过使用OpenCV库中的函数来实现。 图像畸变校正校正图像中的畸变,即由于相机镜头和光学系统的特性引起的图像畸变图像畸变一般包括径向畸变和切向畸变。 径向畸变是由于相机镜头的形状引起的,使得离中心点越远的像素位置与实际位置之间有所偏差。切向畸变则是由于镜头装配不完美或相机传感器平面与光轴不平行导致的图像失真。 校正图像畸变的步骤如下: 1. 从相机或设备获取图像。 2. 检测并识别相机的内标定参数,包括相机的焦距、畸变系数等。 3. 利用相机内参和外参,根据畸变模型计算每个像素的畸变向量。 4. 对每个像素点应用畸变向量,将其映射到没有畸变的位置。 5. 可以选择进行裁剪,将不需要的黑边去除,得到校正后的图像OpenCV中提供了函数cv::undistort()来实现图像畸变校正。该函数需要输入畸变图像、相机的内标定参数矩阵、畸变系数等作为输入,然后返回校正后的图像。 总而言之,OpenCV提供了实现图像畸变校正的函数,可以便捷地对图像进行畸变校正,提高图像的质量和准确性。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值