当前位置: 首页 > news >正文

Opencv图像畸变校正---个人学习笔记(待完善版)

文章目录

  • 前言
  • 1.针孔相机模型图像去畸变
    • 1.1 cv::getOptimalNewCameraMatrix()
    • 1.2 cv::initUndistortRectifyMap()
    • 1.3 cv::remap()
    • 1.4 cv::undistort()
    • 1.5 cv::undistortPoints()
  • 2.示例代码
    • 2.1 使用getOptimalNewCameraMatrix + initUndistortRectifyMap + remap矫正图像
    • 2.2 使用undistort矫正图像
    • 2.3 使用undistortPoints矫正角点
      • 2.3.1 方法一
      • 2.3.2 方法二
    • 2.4 undistortPoints矫正BoundingBox
  • 3.鱼眼相机模型图像去畸变
    • cv::fisheye::initUndistortRectifyMap
    • cv::omnidir::initUndistortRectifyMap
      • RECTIFY_PERSPECTIV
      • RECTIFY_CYLINDRICAL
      • RECTIFY_LONGLATI
      • RECTIFY_STEREOGRAPHIC
  • 4.参考链接

前言

在这里插入图片描述
在这里插入图片描述

1.针孔相机模型图像去畸变

  针对畸变参数,k1,k2,k3,k4描述径向的畸变参数,p1,p2描述切向的畸变参数,一般k4不使用,默认为0。

1.1 cv::getOptimalNewCameraMatrix()

  函数参数说明:根据根据比例因子返回相应的新的相机内参矩阵

Mat cv::getOptimalNewCameraMatrix	
(	InputArray 	cameraMatrix,                  // 相机内参矩阵InputArray 	distCoeffs,                    // 相机畸变参数Size 	        imageSize,                     // 图像尺寸double 	        alpha,                         // 缩放比例Size 	        newImgSize = Size(),           // 校正后的图像尺寸Rect * 	        validPixROI = 0,               // 输出感兴趣区域设置bool 	        centerPrincipalPoint = false   // 可选标志
)	

  这里的"比例因子"就是函数的第四个参数(alpha),这个参数的范围是(0, 1)。调节alpha的值能够控制得到的新矩阵中的fx和fy的大小。
  当alpha=1的时候,原图像中的所有像素能够得到保留,因此这个时候得到的矫正后的图像是带黑框的。
  当alpha=0时,得到的图像是不带黑色边框的,相对于原图像,此时的图像损失了部分像素
  alphanewImageSize是两个互不干扰的参数,alpha只管是否对图像进行裁剪,而newImageSize只负责把图像进行缩放,这二者都会对返回的新的相机参数造成影响

1.2 cv::initUndistortRectifyMap()

  函数参数说明:用于计算原始图像和矫正图像之间的转换关系,将结果以映射的形式表达,映射关系存储在map1和map2中

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

  1.在单目相机例子中,newCameraMatrix可以用cv::getOptimalNewCameraMatrix**来计算,或者直接与cameraMatrix相等。
  2.在双目相机例子中,newCameraMatrix一般是用cv::stereoRectify计算而来的(这里不做讨论)。

1.3 cv::remap()

  函数功能:把原始图像中某位置的像素映射到矫正后的图像指定位置。

void cv::remap
(       InputArray    src,                        // 原始图像OutputArray   dst,                        // 矫正图像           InputArray    map1,                       // 第一个映射          InputArray    map2,                       // 第二个映射      int           interpolation,              // 插值方式int           borderMode=BORDER_CONSTANT, // 边界模式           const Scalar& borderValue=Scalar()        // 边界颜色,默认Scalar()黑色   
)

  这里的map1map2就是上面cv::initUndistortRectifyMap()计算出来的结果。

  OpenCV图像缩放resize各种插值方式的比较
  Opencv 插值方法 总结

方法适用场景优点缺点
最近邻插值实时应用、像素艺术、低计算资源场景计算速度最快,实现简单锯齿明显,图像质量低,不适合平滑区域
双线性插值通用缩放、实时视频处理速度较快,结果较平滑,平衡质量和性能在放大时可能模糊细节,不如高阶方法精确
像素区域插值图像缩小、避免锯齿缩小图像时质量高,减少混叠放大图像时效果差,不推荐用于放大
双三次插值高质量放大、摄影图像处理平滑度高,细节保留较好计算量大,速度较慢,可能过度平滑
Lanczos插值高分辨率图像、专业编辑最高质量,锐利边缘保留计算复杂,速度慢,资源消耗大

1.4 cv::undistort()

  函数功能:直接对图像进行畸变矫正。

void cv::undistort	
(	InputArray 	src,                        // 原始图像OutputArray 	dst,                        // 矫正图像InputArray 	cameraMatrix,               // 原相机内参矩阵InputArray 	distCoeffs,                 // 相机畸变参数InputArray 	newCameraMatrix = noArray() // 新相机内参矩阵
)	

  其内部调用了initUndistortRectifyMap和remap函数。

1.5 cv::undistortPoints()

  函数功能:只对图像中的某些点做畸变矫正。

void cv::undistortPoints	
(	InputArray 	src,            // 原始像素点矩阵  1xN or Nx1 (CV_32FC2 or CV_64FC2).OutputArray 	dst,            // 矫正像素点矩阵InputArray 	cameraMatrix,   // 原相机内参矩阵InputArray 	distCoeffs,     // 相机畸变参数InputArray 	R = noArray(),  // 可选的修正变换矩阵InputArray 	P = noArray()   // 新的相机矩阵
)

  源码分析

作者:小松鼠
链接:https://zhuanlan.zhihu.com/p/419775816
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。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共享内存指针}elsecvSetIdentity(&_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);// 定义中间变量--A相机内参数组,和matA共享内存;RR-矫正变换数组,和_RR共享内存// k-畸变系数数组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.01break;double r2 = x*x + y*y;// k=[k1,k2,p1,p2,k3]// icdist = 1.0∕( 1 + k1*r^2 + k2*r^4 + k3*r^6)double icdist = (1 + ((k[7]*r2 + k[6])*r2 + k[5])*r2)/(1 + ((k[4]*r2 + k[1])*r2 + k[0])*r2);// deltaX = 2*p1*xy + p2*(r^2+2*x*x)double deltaX = 2*k[2]*x*y + k[3]*(r2 + 2*x*x)+ k[8]*r2+k[9]*r2*r2;// deltaY = p1*(r^2+2*y*y) + 2*p2*x*ydouble 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;// k=[k1,k2,p1,p2,k3]// cdist = 1 + k1*r^2 + k2*r^4 +k3*r^6cdist = 1 + k[0]*r2 + k[1]*r4 + k[4]*r6;// icdist2 = 1.icdist2 = 1./(1 + k[5]*r2 + k[6]*r4 + k[7]*r6);// k[2]*a1=2*p1*x*y, k[3]*a2=p2*(r^2+2*x*x)xd0 = x*cdist*icdist2 + k[2]*a1 + k[3]*a2 + k[8]*r2+k[9]*r4;// k[2]*a3=p1*(r^2+2*y*y), k[3]*a1=2*p2*x*yyd0 = 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) );}}}// 将坐标从归一化图像坐标系转换到成像平面坐标系// RR=[[1 0 0 ],[0 1 0], [0 0 1]]double xx = RR[0][0]*x + RR[0][1]*y + RR[0][2];       // xdouble yy = RR[1][0]*x + RR[1][1]*y + RR[1][2];       // ydouble ww = 1./(RR[2][0]*x + RR[2][1]*y + RR[2][2]);  // 1.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;}}
}

2.示例代码

2.1 使用getOptimalNewCameraMatrix + initUndistortRectifyMap + remap矫正图像

#include <iostream>
#include <opencv2/opencv.hpp>using namespace std;int main()
{const cv::Mat K = ( cv::Mat_<double> ( 3,3 ) << 931.73, 0.0, 480.0, 0.0, 933.16, 302.0, 0.0, 0.0, 1.0 );const cv::Mat D = ( cv::Mat_<double> ( 5,1 ) << -1.7165e-1, 1.968259e-1, 0.0, 0.0, -3.639514e-1 );const string str = "/home/4_learn/ImageUndistort/data/";const int nImage = 5;const int ImgWidth = 640;const int ImgHeight = 480;cv::Mat map1, map2;cv::Size imageSize(ImgWidth, ImgHeight);const double alpha = 1;cv::Mat NewCameraMatrix = getOptimalNewCameraMatrix(K, D, imageSize, alpha, imageSize, 0);initUndistortRectifyMap(K, D, cv::Mat(), NewCameraMatrix, imageSize, CV_16SC2, map1, map2);for(int i=0; i<nImage; i++){string InputPath = str + to_string(i) + ".png";cv::Mat RawImage = cv::imread(InputPath);cv::imshow("RawImage", RawImage);cv::Mat UndistortImage;remap(RawImage, UndistortImage, map1, map2, cv::INTER_LINEAR);cv::imshow("UndistortImage", UndistortImage);string OutputPath = str + to_string(i) + "_un" + ".png";cv::imwrite(OutputPath, UndistortImage);cv::waitKey(0);}return 0;
}

  当alpha=1时,所有像素均保留,但存在黑色边框,矫正后的图像如图1所示。
在这里插入图片描述

alpha=1,所有像素均保留,带黑框

  当alpha=0时,损失最多的像素,没有黑色边框,矫正后的图像如图2所示。
在这里插入图片描述

alpha=0,损失最多的像素,无黑框

2.2 使用undistort矫正图像

#include <iostream>
#include <opencv2/opencv.hpp>using namespace std;int main()
{const cv::Mat K = ( cv::Mat_<double> ( 3,3 ) << 931.73, 0.0, 480.0, 0.0, 933.16, 302.0, 0.0, 0.0, 1.0 );const cv::Mat D = ( cv::Mat_<double> ( 5,1 ) << -1.7165e-1, 1.968259e-1, 0.0, 0.0, -3.639514e-1 );const string str = "/home/ImageUndistort/data/";const int nImage = 5;const int ImgWidth = 640;const int ImgHeight = 480;cv::Mat map1, map2;cv::Size imageSize(ImgWidth, ImgHeight);const double alpha = 1;cv::Mat NewCameraMatrix = getOptimalNewCameraMatrix(K, D, imageSize, alpha, imageSize, 0);for(int i=0; i<nImage; i++){string InputPath = str + to_string(i) + ".png";cv::Mat RawImage = cv::imread(InputPath);cv::imshow("RawImage", RawImage);cv::Mat UndistortImage;cv::undistort(RawImage, UndistortImage, K, D, K);
//        cv::undistort(RawImage, UndistortImage, K, D, NewCameraMatrix);cv::imshow("UndistortImage", UndistortImage);string OutputPath = str + to_string(i) + "_un2" + ".png";cv::imwrite(OutputPath, UndistortImage);cv::waitKey(0);}return 0;
}

  1.如果undistort函数的最后一个参数使用原相机内参,那么得到的结果相当于alpha=0的情况。
  2.如果undistort函数的最后一个参数使用getOptimalNewCameraMatrix计算出来的新矩阵,那么得到损失像素后的图像,相等于alpha=1的情况。

  1.如果像示例程序一样,有多个图片需要矫正,那么推荐使用2.1的方法,因为initUndistortRectifyMap函数只需要计算一次就行,不需要每次循环都计算,可以像程序中一样,将initUndistortRectifyMap放在循环外面。
  2.而使用2.2的方法,因为undistort函数内部调用了initUndistortRectifyMapremap,所以相当于你n张图像计算了n次initUndistortRectifyMap,这会大大降低效率,增加程序耗时。

2.3 使用undistortPoints矫正角点

2.3.1 方法一

#include <iostream>
#include <opencv2/opencv.hpp>using namespace std;const cv::Mat K = ( cv::Mat_<double> ( 3,3 ) << 931.73, 0.0, 480.0, 0.0, 933.16, 302.0, 0.0, 0.0, 1.0 );
const cv::Mat D = ( cv::Mat_<double> ( 5,1 ) << -1.7165e-1, 1.968259e-1, 0.0, 0.0, -3.639514e-1 );void UndistortKeyPoints(vector<cv::Point2f> &points);int main()
{const string str = "/home/ImageUndistort/data/";const int nImage = 5;const int MAX_CNT = 150;const int MIN_DIST = 30;for(int i=0; i<nImage; i++){string InputPath = str + to_string(i) + ".png";cv::Mat RawImage = cv::imread(InputPath);vector<cv::Point2f> pts;cv::Mat RawImage_Gray;cv::cvtColor(RawImage, RawImage_Gray, CV_RGB2GRAY);cv::goodFeaturesToTrack(RawImage_Gray, pts, MAX_CNT, 0.01, MIN_DIST);for(auto& pt:pts)circle(RawImage, pt, 2, cv::Scalar(255, 0, 0), 2);cv::imshow("pts", RawImage);UndistortKeyPoints(pts);cv::Mat UndistortImage;cv::undistort(RawImage, UndistortImage, K, D, K);for(auto& pt:pts)circle(UndistortImage, pt, 2, cv::Scalar(0, 0, 255), 2);cv::imshow("pts_un", UndistortImage);string OutputPath = str + to_string(i) + "_pts_un" + ".png";cv::imwrite(OutputPath, UndistortImage);cv::waitKey(0);}return 0;
}void UndistortKeyPoints(vector<cv::Point2f> &points)
{if(D.at<float>(0)==0.0)    // 图像矫正过return;// N为提取的特征点数量,将N个特征点保存在N*2的mat中uint N = points.size();cv::Mat mat(N,2,CV_32F);for(int i=0; i<N; i++){mat.at<float>(i,0)=points[i].x;mat.at<float>(i,1)=points[i].y;}// 调整mat的通道为2,矩阵的行列形状不变mat=mat.reshape(2);cv::undistortPoints(mat, mat, K, D, cv::Mat(), K);mat=mat.reshape(1);// 存储校正后的特征点for(int i=0; i<N; i++){cv::Point2f kp = points[i];kp.x=mat.at<float>(i,0);kp.y=mat.at<float>(i,1);points[i] = kp;}
}

2.3.2 方法二

#include <iostream>
#include <opencv2/opencv.hpp>using namespace std;const cv::Mat K = ( cv::Mat_<double> ( 3,3 ) << 931.73, 0.0, 480.0, 0.0, 933.16, 302.0, 0.0, 0.0, 1.0 );
const cv::Mat D = ( cv::Mat_<double> ( 5,1 ) << -1.7165e-1, 1.968259e-1, 0.0, 0.0, -3.639514e-1 );void UndistortKeyPoints(vector<cv::Point2f> &points, cv::Mat &newCamMatrix);
void UndistortKeyPoints(vector<cv::Point2f> &points, vector<cv::Point2f> &points_un);int main()
{const string str = "/home/ImageUndistort/data/";const int nImage = 5;const int ImgWidth = 960;const int ImgHeight = 640;const int MAX_CNT = 150;const int MIN_DIST = 30;cv::Mat map1, map2;cv::Size imageSize(ImgWidth, ImgHeight);//const double alpha = 1;//cv::Mat NewCameraMatrix = getOptimalNewCameraMatrix(K, D, imageSize, alpha, imageSize, 0);//initUndistortRectifyMap(K, D, cv::Mat(), NewCameraMatrix, imageSize, CV_16SC2, map1, map2);initUndistortRectifyMap(K, D, cv::Mat(), K, imageSize, CV_16SC2, map1, map2);for(int i=0; i<nImage; i++){string InputPath = str + to_string(i) + ".png";cv::Mat RawImage = cv::imread(InputPath);cv::Mat UndistortImage;cv::remap(RawImage, UndistortImage, map1, map2, cv::INTER_LINEAR);vector<cv::Point2f> pts;cv::Mat RawImage_Gray;cv::cvtColor(RawImage, RawImage_Gray, CV_RGB2GRAY);cv::goodFeaturesToTrack(RawImage_Gray, pts, MAX_CNT, 0.01, MIN_DIST);for(auto& pt:pts)circle(RawImage, pt, 2, cv::Scalar(255, 0, 0), 2);cv::imshow("pts", RawImage);// UndistortKeyPoints(pts, NewCameraMatrix);vector<cv::Point2f> un_pts;UndistortKeyPoints(pts, un_pts);pts = un_pts;for(auto& pt:pts)circle(UndistortImage, pt, 2, cv::Scalar(0, 0, 255), 2);cv::imshow("pts_un", UndistortImage);string OutputPath = str + to_string(i) + "_pts_un" + ".png";cv::imwrite(OutputPath, UndistortImage);cv::waitKey(0);}return 0;
}void UndistortKeyPoints(vector<cv::Point2f> &points, cv::Mat &newCamMatrix)
{if(D.at<float>(0)==0.0)    // 图像矫正过return;// N为提取的特征点数量,将N个特征点保存在N*2的mat中uint N = points.size();cv::Mat mat(N,2,CV_32F);for(int i=0; i<N; i++){mat.at<float>(i,0)=points[i].x;mat.at<float>(i,1)=points[i].y;}// 调整mat的通道为2,矩阵的行列形状不变mat=mat.reshape(2);cv::undistortPoints(mat, mat, K, D, cv::Mat(), newCamMatrix);mat=mat.reshape(1);// 存储校正后的特征点for(int i=0; i<N; i++){cv::Point2f kp = points[i];kp.x=mat.at<float>(i,0);kp.y=mat.at<float>(i,1);points[i] = kp;}
}void UndistortKeyPoints(vector<cv::Point2f> &points, vector<cv::Point2f> &points_un)
{if(D.at<float>(0)==0.0)    // 图像矫正过{points_un = points;return;}// N为提取的特征点数量,将N个特征点保存在N*2的mat中uint N = points.size();cv::Mat mat(N,2,CV_32F);for(int i=0; i<N; i++){mat.at<float>(i,0)=points[i].x;mat.at<float>(i,1)=points[i].y;}// 调整mat的通道为2,矩阵的行列形状不变mat=mat.reshape(2);cv::undistortPoints(mat, mat, K, D, cv::Mat(), K);mat=mat.reshape(1);// 存储校正后的特征点
//    points_un.reserve(N);points_un.resize(N);for(int i=0; i<N; i++){cv::Point2f kp = points[i];kp.x=mat.at<float>(i,0);kp.y=mat.at<float>(i,1);points_un[i] = kp;}
}

  先使用cv::goodFeaturesToTrack函数在图像中提取一些FAST角点。
  然后对这些角点去畸变,注意,我们矫正后的点是不能直接打印在原图像上的,所以我们需要先对图像去畸变(这里的两种方法就是为了应对两种不同的图像矫正),然后将矫正后的点打印在矫正后的图像上。

注意undistortPointsundistort函数都有newCameraMatrix参数,这两个函数要使用相同的newCameraMatrix参数,这样才能将去畸变的点和去畸变的图像对应起来,这里的newCameraMatrix使用原相机内参

2.4 undistortPoints矫正BoundingBox

#include <iostream>
#include <opencv2/opencv.hpp>using namespace std;const cv::Mat K = ( cv::Mat_<double> ( 3,3 ) << 931.73, 0.0, 480.0, 0.0, 933.16, 302.0, 0.0, 0.0, 1.0 );
const cv::Mat D = ( cv::Mat_<double> ( 5,1 ) << -1.7165e-1, 1.968259e-1, 0.0, 0.0, -3.639514e-1 );void UndistortBbox(cv::Rect &rect, cv::Mat &newCamMatrix);int main()
{const string str = "/home/jiang/4_learn/WeChatCode/ImageUndistort/data/";const int nImage = 5;const int ImgWidth = 960;const int ImgHeight = 640;cv::Mat map1, map2;cv::Size imageSize(ImgWidth, ImgHeight);const double alpha = 1;cv::Mat NewCameraMatrix = getOptimalNewCameraMatrix(K, D, imageSize, alpha, imageSize, 0);initUndistortRectifyMap(K, D, cv::Mat(), NewCameraMatrix, imageSize, CV_16SC2, map1, map2);cv::Rect Bbox{338, 141, 23, 57};for(int i=0; i<nImage; i++){string InputPath = str + to_string(i) + ".png";cv::Mat RawImage = cv::imread(InputPath);cv::Mat UndistortImage;cv::remap(RawImage, UndistortImage, map1, map2, cv::INTER_LINEAR);
//        cv::undistort(RawImage, UndistortImage, K, D, K);cv::rectangle(RawImage, Bbox, cv::Scalar(255, 0, 0), 2, 1);cv::imshow("RawImage", RawImage);string OutputPath1 = str + to_string(i) + "_Bbox" + ".png";cv::imwrite(OutputPath1, RawImage);UndistortBbox(Bbox, NewCameraMatrix);cv::rectangle(UndistortImage, Bbox, cv::Scalar(0, 0, 255), 2, 1);cv::imshow("UndistortImage", UndistortImage);string OutputPath2 = str + to_string(i) + "_Bbox_un" + ".png";cv::imwrite(OutputPath2, UndistortImage);cv::waitKey(0);}return 0;
}void UndistortBbox(cv::Rect &rect, cv::Mat &newCamMatrix)
{cv::Mat mat(4, 2,  CV_32F);mat.at<float>(0, 0) = rect.x;mat.at<float>(0, 1) = rect.y;mat.at<float>(1, 0) = rect.x + rect.width;mat.at<float>(1, 1) = rect.y;mat.at<float>(2, 0) = rect.x;mat.at<float>(2, 1) = rect.y + rect.height;mat.at<float>(3, 0) = rect.x + rect.width;mat.at<float>(3, 1) = rect.y + rect.height;mat = mat.reshape(2);  // 2通道,行列不变cv::undistortPoints(mat, mat, K, D, cv::Mat(), newCamMatrix);mat = mat.reshape(1);  // 单通道,行列不变double MaxX, MaxY;rect.x = min(mat.at<float>(0, 0), mat.at<float>(2, 0));MaxX   = max(mat.at<float>(1, 0), mat.at<float>(3, 0));rect.y = min(mat.at<float>(0, 1), mat.at<float>(1, 1));MaxY   = max(mat.at<float>(2, 1), mat.at<float>(3, 1));rect.width = MaxX - rect.x;rect.height = MaxY - rect.y;
}

3.鱼眼相机模型图像去畸变

  1.在此采用的是cv::fisheye描述方法,畸变参数只有四个:k1,k2,k3,k4,这是opencv专门为鱼眼相机创建的模型,也对应着kalibr中的pinhole-equidistant模型。
  2.鱼眼相机模型去畸变后图像左右上下边缘可能存在黒框,需要进一步裁切处理。

cv::fisheye::initUndistortRectifyMap

  适用于鱼眼相机的畸变校正和图像矫正。
  它的参数包括输入图像的大小、相机内参矩阵、畸变系数、旋转矩阵和投影矩阵等。函数的返回值是两个映射矩阵,可以用于后续的图像矫正操作。

cv::omnidir::initUndistortRectifyMap

  全景图像有非常大的畸变,所以它与人类眼球不兼容。如果已知相机参数,可以应用校正以获得更好的视野。下面是一个 360 度水平视野的全景图像示例。
在这里插入图片描述
  校正后,生成类似透视图的视图。下面是一个在这个模块中运行图像校正的示例

cv::omnidir::undistortImage(distorted, undistorted, K, D, xi, int flags, Knew, new_size)
cv::omnidir::undistortImage
(
distorted,   //原始图像
undistorted, //校正的透视图图像
K, D, xi,   //K、D、xi是相机参数
int flags,  //校正类型
Knew,      //校正的图像的相机矩阵
new_size,  //校正的图像的图像大小
)

  opencv的omnidir模型提供4种去畸变效果,由上述函数中的flags参数进行设置,支持的类型分别是:

RECTIFY_PERSPECTIV

  矫正到投影(针孔)模型,会损失视角,呈现的视角大小由knew决定。
在这里插入图片描述

:透视校正图像仅保留了一小部分视野,并且不好看。

RECTIFY_CYLINDRICAL

  校正为保留所有视图的柱状图像。
  矫正到柱面投影,保留所有视角。
在这里插入图片描述

注:转到柱面效果如上图, 可以发现图像底部中间的区域不太自然。

RECTIFY_LONGLATI

  校正为可能丢失一些视图的立体图形图像。
在这里插入图片描述

注1:矫正到立体图像,可能会损失一点视角,图像中物体变形未改善,矫正效果如下。
注2:立体矫正法底部中间的畸变比圆柱矫正法小,但其他地方的畸变较大,且不能保留全部视野。

RECTIFY_STEREOGRAPHIC

  校正为类似于地球世界地图的经纬度地图。此校正可用于立体声重建,但可能对视图不友好。
在这里插入图片描述

: 对于畸变非常大的图像,经度-纬度校正的效果并不好,但可以在一条线上进行外极约束,这样就可以在全向图像中进行立体匹配。

4.参考链接

  1.计算机视觉:Opencv图像去畸变
  2.关于OpenCV中的去畸变
  3.全景摄像机校准

http://www.dtcms.com/a/572843.html

相关文章:

  • Java设计模式精讲---02抽象工厂模式
  • 【ChatGPT系列】ChatGPT Atlas:未来浏览器的智慧体验
  • AWS实现S3配置私钥以及上传
  • C++ opencv拟合直线
  • TDengine IDMP 1.0.5.0 及近期更新总览:模型计算、可视化、异常检测全面升级
  • Melotopia For HarmonyOS 的 HAP 签名安装包在 DevEco Studio 中安装的详细步骤
  • 网页遇到ddos网络攻击,应该如何应对
  • LlamaFactory微调效果与vllm部署效果不一致
  • 国外营销网站求一个旅游网站的代码
  • Opencv(八) :ROI 切割
  • 计算机网络自顶向下方法34——网络层 排队论 缓存大小调节 分组调度 网络中立性
  • 网站怎么做适配南通网站建设培训
  • 本地缓存与分布式缓存:深入解析与多级缓存架构实践
  • 【C语言实战(73)】深入C语言网络编程:UDP与TCP的实战对决
  • 健身房预约系统SSM+Mybatis(五、预约展示)
  • 记录对某985证书站挖掘
  • 解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
  • 探索LoSA:动态低秩稀疏自适应——大模型高效微调的新突破
  • wordpress表格插件网站建设关键词优化价格
  • Gitlab+Jenkins+Docker+Harbor+K8s+Rancher集群搭建CICD平台
  • Linux服务器安装jdk和maven详解
  • 回归、预测、分类三者关系
  • 微信平台微网站开发乙肝能治好吗
  • Skill 与 Workflow:让自动化更“聪明”的系统架构
  • AI+Python近红外光谱分析机器学习与深度学习实战,覆盖提示词撰写、数据预处理、回归/神经网络/集成学习/迁移学习/可解释性可视化等
  • ESP8266植入程序实现MQTT控制
  • 突击宝典:pytorch面试高频考点精析
  • 建设公司网站的背景意义上海网站开发设计培训
  • 电子商务网站的建设和流程就业培训机构有哪些
  • ICML 2025|基于大语言模型的多比特文本水印方法