免费做的网站怎么设置域名,企业网站设计经典案例,建设银行开县支行 网站,整合营销相机的成像过程实质上是坐标系转换。首先空间中的点坐标由世界坐标系转换到相机坐标系#xff0c;然后将其投影到成像平面#xff08;图像物理坐标系#xff09;#xff0c;最后再将成像平面上的数据转换到图像像素坐标系。但是由于透镜制造精度及组装工艺的差别会引入畸变…相机的成像过程实质上是坐标系转换。首先空间中的点坐标由世界坐标系转换到相机坐标系然后将其投影到成像平面图像物理坐标系最后再将成像平面上的数据转换到图像像素坐标系。但是由于透镜制造精度及组装工艺的差别会引入畸变导致原始图像的失真。镜头的畸变分为镜像畸变和切向畸变两类。
一、坐标系简介
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基本矩阵 故整个相机坐标系到世界坐标系的变换公式为 其中RRxRyRzT[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 flagsCALIB_CB_ADAPTIVE_THRESHCALIB_CB_NORMALIZE_IMAGE );
// image传入拍摄的棋盘图Mat图像必须是8位的灰度或者彩色图像
// patternSize每个棋盘图上内角点的行列数一般情况下行列数不要相同便于后续标定程序识别标定板的方向
// corners用于存储检测到的内角点图像坐标位置一般用元素是Point2f的向量来表示vectorPoint2f 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的向量来表示vectorPoint2f/Point2d iamgePointsBuf//第三个参数winSize大小为搜索窗口的一半//第四个参数zeroZone死区的一半尺寸死区为不对搜索区的中央位置做求和运算的区域。它是用来避免自相关矩阵出现某些可能的奇异性。当值为-1-1时表示没有死区//第五个参数criteria定义求角点的迭代过程的终止条件可以为迭代次数和角点精度两者的组合专门用来获取棋盘图上内角点的精确位置降低相机标定偏差还可以使用cornerSubPix函数
bool find4QuadCornerSubpix(InputArray img, InputOutputArray corners, Size region_size);
// img输入的Mat矩阵最好是8位灰度图像检测效率更高
// corners初始的角点坐标向量同时作为亚像素坐标位置的输出vectorPoint2f 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 ); //第一个参数image8位灰度或者彩色图像
//第二个参数patternSize每张标定棋盘上内角点的行列数//第三个参数corners初始的角点坐标向量同时作为亚像素坐标位置的输出所以需要是浮点型数据一般用元素是Pointf2f/Point2d的向量来表示vectorPoint2f/Point2d iamgePointsBuf//第四个参数patternWasFound标志位用来指示定义的棋盘内角点是否被完整的探测到true表示别完整的探测到函数会用直线依次连接所有的内角点作为一个整体false表示有未被探测到的内角点这时候函数会以红色圆圈标记处检测到的内角点//以下是drawChessboardCorners函数中第四个参数patternWasFound设置为true和false时内角点的绘制效果//patternWasFoundture时依次连接各个内角点 patternWasFoundfalse时以红色圆圈标记处角点位置
3、完成相机标定 获取到棋盘标定图的内角点图像坐标之后就可以使用calibrateCamera函数进行标定计算相机内参和外参系数
calibrateCamera函数原型 objectPoints为世界坐标系中的点。在使用时应该输入一个三维点vector的vector即vectorvector objectPoints。第一层vector表示每一个视角第二层vector表示每一个点。
double calibrateCamera( InputArrayOfArrays objectPoints,InputArrayOfArrays imagePoints,Size imageSize,CV_OUT InputOutputArray cameraMatrix,CV_OUT InputOutputArray distCoeffs,OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs,int flags0, TermCriteria criteria TermCriteria(TermCriteria::COUNTTermCriteria::EPS, 30, DBL_EPSILON));
// objectPoints世界坐标系中的三维点三维坐标点的向量的向量vectorvectorPoint3f object_points
// imagePoints每一个内角点对应的图像坐标点vectorvectorPoint2f image_points_seq形式
// imageSize图像的像素尺寸大小列数cols行数rows宽度width高度height
// cameraMatrix相机的3*3内参矩阵Mat cameraMatrixMat(3,3,CV_32FC1,Scalar::all(0));
// distCoeffs1*5畸变矩阵Mat distCoeffsMat(1,5,CV_32FC1,Scalar::all(0))
// rvecs旋转向量输入一个Mat类型的vector即vectorMatrvecs;
// tvecs位移向量和rvecs一样应该为vectorMat 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 jacobiannoArray(), double aspectRatio0 );
//第一个参数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 borderModeBORDER_CONSTANT, const Scalar borderValueScalar());
//第一个参数src输入参数代表畸变的原始图像//第二个参数dst矫正后的输出图像跟输入图像具有相同的类型和大小//第三个参数map1和第四个参数map2X坐标和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 newCameraMatrixnoArray() ); 第一个参数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_count0; /* 图像数量 */ Size image_size; /* 图像的尺寸 */ Size board_size Size(4,6); /* 标定板上每行、列的角点数 */ vectorPoint2f image_points_buf; /* 缓存每幅图像上检测到的角点 */ vectorvectorPoint2f image_points_seq; /* 保存检测到的所有角点 */ string filename; int count -1 ;//用于存储角点个数。 while (getline(fin,filename)) { image_count; // 用于观察检验输出 coutimage_count image_countendl; /* 输出检验*/ cout--count count; Mat imageInputimread(filename); if (image_count 1) //读入第一张图片时获取图像宽高信息 { image_size.width imageInput.cols; image_size.height imageInput.rows; coutimage_size.width image_size.widthendl; coutimage_size.height image_size.heightendl; } /* 提取角点 */ if (0 findChessboardCorners(imageInput,board_size,image_points_buf)) { coutcan 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_EPSCV_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(); couttotal totalendl; int CornerNumboard_size.width*board_size.height; //每张图片上总的角点数 for (int ii0 ; iitotal ;ii) { if (0 ii%CornerNum)// 24 是每幅图片的角点个数。此判断语句是为了输出 图片号便于控制台观看 { int i -1; i ii/CornerNum; int ji1; cout-- 第 j 图片的数据 -- : endl; } if (0 ii%3) // 此判断语句格式化输出便于控制台查看 { coutendl; } 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); /* 实际测量得到的标定板上每个棋盘格的大小 */ vectorvectorPoint3f object_points; /* 保存标定板上角点的三维坐标 */ /*内外参数*/ Mat cameraMatrixMat(3,3,CV_32FC1,Scalar::all(0)); /* 摄像机内参数矩阵 */ vectorint point_counts; // 每幅图像中角点的数量 Mat distCoeffsMat(1,5,CV_32FC1,Scalar::all(0)); /* 摄像机的5个畸变系数k1,k2,p1,p2,k3 */ vectorMat tvecsMat; /* 每幅图像的旋转向量 */ vectorMat rvecsMat; /* 每幅图像的平移向量 */ /* 初始化标定板上角点的三维坐标 */ int i,j,t; for (t0;timage_count;t) { vectorPoint3f tempPointSet; for (i0;iboard_size.height;i) { for (j0;jboard_size.width;j) { Point3f realPoint; /* 假设标定板放在世界坐标系中z0的平面上 */ 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 (i0;iimage_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; /* 每幅图像的平均误差 */ vectorPoint2f image_points2; /* 保存重新计算得到的投影点 */ cout\t每幅图像的标定误差\n; fout每幅图像的标定误差\n; for (i0;iimage_count;i) { vectorPoint3f tempPointSetobject_points[i]; /* 通过得到的摄像机内外参数对空间的三维点进行重新投影计算得到新的投影点 */ projectPoints(tempPointSet,rvecsMat[i],tvecsMat[i],cameraMatrix,distCoeffs,image_points2); /* 计算新的投影点和旧的投影点之间的误差*/ vectorPoint2f 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.atVec2f(0,j) Vec2f(image_points2[j].x, image_points2[j].y); tempImagePointMat.atVec2f(0,j) Vec2f(tempImagePoint[j].x, tempImagePoint[j].y); } err norm(image_points2Mat, tempImagePointMat, NORM_L2); total_err err/ point_counts[i]; std::cout第i1幅图像的平均误差err像素endl; fout第i1幅图像的平均误差err像素endl; } std::cout总体平均误差total_err/image_count像素endl; fout总体平均误差total_err/image_count像素endlendl; std::cout评价完成endl; //保存定标结果 std::cout开始保存定标结果………………endl; Mat rotation_matrix Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */ fout相机内参数矩阵endl; foutcameraMatrixendlendl; fout畸变系数\n; foutdistCoeffsendlendlendl; for (int i0; iimage_count; i) { fout第i1幅图像的旋转向量endl; fouttvecsMat[i]endl; /* 将旋转向量转换为相对应的旋转矩阵 */ Rodrigues(tvecsMat[i],rotation_matrix); fout第i1幅图像的旋转矩阵endl; foutrotation_matrixendl; fout第i1幅图像的平移向量endl; foutrvecsMat[i]endlendl; } std::cout完成保存endl; foutendl; /************************************************************************ 显示定标结果 *************************************************************************/ 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::coutFrame #i1...endl; initUndistortRectifyMap(cameraMatrix,distCoeffs,R,cameraMatrix,image_size,CV_32FC1,mapx,mapy); StrStm.clear(); imageFileName.clear(); string filePathchess; StrStmi1; StrStmimageFileName; filePathimageFileName; 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(); StrStmi1; StrStmimageFileName; 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