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时,得到的图像是不带黑色边框的,相对于原图像,此时的图像损失了部分像素
alpha和newImageSize是两个互不干扰的参数,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()黑色
)
这里的map1和map2就是上面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=0时,损失最多的像素,没有黑色边框,矫正后的图像如图2所示。

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函数内部调用了initUndistortRectifyMap和remap,所以相当于你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角点。
然后对这些角点去畸变,注意,我们矫正后的点是不能直接打印在原图像上的,所以我们需要先对图像去畸变(这里的两种方法就是为了应对两种不同的图像矫正),然后将矫正后的点打印在矫正后的图像上。
注意: undistortPoints和undistort函数都有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.全景摄像机校准
