相机的内外参数标定和畸变矫正原理和代码

相机的成像过程实质上是坐标系转换。首先空间中的点坐标由世界坐标系转换到相机坐标系,然后将其投影到成像平面(图像物理坐标系),最后再将成像平面上的数据转换到图像像素坐标系。但是由于透镜制造精度及组装工艺的差别会引入畸变,导致原始图像的失真。镜头的畸变分为镜像畸变和切向畸变两类。

一、坐标系简介

1、像素坐标系

数字图像在计算机内部存储的形式类似于像素坐标系,如下图所示,图像中任意一点的坐标可以表示为:(u,v)。
在这里插入图片描述

2、图像坐标系

将像素坐标系的中心平移到图像中心(u0,v0),就得到了图像坐标系,此坐标系可以方便地反映出物体地尺寸信息。坐标系如下图所示:
在这里插入图片描述
设图像坐标系的中心Oi(u0,v0),相机中感光器件的尺寸为dx*dy,则两坐标系之间的关系可表示为:
在这里插入图片描述
将其写成矩阵形式:
在这里插入图片描述
将偏移项纳入乘积项,转化为齐次坐标的形式:
在这里插入图片描述

3、相机坐标系

相机坐标系中心Oc与图像坐标系中心Oi的连线就是Zc轴,Xc轴Yc轴分别与x轴和y轴平行。并且Oc和Oi的连线距离就是焦距f,一个物体从相机坐标系B(Xc,Yc,Zc)成像到图像坐标系P(x,y),过程如图所示:
在这里插入图片描述
根据距离关系有:
在这里插入图片描述
即:
在这里插入图片描述
转化为齐次形式:
在这里插入图片描述

4、世界坐标系

安装相机时,会分别绕相机坐标系的Xc、Yc、Zc轴做平移和旋转操作i,最后得到世界坐标系的。注意,此时Xw、Yw、Zw三个轴与其他坐标系并不平行。
在这里插入图片描述
首先考虑平移操作:
在这里插入图片描述其次考虑旋转操作,分别绕Xc、Yc、Zc轴旋转有旋转矩阵Rx、Ry、Rz:
(1)基本旋转矩阵:
在这里插入图片描述
(2)基本矩阵:
在这里插入图片描述
故整个相机坐标系到世界坐标系的变换公式为:
在这里插入图片描述
其中R=RxRyRz,T=[tx ty tz]

二、相机的内参和外参

通过几个坐标的转化,我们现在可以直接从像素坐标系变换到世界坐标系:
在这里插入图片描述

其中,u和v是像素坐标系中的坐标,Xw、Yw、Zw是世界坐标系中的坐标,剩余的两个矩阵分别为:
RT01矩阵:相机外参,是相机相对世界坐标系的旋转和平移变换。
4*4矩阵:相机内参,是相机固有的属性,含有焦距、像素尺寸等参数。

相机畸变矫正中,最重要的一步就是获取相机的内参。

三、图像的畸变和矫正

图像畸变矫正主要有两种:径向畸变和切向畸变
径向畸变:正中心位置的畸变最小,随着半径的增大,畸变增大。径向畸变可以分为枕形畸变和桶形畸变:
在这里插入图片描述

1、径向畸变

径向畸变是沿着透镜半径方向分布的畸变,产生的原因是光线在远离透镜中心的地方比考经中心的地方更加弯曲,这种畸变在普通廉价的镜头中表现更加明显,径向畸变主要包括桶形畸变和枕形畸变两种。
成像仪光轴中心的畸变为0,沿着镜头半径方向向边缘移动,畸变越来越严重。畸变的数学模型可以用主点(principle point)周围的泰勒级数展开式的前几项进行描述,通常使用前两项,即k1和k2,对于畸变很大的镜头,如鱼眼镜头,可以增加使用第三项k3来进行描述,
径向畸变的矫正公式如下(泰勒级数展开公式前3项):

在这里插入图片描述

其中(x,y)是理想坐标(畸变矫正后的新位置),x0和y0是畸变后的像素坐标点,即也是畸变点在成像仪上的原始位置,且sqr®=sqr(x)+sqr(y)
下图是距离光心不同距离上的点经过透镜径向畸变后点位的偏移示意图,距离光心越远,径向位移越大,表示畸变也越大,在光心附近,几乎没有偏移。
在这里插入图片描述

2、切向畸变

切向畸变是由于透镜本身与相机传感器平面(成像平面)或图像平面不平行而产生的,这种情况多是由于透镜被粘贴到镜头模组上的安装偏差导致。畸变模型可以用两个额外的参数p1和p2来描述:

切向畸变:在透镜与成像平面不平行时就会产生,类似于透视变换。
在这里插入图片描述
在这里插入图片描述
其中x0 y0是畸变后的坐标,x y是理想坐标。
两中畸变最后都归结到五个参数:k1 k2 k3 p1 p2,知道这五个参数后即可完成畸变矫正。

大体上畸变位移相对于左下——右上角的连线是对称的,说明该镜头在垂直于该方向上有一个旋转角度。
在这里插入图片描述

四、畸变矫正代码实现

畸变矫正在opencv中已经很成熟了,只需要调用封装号的API就可以了。接下来简要地说明一下流程:
1、完成标定版图像的采集(至少3张)
2、利用findChessboardCorners()函数检测标定板角点,并利用find4QuadCornerSubpix()函数完成亚像素级校准
3、利用calibrateCamera()函数进行相机标定,得到内参矩阵和畸变系数系数
实现步骤
(1)标定板图像采集:
标定图片需要使用标定板在不同位置、不同角度、不同姿态下拍摄,最少需要3张,以10~20张为宜。标定板需要是黑白相间的矩形构成的棋盘图,制作精度要求较高,如下图所示:
标准标定板
在这里插入图片描述

在这里插入图片描述
将标定板置于不同角度进行拍摄,此时采集的图像为畸变图像。
(2)角点检测和亚像素级校准
需要使用findChessboardCorners函数提取角点,这里的角点专指的是标定板上的内角点,这些角点与标定板的边缘不接触。

1、角点检测函数
bool findChessboardCorners(InputArray image, Size patternSize,OutputArray corners, int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE ); 
// image:传入拍摄的棋盘图Mat图像,必须是8位的灰度或者彩色图像
// patternSize:每个棋盘图上内角点的行列数,一般情况下,行列数不要相同,便于后续标定程序识别标定板的方向;
// corners:用于存储检测到的内角点图像坐标位置,一般用元素是Point2f的向量来表示:vector<Point2f> image_points_buf;
// flage:用于定义棋盘图上内角点查找的不同处理方式,有默认值。

为了提高标定精度,需要在初步提取的角点信息上进一步提取亚像素信息,降低相机标定偏差,常用的方法是cornerSubPix,另一个方法是使用find4QuadCornerSubpix函数,这个方法是专门用来获取棋盘图上内角点的精确位置的,或许在相机标定的这个特殊场合下它的检测精度会比cornerSubPix更高?

2、提取亚像素角点信息CV_EXPORTS_W void cornerSubPix( InputArray image, InputOutputArray corners,  Size winSize, Size zeroZone,  TermCriteria criteria );//第一个参数image,输入的Mat矩阵,最好是8位灰度图像,检测效率更高;//第二个参数corners,初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据,一般用元素是Pointf2f/Point2d的向量来表示:vector<Point2f/Point2d> iamgePointsBuf;//第三个参数winSize,大小为搜索窗口的一半;//第四个参数zeroZone,死区的一半尺寸,死区为不对搜索区的中央位置做求和运算的区域。它是用来避免自相关矩阵出现某些可能的奇异性。当值为(-1,-1)时表示没有死区;//第五个参数criteria,定义求角点的迭代过程的终止条件,可以为迭代次数和角点精度两者的组合;专门用来获取棋盘图上内角点的精确位置,降低相机标定偏差,还可以使用cornerSubPix函数
bool find4QuadCornerSubpix(InputArray img, InputOutputArray corners, Size region_size);
// img:输入的Mat矩阵,最好是8位灰度图像,检测效率更高
// corners:初始的角点坐标向量,同时作为亚像素坐标位置的输出vector<Point2f> iamgePointsBuf;
// region_size:角点搜索窗口的尺寸

角点检测结果可以可视化出来:
drawChessboardCorners函数用于绘制被成功标定的角点,函数原型:

    //! draws the checkerboard pattern (found or partly found) in the image  CV_EXPORTS_W void drawChessboardCorners( InputOutputArray image, Size patternSize,  InputArray corners, bool patternWasFound );  //第一个参数image,8位灰度或者彩色图像;
//第二个参数patternSize,每张标定棋盘上内角点的行列数;//第三个参数corners,初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据,一般用元素是Pointf2f/Point2d的向量来表示:vector<Point2f/Point2d> iamgePointsBuf;//第四个参数patternWasFound,标志位,用来指示定义的棋盘内角点是否被完整的探测到,true表示别完整的探测到,函数会用直线依次连接所有的内角点,作为一个整体,false表示有未被探测到的内角点,这时候函数会以(红色)圆圈标记处检测到的内角点;//以下是drawChessboardCorners函数中第四个参数patternWasFound设置为true和false时内角点的绘制效果://patternWasFound=ture时,依次连接各个内角点:

在这里插入图片描述
patternWasFound=false时,以(红色)圆圈标记处角点位置:
在这里插入图片描述

3、完成相机标定
获取到棋盘标定图的内角点图像坐标之后,就可以使用calibrateCamera函数进行标定,计算相机内参和外参系数,

calibrateCamera函数原型:
objectPoints为世界坐标系中的点。在使用时,应该输入一个三维点vector的vector,即vector<vector> objectPoints。第一层vector表示每一个视角,第二层vector表示每一个点。

double calibrateCamera( InputArrayOfArrays objectPoints,InputArrayOfArrays imagePoints,Size imageSize,CV_OUT InputOutputArray cameraMatrix,CV_OUT InputOutputArray distCoeffs,OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs,int flags=0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, DBL_EPSILON));  
// objectPoints:世界坐标系中的三维点,三维坐标点的向量的向量vector<vector<Point3f>> object_points
// imagePoints:每一个内角点对应的图像坐标点,vector<vector<Point2f>> image_points_seq形式
// imageSize:图像的像素尺寸大小(列数=cols,行数=rows)(宽度=width,高度=height)
// cameraMatrix:相机的3*3内参矩阵,Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0));
// distCoeffs:1*5畸变矩阵,Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0))
// rvecs:旋转向量,输入一个Mat类型的vector,即vector<Mat>rvecs;
// tvecs:位移向量,和rvecs一样,应该为vector<Mat> tvecs;
// flags:标定时所采用的算法
// criteria:最优迭代终止条件设定

如果使用OpenCV自带的棋盘格,可以直接传入交叉点(不包括边角)的实际坐标,以物理世界尺度(例如毫米)为单位。

写坐标时,要保证z轴为0(假设世界坐标系的原点在棋盘格左上角的第一个角点上),按照先x变化,后y变化,从小到大的顺序来写。如果网格尺寸为5厘米,写作:(0,0,0),(5,0,0), (10,0,0)…(0,5,0), (5,5,0), (10,5,0),…

如下图例子,x方向是8个交叉点,y方向3个交叉点。
在这里插入图片描述
在objectPoints中,我们经常只设定了每个交叉点的间隔,具体xyz指向哪里却没有给出。

在使用calibrateCamera标定前,一般使用findChessboardCorners()函数获得棋盘标定板的角点位置。这时候,需要使用drawChessboardCorners函数,把检测到的2D点在原图上显示出来:
在这里插入图片描述
与前述相同,内角点的标注先变化x,后变化y。先画出的角点用红色标注,并逐渐过渡到紫色(按红橙黄绿青蓝紫的顺序)。

回顾我们写objectPoints时的方式,可以推断出世界坐标的方向:图中x轴正方向向左,y轴正方向远离镜头,根据右手螺旋,z轴正方向向下。

总结来说,calibrateCamera函数给出的世界坐标方向,是由obejctPoints的设定顺序,以及findChessboardCorners的检测顺序共同决定的。

但是,findChessboardCorners先检测那个角点并不确定。如下图:
在这里插入图片描述

注:另一种常见用法是,先用calibrateCamera标定好摄像机内参,而后使用solvePnP函数只求解外参。solvePnP函数的参数意义和calibrateCamera类似。(暂存,还没用到过)

4、对标定结果进行评价
对标定结果进行评价的方法是通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到空间三维点在图像上新的投影点的坐标,计算投影坐标和亚像素角点坐标之间的偏差,偏差越小,标定结果越好。

对空间三维坐标点进行反向投影的函数是projectPoints,函数原型是:

    //! projects points from the model coordinate space to the image coordinates. Also computes derivatives of the image coordinates w.r.t the intrinsic and extrinsic camera parameters  CV_EXPORTS_W void projectPoints( InputArray objectPoints,  InputArray rvec, InputArray tvec,  InputArray cameraMatrix, InputArray distCoeffs,  OutputArray imagePoints,  OutputArray jacobian=noArray(),  double aspectRatio=0 ); 
//第一个参数objectPoints,为相机坐标系中的三维点坐标;//第二个参数rvec为旋转向量,每一张图像都有自己的选择向量;//第三个参数tvec为位移向量,每一张图像都有自己的平移向量;//第四个参数cameraMatrix为求得的相机的内参数矩阵;//第五个参数distCoeffs为相机的畸变矩阵;//第六个参数iamgePoints为每一个内角点对应的图像上的坐标点;//第七个参数jacobian是雅可比行列式;//第八个参数aspectRatio是跟相机传感器的感光单元有关的可选参数,如果设置为非0,则函数默认感光单元的dx/dy是固定的,会依此对雅可比矩阵进行调整;

以下是每一幅图像上24个内角点的平均误差统计数据
在这里插入图片描述
5、查看标定效果——利用标定结果对棋盘图进行矫正
利用求得的相机的内参和外参数据,可以对图像进行畸变的矫正,这里有两种方法可以达到矫正的目的,分别说明一下。

方法一:使用initUndistortRectifyMap和remap两个函数配合实现。

initUndistortRectifyMap用来计算畸变映射,remap把求得的映射应用到图像上。

initUndistortRectifyMap的函数原型:

    //! initializes maps for cv::remap() to correct lens distortion and optionally rectify the image  CV_EXPORTS_W void initUndistortRectifyMap( InputArray cameraMatrix, InputArray distCoeffs,  InputArray R, InputArray newCameraMatrix,  Size size, int m1type, OutputArray map1, OutputArray map2 ); 
//第一个参数cameraMatrix为之前求得的相机的内参矩阵;//第二个参数distCoeffs为之前求得的相机畸变矩阵;//第三个参数R,可选的输入,是第一和第二相机坐标之间的旋转矩阵;//第四个参数newCameraMatrix,输入的校正后的3X3摄像机矩阵;//第五个参数size,摄像机采集的无失真的图像尺寸;//第六个参数m1type,定义map1的数据类型,可以是CV_32FC1或者CV_16SC2;//第七个参数map1和第八个参数map2,输出的X/Y坐标重映射参数;

remap函数原型:

    //! warps the image using the precomputed maps. The maps are stored in either floating-point or integer fixed-point format  CV_EXPORTS_W void remap( InputArray src, OutputArray dst,  InputArray map1, InputArray map2,  int interpolation, int borderMode=BORDER_CONSTANT,  const Scalar& borderValue=Scalar()); 
//第一个参数src,输入参数,代表畸变的原始图像;//第二个参数dst,矫正后的输出图像,跟输入图像具有相同的类型和大小;//第三个参数map1和第四个参数map2,X坐标和Y坐标的映射;//第五个参数interpolation,定义图像的插值方式;、第六个参数borderMode,定义边界填充方式;

方法二:使用undistort函数实现
undistort函数原型:

    //! corrects lens distortion for the given camera matrix and distortion coefficients  CV_EXPORTS_W void undistort( InputArray src, OutputArray dst,  InputArray cameraMatrix,  InputArray distCoeffs,  InputArray newCameraMatrix=noArray() );  第一个参数src,输入参数,代表畸变的原始图像;第二个参数dst,矫正后的输出图像,跟输入图像具有相同的类型和大小;第三个参数cameraMatrix为之前求得的相机的内参矩阵;第四个参数distCoeffs为之前求得的相机畸变矩阵;第五个参数newCameraMatrix,默认跟cameraMatrix保持一致;

方法一相比方法二执行效率更高一些,推荐使用。

工程代码

#include "opencv2/core/core.hpp"  #include "opencv2/imgproc/imgproc.hpp"  #include "opencv2/calib3d/calib3d.hpp"  #include "opencv2/highgui/highgui.hpp"  #include <iostream>  #include <fstream>  using namespace cv;  using namespace std;  void main()   {  ifstream fin("calibdata.txt"); /* 标定所用图像文件的路径 */  ofstream fout("caliberation_result.txt");  /* 保存标定结果的文件 */    //读取每一幅图像,从中提取出角点,然后对角点进行亚像素精确化   cout<<"开始提取角点………………";  int image_count=0;  /* 图像数量 */  Size image_size;  /* 图像的尺寸 */  Size board_size = Size(4,6);    /* 标定板上每行、列的角点数 */  vector<Point2f> image_points_buf;  /* 缓存每幅图像上检测到的角点 */  vector<vector<Point2f>> image_points_seq; /* 保存检测到的所有角点 */  string filename;  int count= -1 ;//用于存储角点个数。  while (getline(fin,filename))  {  image_count++;        // 用于观察检验输出  cout<<"image_count = "<<image_count<<endl;          /* 输出检验*/  cout<<"-->count = "<<count;        Mat imageInput=imread(filename);  if (image_count == 1)  //读入第一张图片时获取图像宽高信息  {  image_size.width = imageInput.cols;  image_size.height =imageInput.rows;           cout<<"image_size.width = "<<image_size.width<<endl;  cout<<"image_size.height = "<<image_size.height<<endl;  }  /* 提取角点 */  if (0 == findChessboardCorners(imageInput,board_size,image_points_buf))  {             cout<<"can not find chessboard corners!\n"; //找不到角点  exit(1);  }   else   {  Mat view_gray;  cvtColor(imageInput,view_gray,CV_RGB2GRAY);  /* 亚像素精确化 */  find4QuadCornerSubpix(view_gray,image_points_buf,Size(5,5)); //对粗提取的角点进行精确化  //cornerSubPix(view_gray,image_points_buf,Size(5,5),Size(-1,-1),TermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER,30,0.1));  image_points_seq.push_back(image_points_buf);  //保存亚像素角点  /* 在图像上显示角点位置 */  drawChessboardCorners(view_gray,board_size,image_points_buf,false); //用于在图片中标记角点  imshow("Camera Calibration",view_gray);//显示图片  waitKey(500);//暂停0.5S         }  }  int total = image_points_seq.size();  cout<<"total = "<<total<<endl;  int CornerNum=board_size.width*board_size.height;  //每张图片上总的角点数  for (int ii=0 ; ii<total ;ii++)  {  if (0 == ii%CornerNum)// 24 是每幅图片的角点个数。此判断语句是为了输出 图片号,便于控制台观看   {     int i = -1;  i = ii/CornerNum;  int j=i+1;  cout<<"--> 第 "<<j <<"图片的数据 --> : "<<endl;  }  if (0 == ii%3)  // 此判断语句,格式化输出,便于控制台查看  {  cout<<endl;  }  else  {  cout.width(10);  }  //输出所有的角点  cout<<" -->"<<image_points_seq[ii][0].x;  cout<<" -->"<<image_points_seq[ii][0].y;  }     cout<<"角点提取完成!\n";  //以下是摄像机标定  cout<<"开始标定………………";  /*棋盘三维信息*/  Size square_size = Size(10,10);  /* 实际测量得到的标定板上每个棋盘格的大小 */  vector<vector<Point3f>> object_points; /* 保存标定板上角点的三维坐标 */  /*内外参数*/  Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0)); /* 摄像机内参数矩阵 */  vector<int> point_counts;  // 每幅图像中角点的数量  Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0)); /* 摄像机的5个畸变系数:k1,k2,p1,p2,k3 */  vector<Mat> tvecsMat;  /* 每幅图像的旋转向量 */  vector<Mat> rvecsMat; /* 每幅图像的平移向量 */  /* 初始化标定板上角点的三维坐标 */  int i,j,t;  for (t=0;t<image_count;t++)   {  vector<Point3f> tempPointSet;  for (i=0;i<board_size.height;i++)   {  for (j=0;j<board_size.width;j++)   {  Point3f realPoint;  /* 假设标定板放在世界坐标系中z=0的平面上 */  realPoint.x = i*square_size.width;  realPoint.y = j*square_size.height;  realPoint.z = 0;  tempPointSet.push_back(realPoint);  }  }  object_points.push_back(tempPointSet);  }  /* 初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板 */  for (i=0;i<image_count;i++)  {  point_counts.push_back(board_size.width*board_size.height);  }     /* 开始标定 */  calibrateCamera(object_points,image_points_seq,image_size,cameraMatrix,distCoeffs,rvecsMat,tvecsMat,0);  cout<<"标定完成!\n";  //对标定结果进行评价  cout<<"开始评价标定结果………………\n";  double total_err = 0.0; /* 所有图像的平均误差的总和 */  double err = 0.0; /* 每幅图像的平均误差 */  vector<Point2f> image_points2; /* 保存重新计算得到的投影点 */  cout<<"\t每幅图像的标定误差:\n";  fout<<"每幅图像的标定误差:\n";  for (i=0;i<image_count;i++)  {  vector<Point3f> tempPointSet=object_points[i];  /* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */  projectPoints(tempPointSet,rvecsMat[i],tvecsMat[i],cameraMatrix,distCoeffs,image_points2);  /* 计算新的投影点和旧的投影点之间的误差*/  vector<Point2f> tempImagePoint = image_points_seq[i];  Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);  Mat image_points2Mat = Mat(1,image_points2.size(), CV_32FC2);  for (int j = 0 ; j < tempImagePoint.size(); j++)  {  image_points2Mat.at<Vec2f>(0,j) = Vec2f(image_points2[j].x, image_points2[j].y);  tempImagePointMat.at<Vec2f>(0,j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);  }  err = norm(image_points2Mat, tempImagePointMat, NORM_L2);  total_err += err/=  point_counts[i];     std::cout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;     fout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;     }     std::cout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl;     fout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl<<endl;     std::cout<<"评价完成!"<<endl;    //保存定标结果      std::cout<<"开始保存定标结果………………"<<endl;         Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */  fout<<"相机内参数矩阵:"<<endl;     fout<<cameraMatrix<<endl<<endl;     fout<<"畸变系数:\n";     fout<<distCoeffs<<endl<<endl<<endl;     for (int i=0; i<image_count; i++)   {   fout<<"第"<<i+1<<"幅图像的旋转向量:"<<endl;     fout<<tvecsMat[i]<<endl;      /* 将旋转向量转换为相对应的旋转矩阵 */     Rodrigues(tvecsMat[i],rotation_matrix);     fout<<"第"<<i+1<<"幅图像的旋转矩阵:"<<endl;     fout<<rotation_matrix<<endl;     fout<<"第"<<i+1<<"幅图像的平移向量:"<<endl;     fout<<rvecsMat[i]<<endl<<endl;     }     std::cout<<"完成保存"<<endl;   fout<<endl;  /************************************************************************   显示定标结果   *************************************************************************/  Mat mapx = Mat(image_size,CV_32FC1);  Mat mapy = Mat(image_size,CV_32FC1);  Mat R = Mat::eye(3,3,CV_32F);  std::cout<<"保存矫正图像"<<endl;  string imageFileName;  std::stringstream StrStm;  for (int i = 0 ; i != image_count ; i++)  {  std::cout<<"Frame #"<<i+1<<"..."<<endl;  initUndistortRectifyMap(cameraMatrix,distCoeffs,R,cameraMatrix,image_size,CV_32FC1,mapx,mapy);        StrStm.clear();  imageFileName.clear();  string filePath="chess";  StrStm<<i+1;  StrStm>>imageFileName;  filePath+=imageFileName;  filePath+=".bmp";  Mat imageSource = imread(filePath);  Mat newimage = imageSource.clone();  //另一种不需要转换矩阵的方式  //undistort(imageSource,newimage,cameraMatrix,distCoeffs);  remap(imageSource,newimage,mapx, mapy, INTER_LINEAR);         StrStm.clear();  filePath.clear();  StrStm<<i+1;  StrStm>>imageFileName;  imageFileName += "_d.jpg";  imwrite(imageFileName,newimage);  }  std::cout<<"保存结束"<<endl;      return ;  }  

参考文献

透镜畸变及校正模型

镜头畸变矫正

另外,还可以使用matlab进行校正,具体请参见:
http://blog.csdn.net/Loser__Wang/article/details/51811347,

http://www.cnblogs.com/li-yao7758258/p/5929145.html

python实现:https://zhuanlan.zhihu.com/p/55

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/759803.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SQL】1251. 平均售价(IFNULL函数)

前述 知识点回顾&#xff1a;MySQL中IF()、IFNULL()、NULLIF()、ISNULL()函数的使用 题目描述 leetcode题目&#xff1a;1251. 平均售价 Code select P.product_id,ifnull(round(sum(units * price) / sum(units), 2), 0) as average_price from Prices P left join UnitsS…

无痕消除笔APP好用吗?3款超实用软件分享

无痕消除笔APP好用吗&#xff1f;在日常生活中&#xff0c;无痕消除笔APP的便捷性不言而喻。无论是想要去除照片中的小瑕疵&#xff0c;还是快速修正文案中的错别字&#xff0c;这款工具都能迅速而精准地满足需求。它不仅提升了我们处理图片和文本的效率&#xff0c;还让我们的…

一个Flash编程错误标志的探析

1、问题描述 客户项目中使用的 MCU 型号是 STM32G0B1, 他们反馈在代码中尝试擦除并编程 FLASH时, 发现 FLASH 的状态寄存器显示编程错误(如图 1 所示). 问题是当前代码还没有开始擦除和编程, 怎么就有了编程错误标志了呢 ? 如果不将此错误标志清除, 后续的编程操作无法继续.客…

vue2 table 页面 + 功能 展示

首页代码 <!-- 首页展示页面 弹框展示 --> <template><div style><el-button type"text" size"small" click"dailys()">测试跳转</el-button><!-- <div class"dingwei"><a href"#…

【办公类-16-07-07】“2023下学期 大班户外游戏2(有场地和无场地版,每天不同场地)”(python 排班表系列)

作品展示 背景需求&#xff1a; 2024年2月教务组发放的是“每周五天内容相同&#xff0c;两周10天内容相同”的户外游戏安排 【办公类-16-07-05】合并版“2023下学期 大班户外游戏&#xff08;有场地和无场地版&#xff0c;两周一次&#xff09;”&#xff08;python 排班表系…

css预处理器scss的使用如何全局引入

目录 scss 基本功能 1、嵌套 2、变量 $ 3、mixin 和 include 4、extend 5、import scss 在项目中的使用 1、存放 scss 文件 2、引入 variables 和 mixins 2-1、局部引入 2-2、全局引入 3、入口文件中引入其他文件 项目中使用 css 预处理器&#xff0c;可以提高 cs…

Uibot6.0 (RPA财务机器人师资培训第1天 )RPA+AI、RPA基础语法

训练网站&#xff1a;泓江科技 (lessonplan.cn)https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981(本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff09; 紧接着小北之前的几篇博客&#xff0c;友友们我们即将开展新课的学习~…

Vulnhub - Raven2

希望和各位大佬一起学习&#xff0c;如果文章内容有错请多多指正&#xff0c;谢谢&#xff01; 个人博客链接&#xff1a;CH4SER的个人BLOG – Welcome To Ch4sers Blog Raven2 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/raven-2,269/ 0x01 信息收集 Nmap扫描…

spring-boot-devtools debug SilentExitException

spring-boot-devtools debug SilentExitException&#xff1a;springboot热部署debug模式进入SilentExitException /** Copyright 2012-2019 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use …

软考网工学习笔记(6) 广域通信网

公共交换电话网&#xff08;pstn&#xff09; 在pstn是为了语音通信而建立的网络。从20世纪60你年代开始用于数据传输 电话网有三个部分组成&#xff1a; 本地回路 &#xff0c;干线 和 交换机 。 干线 和 交换机 一般采用数字传输和交换技术 &#xff0c;而 本地回路基本采…

Ubutun部署docker,并使用docker部署springboot项目,关闭软件可继续访问

工具为xftp和xshell。 我这里使用的Ubuntu的版本是20.04的&#xff0c;话不多说&#xff0c;我们来直接上代码。 首先我们最好使用管理员权限进行操作&#xff0c;预防操作时遇到权限问题。 部署docker 登入管理员 不登入管理员也没关系。 su根据提示输入密码&#xff0c;进…

2024蓝桥杯每日一题(并查集)

备战2024年蓝桥杯 -- 每日一题 Python大学A组 试题一&#xff1a;奶酪 试题二&#xff1a;合并集合 试题三&#xff1a;连通块中点的数量 试题四&#xff1a;网络分析 试题一&#xff1a;奶酪 【题目描述】 现有一块大奶酪&#xff0c;它的高度为 hℎ…

shell source脚本中如何读取另外一个脚本中的变量

目录 前言语法举例注意 前言 要在一个Shell脚本中读取另一个Shell脚本中的变量&#xff0c;可以使用source命令或者.命令。这些命令用于在当前Shell环境中运行指定的脚本&#xff0c;从而使得脚本中的变量在当前Shell中可用。 语法 #!/bin/bash # 读取另一个Shell脚本中的变…

金智维的务实主义,打响大模型落地“突围战”

今年以来&#xff0c;新质生产力成为全社会关注的焦点。新质生产力的特征之一&#xff0c;就是深化新技术应用&#xff0c;尤其是AI及大模型&#xff0c;要加速落地到实际业务场景中&#xff0c;为千行万业提质增效。 2024是大模型技术做深、价值做实的一年。3月20日&#xff0…

【C++】详解智能指针

目录 一、智能指针的作用二、内存泄露1、什么是内存泄露2、内存泄漏分类3、如何避免内存泄露 三、智能指针的使用及原理1、RAII2、智能指针的原理3、std::auto_ptr4、std::unique_ptr5、std::shared_ptr1、std::shared_ptr原理2、std::shared_ptr的线程安全问题4、std::shared_…

python爬虫学习第二天----类型转换

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

电脑如何录视频?进阶教程来了!

随着科技的飞速发展&#xff0c;视频录制已成为我们日常生活和工作中不可或缺的一部分。无论是进行在线教育、制作教学视频&#xff0c;还是记录游戏过程、直播分享&#xff0c;录屏都扮演着至关重要的角色。可是您知道电脑如何录视频吗&#xff1f;本文将介绍两种电脑录视频的…

稀碎从零算法笔记Day23-LeetCode:二叉树的最大深度

题型&#xff1a;链表、二叉树的遍历 链接&#xff1a;104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上…

vue2从基础到高级学习笔记

在实际的工作中,我常使用vue的用法去实现效果,但是你要是问我为什么这样写,它的原理是啥就答不上来了。对vue的认知一直停留在表面,写这篇文章主要是为了理清并弄透彻vue的原理。 学习目标 1 学会一些基本用法的原理 2 弄懂vue核心设计原理 3 掌握vue高级api的用法 一 vue…

环境安装篇 之 安装kubevela

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 环境安装 系列文章&#xff0c;介绍 oam规范标准实施项目 kubevela 的安装详细步骤kubevela 官方安装文档&#xff1a;https://kubevela.io/zh/docs/installation/kubernetes/ 1.CentOS 安装kubevela 1.1.前提…