目录

0.极限约束,对极校正

1.摄像机成像原理简述

2.成像畸变

2.1. 畸变数学模型

2.2. 公式推导

3.畸变校正

3.1. 理论推导

4. 图像去畸变**

5. 图像尺度缩放与内参的关系**

5.1 undistortPoints()

5.2 initUndistortRectifyMap()

5.3 undistort()

6.UndistortPoints源码


 

0.极限约束,对极校正


 

 

1.摄像机成像原理简述

成像的过程实质上是几个坐标系的转换。首先空间中的一点由 世界坐标系 转换到 摄像机坐标系 ,然后再将其投影到成像平面 ( 图像物理坐标系 ) ,最后再将成像平面上的数据转换到图像平面 ( 图像像素坐标系 )

图像像素坐标系 (uOv坐标系) 下的无畸变坐标 (U, V),经过 经向畸变切向畸变 后落在了uOv坐标系(Ud, Vd) 上。即就是说,真实图像 imgR畸变图像 imgD 之间的关系为: imgR(U, V) = imgD(Ud, Vd)

img

2.成像畸变

2.1. 畸变数学模型

摄像头成像畸变的数学模型 (符合的对应关系有问题,可能会造成一些干扰,公式主要看后面推导的过程)

img

 

2.2. 公式推导

公式推导:

这里写图片描述

3.畸变校正

3.1. 理论推导

我们已知的是畸变后的图像,要得到没有畸变的图像就要通过畸变模型推导其映射关系。 真实图像 imgR畸变图像 imgD 之间的关系为: imgR(U, V) = imgD(Ud, Vd) 。通过这个关系,找出所有的 imgR(U, V)(U, V) 映射到 (Ud, Vd) 中的 (Ud, Vd) 往往不是整数 (U和V是整数,因为它是我们要组成图像的像素坐标位置,以这正常图像的坐标位置去求在畸变图像中的坐标位置,取出对应的像素值,这也是正常图像的像素值)。 但是畸变的像素往往不是整数,所以需要通过插值来进行求解,详细见我之前的博客 [图像]图像缩放算法-双线性内插法

这里写图片描述

 

 

 

4. 图像去畸变**

图像去畸变的思路是:对于目标图像(无畸变)上的每个像素点,转换到normalize平面,再进行畸变并投影到源图像(带畸变), 获取原图对应位置的像素值作为目标图像该点的像素值。

这里容易有一个误解,以为去畸变是对畸变图像进行畸变逆变换得到无畸变图像,实际不是的,畸变模型太复杂了,很难求逆变换,所以是将无畸变图像进行畸变变换到原图像去获得对应像素值

 

图像去畸变流程如下:

注意:源相机和目标相机使用的内参矩阵不一定是一样的。如果是调用opencv的undistort()函数,cameraMatrix是源相机的内参矩阵,newCameraMatrix是目标相机的内参矩阵,如果不设置newCameraMatrix,则默认与源相机内参一样,即去畸变后,相机的内参矩阵不变。

5. 图像尺度缩放与内参的关系**

结论:图像分辨率缩放比例k, 相机焦距光心等比例缩放k, 畸变系数不变。

证明:图像缩放k倍后,图像平面所有的像素点坐标变为:

而图像畸变是发生在normalize平面,不管图像分辨率如何改变,normalize平面(只取决于焦距光心)是不变的,所以畸变系数不变。

 

5.1 undistortPoints()

1.1功能: 从观测点坐标计算理想点坐标。

void cv::undistortPoints(InputArraysrc,
  OutputArraydst,
  InputArraycameraMatrix,
  InputArraydistCoeffs,
  InputArrayR = noArray(),
  InputArrayP = noArray()
 )  

 

5.2 initUndistortRectifyMap()

2.1功能 Computes the undistortion and rectification transformation map. 计算去畸变和校正变换映射。

void cv::initUndistortRectifyMap(InputArraycameraMatrix,
  InputArraydistCoeffs,
  InputArrayR,
  InputArraynewCameraMatrix,
  Sizesize,
  intm1type,
  OutputArraymap1,
  OutputArraymap2
 )  

模型见4去畸变

5.3 undistort()

void cv::undistort(InputArraysrc,
  OutputArraydst,
  InputArraycameraMatrix,
  InputArraydistCoeffs,
  InputArraynewCameraMatrix = noArray()
 )  

3.1 功能 Transforms an image to compensate for lens distortion. , 对图像进行变换以补偿镜头失真。

The function transforms an image to compensate radial and tangential lens distortion.

The function is simply a combination of cv::initUndistortRectifyMap (with unity R ) and cv::remap (with bilinear interpolation). See the former function for details of the transformation being performed.

6.UndistortPoints源码

 

void cvUndistortPointsInternal( const CvMat* _src, CvMat* _dst, const CvMat* _cameraMatrix,
                   const CvMat* _distCoeffs,
                   const CvMat* matR, const CvMat* matP, cv::TermCriteria criteria)
{
    // 判断迭代条件是否有效
    CV_Assert(criteria.isValid());
    // 定义中间变量--A相机内参数组,和matA共享内存;RR-矫正变换数组,和_RR共享内存
    // k-畸变系数数组
    double A[3][3], RR[3][3], k[14]={0,0,0,0,0,0,0,0,0,0,0,0,0,0};
    CvMat matA=cvMat(3, 3, CV_64F, A), _Dk;
    CvMat _RR=cvMat(3, 3, CV_64F, RR);
    cv::Matx33d invMatTilt = cv::Matx33d::eye();
    cv::Matx33d matTilt = cv::Matx33d::eye();
    
    // 检查输入变量是否有效
    CV_Assert( CV_IS_MAT(_src) && CV_IS_MAT(_dst) &&
        (_src->rows == 1 || _src->cols == 1) &&
        (_dst->rows == 1 || _dst->cols == 1) &&
        _src->cols + _src->rows - 1 == _dst->rows + _dst->cols - 1 &&
        (CV_MAT_TYPE(_src->type) == CV_32FC2 || CV_MAT_TYPE(_src->type) == CV_64FC2) &&
        (CV_MAT_TYPE(_dst->type) == CV_32FC2 || CV_MAT_TYPE(_dst->type) == CV_64FC2));
 
    CV_Assert( CV_IS_MAT(_cameraMatrix) &&
        _cameraMatrix->rows == 3 && _cameraMatrix->cols == 3 );
 
    cvConvert( _cameraMatrix, &matA );// _cameraMatrix <--> matA / A
 
    // 判断输入的畸变系数是否有效
    if( _distCoeffs )
    {
        CV_Assert( CV_IS_MAT(_distCoeffs) &&
            (_distCoeffs->rows == 1 || _distCoeffs->cols == 1) &&
            (_distCoeffs->rows*_distCoeffs->cols == 4 ||
             _distCoeffs->rows*_distCoeffs->cols == 5 ||
             _distCoeffs->rows*_distCoeffs->cols == 8 ||
             _distCoeffs->rows*_distCoeffs->cols == 12 ||
             _distCoeffs->rows*_distCoeffs->cols == 14));
 
        _Dk = cvMat( _distCoeffs->rows, _distCoeffs->cols,
            CV_MAKETYPE(CV_64F,CV_MAT_CN(_distCoeffs->type)), k);// _Dk和数组k共享内存指针
 
        cvConvert( _distCoeffs, &_Dk );
        if (k[12] != 0 || k[13] != 0)
        {
            cv::detail::computeTiltProjectionMatrix<double>(k[12], k[13], NULL, NULL, NULL, &invMatTilt);
            cv::detail::computeTiltProjectionMatrix<double>(k[12], k[13], &matTilt, NULL, NULL);
        }
    }
 
    if( matR )
    {
        CV_Assert( CV_IS_MAT(matR) && matR->rows == 3 && matR->cols == 3 );
        cvConvert( matR, &_RR );// matR和_RR共享内存指针
    }
    else
        cvSetIdentity(&_RR);
 
    if( matP )
    {
        double PP[3][3];
        CvMat _P3x3, _PP=cvMat(3, 3, CV_64F, PP);
        CV_Assert( CV_IS_MAT(matP) && matP->rows == 3 && (matP->cols == 3 || matP->cols == 4));
        cvConvert( cvGetCols(matP, &_P3x3, 0, 3), &_PP );// _PP和数组PP共享内存指针
        cvMatMul( &_PP, &_RR, &_RR );// _RR=_PP*_RR 放在一起计算比较高效
    }
 
    const CvPoint2D32f* srcf = (const CvPoint2D32f*)_src->data.ptr;
    const CvPoint2D64f* srcd = (const CvPoint2D64f*)_src->data.ptr;
    CvPoint2D32f* dstf = (CvPoint2D32f*)_dst->data.ptr;
    CvPoint2D64f* dstd = (CvPoint2D64f*)_dst->data.ptr;
    int stype = CV_MAT_TYPE(_src->type);
    int dtype = CV_MAT_TYPE(_dst->type);
    int sstep = _src->rows == 1 ? 1 : _src->step/CV_ELEM_SIZE(stype);
    int dstep = _dst->rows == 1 ? 1 : _dst->step/CV_ELEM_SIZE(dtype);
 
    double fx = A[0][0];
    double fy = A[1][1];
    double ifx = 1./fx;
    double ify = 1./fy;
    double cx = A[0][2];
    double cy = A[1][2];
 
    int n = _src->rows + _src->cols - 1;
    // 开始对所有点开始遍历
    for( int i = 0; i < n; i++ )
    {
        double x, y, x0 = 0, y0 = 0, u, v;
        if( stype == CV_32FC2 )
        {
            x = srcf[i*sstep].x;
            y = srcf[i*sstep].y;
        }
        else
        {
            x = srcd[i*sstep].x;
            y = srcd[i*sstep].y;
        }
        u = x; v = y;
        x = (x - cx)*ifx;//转换到归一化图像坐标系(含有畸变)
        y = (y - cy)*ify;
 
        //进行畸变矫正
        if( _distCoeffs ) {
            // compensate tilt distortion--该部分系数用来弥补沙氏镜头畸变??
            // 如果不懂也没管,因为普通镜头中没有这些畸变系数
            cv::Vec3d vecUntilt = invMatTilt * cv::Vec3d(x, y, 1);
            double invProj = vecUntilt(2) ? 1./vecUntilt(2) : 1;
            x0 = x = invProj * vecUntilt(0);
            y0 = y = invProj * vecUntilt(1);
 
            double error = std::numeric_limits<double>::max();// error设定为系统最大值
            // compensate distortion iteratively
            // 迭代去除镜头畸变
            // 迭代公式    x′= (x−2p1 xy−p2 (r^2 + 2x^2))∕( 1 + k1*r^2 + k2*r^4 + k3*r^6)
            //             y′= (y−2p2 xy−p1 (r^2 + 2y^2))∕( 1 + k1*r^2 + k2*r^4 + k3*r^6)
 
            for( int j = 0; ; j++ )
            {
                if ((criteria.type & cv::TermCriteria::COUNT) && j >= criteria.maxCount)// 迭代最大次数为5次
                    break;
                if ((criteria.type & cv::TermCriteria::EPS) && error < criteria.epsilon)// 迭代误差阈值为0.01
                    break;
                double r2 = x*x + y*y;
                double icdist = (1 + ((k[7]*r2 + k[6])*r2 + k[5])*r2)/(1 + ((k[4]*r2 + k[1])*r2 + k[0])*r2);
                double deltaX = 2*k[2]*x*y + k[3]*(r2 + 2*x*x)+ k[8]*r2+k[9]*r2*r2;
                double deltaY = k[2]*(r2 + 2*y*y) + 2*k[3]*x*y+ k[10]*r2+k[11]*r2*r2;
                x = (x0 - deltaX)*icdist;
                y = (y0 - deltaY)*icdist;
 
                // 对当前迭代的坐标加畸变,计算误差error用于判断迭代条件
                if(criteria.type & cv::TermCriteria::EPS)
                {
                    double r4, r6, a1, a2, a3, cdist, icdist2;
                    double xd, yd, xd0, yd0;
                    cv::Vec3d vecTilt;
 
                    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);
                    xd0 = x*cdist*icdist2 + k[2]*a1 + k[3]*a2 + k[8]*r2+k[9]*r4;
                    yd0 = y*cdist*icdist2 + k[2]*a3 + k[3]*a1 + k[10]*r2+k[11]*r4;
 
                    vecTilt = matTilt*cv::Vec3d(xd0, yd0, 1);
                    invProj = vecTilt(2) ? 1./vecTilt(2) : 1;
                    xd = invProj * vecTilt(0);
                    yd = invProj * vecTilt(1);
 
                    double x_proj = xd*fx + cx;
                    double y_proj = yd*fy + cy;
 
                    error = sqrt( pow(x_proj - u, 2) + pow(y_proj - v, 2) );
                }
            }
        }
        // 将坐标从归一化图像坐标系转换到成像平面坐标系
        double xx = RR[0][0]*x + RR[0][1]*y + RR[0][2];
        double yy = RR[1][0]*x + RR[1][1]*y + RR[1][2];
        double ww = 1./(RR[2][0]*x + RR[2][1]*y + RR[2][2]);
        x = xx*ww;
        y = yy*ww;
 
        if( dtype == CV_32FC2 )
        {
            dstf[i*dstep].x = (float)x;
            dstf[i*dstep].y = (float)y;
        }
        else
        {
            dstd[i*dstep].x = x;
            dstd[i*dstep].y = y;
        }
    }
}

简化版ubdistortpoint

//for (size_t u = 0; u < ir_image_height; u++)
      //{
      //    for (size_t v = 0; v < ir_image_width; v++)
      //    {//(u,v) undistort
      //        float x = (u - cx) * fx_inv;
      //        float y = (v - cy) * fy_inv;
​
      //        float r2 = (x*x + y*y);
      //        float r = std::sqrt(r2);
      //        float r4 = r2 * r2;
      //        float x_distort = x*(1 + k1*r2 + k2 * r4) + 2 * p1*x*y + p2*(r2 + 2 * x*x);
      //        float y_distort = y*(1 + k1*r2 + k2 * r4) + p1*(r2 + 2 * y*y) + 2 * p2*x*y;
​
      //        float X = ir_depth_rx.at<float>(0, 0) * x_distort + ir_depth_rx.at<float>(0, 1)*y_distort +ir_depth_rx.at<float>(0, 2) * 1;
      //        float Y = ir_depth_rx.at<float>(1, 0) * x_distort + ir_depth_rx.at<float>(1, 1)*y_distort +ir_depth_rx.at<float>(1, 2) * 1;
      //        float W = ir_depth_rx.at<float>(2, 0) * x_distort + ir_depth_rx.at<float>(2, 1)*y_distort +ir_depth_rx.at<float>(2, 2) * 1;
      //        
      //        float x_camera = X / W;
      //        float y_camera = Y / W;
      //        
      //        float u_distort = fx*x_camera + cx;
      //        float v_distort = fy*y_camera + cy;
​
      //        calib_params->updated_ir_depth_forward_map_x->operator()(v, u) = u_distort;
      //        calib_params->updated_ir_depth_forward_map_y->operator()(v, u) = v_distort;
​
      //    }
      //}

 

 

GitHub 加速计划 / opencv31 / opencv
144
15
下载
OpenCV: 开源计算机视觉库
最近提交(Master分支:3 个月前 )
d9a139f9 Animated WebP Support #25608 related issues #24855 #22569 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake 2 天前
09030615 V4l default image size #25500 Added ability to set default image width and height for V4L capture. This is required for cameras that does not support 640x480 resolution because otherwise V4L capture cannot be opened and failed with "Pixel format of incoming image is unsupported by OpenCV" and then with "can't open camera by index" message. Because of the videoio architecture it is not possible to insert actions between CvCaptureCAM_V4L::CvCaptureCAM_V4L and CvCaptureCAM_V4L::open so the only way I found is to use environment variables to preselect the resolution. Related bug report is [#25499](https://github.com/opencv/opencv/issues/25499) Maybe (but not confirmed) this is also related to [#24551](https://github.com/opencv/opencv/issues/24551) This fix was made and verified in my local environment: capture board AVMATRIX VC42, Ubuntu 20, NVidia Jetson Orin. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [X] I agree to contribute to the project under Apache 2 License. - [X] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [X] The PR is proposed to the proper branch - [X] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake 2 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐