使用opencv实现单目标定
相机标定的目的:获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的选择和平移矩阵),内参和外参系数可以对之后相机拍摄的图像就进行矫正,得到畸变相对很小的图像。
相机标定的输入:标定图像上所有内角点的图像坐标,标定板图像上所有内角点的空间三维坐标(一般情况下假定图像位于Z=0平面上)。
相机标定的输出:摄像机的内参、外参系数。
这三个基础的问题就决定了使用Opencv实现张正友法标定相机的标定流程、标定结果评价以及使用标定结果矫正原始图像的完整流程:
-
1. 准备标定图片
-
2. 对每一张标定图片,提取角点信息
-
3.对每一张标定图片,进一步提取亚像素角点信息
-
4. 在棋盘标定图上绘制找到的内角点(非必须,仅为了显示)
-
5. 相机标定
-
6. 对标定结果进行评价
-
7. 查看标定效果——利用标定结果对棋盘图进行矫正
准备标定图片
标定图片需要使用标定板在不同位置、不同角度、不同姿态下拍摄,最少需要3张,以10~20张为宜。标定板需要是黑白相间的矩形构成的棋盘图,制作精度要求较高。
这里我们使用OpenCV提供的sample程序中的标定图片,图片位于opencv(C++版本)的安装路径:opencv\sources\samples\data下:
我们先创建一个C++控制台项目,并把标定图片按如下格式存放:
sample文件夹下有两个文件夹left和right,分别对应左摄像头和右摄像头拍摄到的标定板图片:
filename.txt存放标定图片的路径,内容如下:
关于OpenCV提供的用于相机标定的API函数可以查看博客双目视觉标定程序讲解,单目标定的代码如下:
/************************************************************************************
*
* Description:相机标定,张氏标定法 单目标定
* Author :JNU
* Data :2018.7.22
*
************************************************************************************/
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <fstream>
#include <vector>
using namespace cv;
using namespace std;
void main(char *args)
{
//保存文件名称
std::vector<std::string> filenames;
//需要更改的参数
//左相机标定,指定左相机图片路径,以及标定结果保存文件
string infilename = "sample/left/filename.txt"; //如果是右相机把left改为right
string outfilename = "sample/left/caliberation_result.txt";
//标定所用图片文件的路径,每一行保存一个标定图片的路径 ifstream 是从硬盘读到内存
ifstream fin(infilename);
//保存标定的结果 ofstream 是从内存写到硬盘
ofstream fout(outfilename);
/*
1.读取毎一幅图像,从中提取出角点,然后对角点进行亚像素精确化、获取每个角点在像素坐标系中的坐标
像素坐标系的原点位于图像的左上角
*/
std::cout << "开始提取角点......" << std::endl;;
//图像数量
int imageCount = 0;
//图像尺寸
cv::Size imageSize;
//标定板上每行每列的角点数
cv::Size boardSize = cv::Size(9, 6);
//缓存每幅图像上检测到的角点
std::vector<Point2f> imagePointsBuf;
//保存检测到的所有角点
std::vector<std::vector<Point2f>> imagePointsSeq;
char filename[100];
if (fin.is_open())
{
//读取完毕?
while (!fin.eof())
{
//一次读取一行
fin.getline(filename, sizeof(filename) / sizeof(char));
//保存文件名
filenames.push_back(filename);
//读取图片
Mat imageInput = cv::imread(filename);
//读入第一张图片时获取图宽高信息
if (imageCount == 0)
{
imageSize.width = imageInput.cols;
imageSize.height = imageInput.rows;
std::cout << "imageSize.width = " << imageSize.width << std::endl;
std::cout << "imageSize.height = " << imageSize.height << std::endl;
}
std::cout << "imageCount = " << imageCount << std::endl;
imageCount++;
//提取每一张图片的角点
if (cv::findChessboardCorners(imageInput, boardSize, imagePointsBuf) == 0)
{
//找不到角点
std::cout << "Can not find chessboard corners!" << std::endl;
exit(1);
}
else
{
Mat viewGray;
//转换为灰度图片
cv::cvtColor(imageInput, viewGray, cv::COLOR_BGR2GRAY);
//亚像素精确化 对粗提取的角点进行精确化
cv::find4QuadCornerSubpix(viewGray, imagePointsBuf, cv::Size(5, 5));
//保存亚像素点
imagePointsSeq.push_back(imagePointsBuf);
//在图像上显示角点位置
cv::drawChessboardCorners(viewGray, boardSize, imagePointsBuf, true);
//显示图片
//cv::imshow("Camera Calibration", viewGray);
cv::imwrite("test.jpg", viewGray);
//等待0.5s
//waitKey(500);
}
}
//计算每张图片上的角点数 54
int cornerNum = boardSize.width * boardSize.height;
//角点总数
int total = imagePointsSeq.size()*cornerNum;
std::cout << "total = " << total << std::endl;
for (int i = 0; i < total; i++)
{
int num = i / cornerNum;
int p = i%cornerNum;
//cornerNum是每幅图片的角点个数,此判断语句是为了输出,便于调试
if (p == 0)
{
std::cout << "\n第 " << num+1 << "张图片的数据 -->: " << std::endl;
}
//输出所有的角点
std::cout<<p+1<<":("<< imagePointsSeq[num][p].x;
std::cout << imagePointsSeq[num][p].y<<")\t";
if ((p+1) % 3 == 0)
{
std::cout << std::endl;
}
}
std::cout << "角点提取完成!" << std::endl;
/*
2.摄像机标定 世界坐标系原点位于标定板左上角(第一个方格的左上角)
*/
std::cout << "开始标定" << std::endl;
//棋盘三维信息,设置棋盘在世界坐标系的坐标
//实际测量得到标定板上每个棋盘格的大小
cv::Size squareSize = cv::Size(26, 26);
//毎幅图片角点数量
std::vector<int> pointCounts;
//保存标定板上角点的三维坐标
std::vector<std::vector<cv::Point3f>> objectPoints;
//摄像机内参数矩阵 M=[fx γ u0,0 fy v0,0 0 1]
cv::Mat cameraMatrix = cv::Mat(3, 3, CV_64F, Scalar::all(0));
//摄像机的5个畸变系数k1,k2,p1,p2,k3
cv::Mat distCoeffs = cv::Mat(1, 5, CV_64F, Scalar::all(0));
//每幅图片的旋转向量
std::vector<cv::Mat> tvecsMat;
//每幅图片的平移向量
std::vector<cv::Mat> rvecsMat;
//初始化标定板上角点的三维坐标
int i, j, t;
for (t = 0; t < imageCount; t++)
{
std::vector<cv::Point3f> tempPointSet;
//行数
for (i = 0; i < boardSize.height; i++)
{
//列数
for (j = 0; j < boardSize.width; j++)
{
cv::Point3f realPoint;
//假设标定板放在世界坐标系中z=0的平面上。
realPoint.x = i*squareSize.width;
realPoint.y = j*squareSize.height;
realPoint.z = 0;
tempPointSet.push_back(realPoint);
}
}
objectPoints.push_back(tempPointSet);
}
//初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板
for (i = 0; i < imageCount; i++)
{
pointCounts.push_back(boardSize.width*boardSize.height);
}
//开始标定
cv::calibrateCamera(objectPoints, imagePointsSeq, imageSize, cameraMatrix, distCoeffs, rvecsMat, tvecsMat);
std::cout << "标定完成" << std::endl;
//对标定结果进行评价
std::cout << "开始评价标定结果......" << std::endl;
//所有图像的平均误差的总和
double totalErr = 0.0;
//每幅图像的平均误差
double err = 0.0;
//保存重新计算得到的投影点
std::vector<cv::Point2f> imagePoints2;
std::cout << "每幅图像的标定误差:" << std::endl;
fout << "每幅图像的标定误差:" << std::endl;
for (i = 0; i < imageCount; i++)
{
std::vector<cv::Point3f> tempPointSet = objectPoints[i];
//通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点imagePoints2(在像素坐标系下的点坐标)
cv::projectPoints(tempPointSet, rvecsMat[i], tvecsMat[i], cameraMatrix, distCoeffs, imagePoints2);
//计算新的投影点和旧的投影点之间的误差
std::vector<cv::Point2f> tempImagePoint = imagePointsSeq[i];
cv::Mat tempImagePointMat = cv::Mat(1, tempImagePoint.size(), CV_32FC2);
cv::Mat imagePoints2Mat = cv::Mat(1, imagePoints2.size(), CV_32FC2);
for (int j = 0; j < tempImagePoint.size(); j++)
{
imagePoints2Mat.at<cv::Vec2f>(0, j) = cv::Vec2f(imagePoints2[j].x, imagePoints2[j].y);
tempImagePointMat.at<cv::Vec2f>(0, j) = cv::Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
}
//Calculates an absolute difference norm or a relative difference norm.
err = cv::norm(imagePoints2Mat, tempImagePointMat, NORM_L2);
totalErr += err /= pointCounts[i];
std::cout << " 第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
fout<< "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
}
//每张图像的平均总误差
std::cout << " 总体平均误差:" << totalErr / imageCount << "像素" << std::endl;
fout << "总体平均误差:" << totalErr / imageCount << "像素" << std::endl;
std::cout << "评价完成!" << std::endl;
//保存标定结果
std::cout << "开始保存标定结果....." << std::endl;
//保存每张图像的旋转矩阵
cv::Mat rotationMatrix = cv::Mat(3, 3, CV_32FC1, Scalar::all(0));
fout << "相机内参数矩阵:" << std::endl;
fout << cameraMatrix << std::endl << std::endl;
fout << "畸变系数:" << std::endl;
fout << distCoeffs << std::endl << std::endl;
for (int i = 0; i < imageCount; i++)
{
fout << "第" << i + 1 << "幅图像的旋转向量:" << std::endl;
fout << tvecsMat[i] << std::endl;
//将旋转向量转换为相对应的旋转矩阵
cv::Rodrigues(tvecsMat[i], rotationMatrix);
fout << "第" << i + 1 << "幅图像的旋转矩阵:" << std::endl;
fout << rotationMatrix << std::endl;
fout << "第" << i + 1 << "幅图像的平移向量:" << std::endl;
fout << rvecsMat[i] << std::endl;
}
std::cout << "保存完成" << std::endl;
/************************************************************************
显示定标结果
*************************************************************************/
cv::Mat mapx = cv::Mat(imageSize, CV_32FC1);
cv::Mat mapy = cv::Mat(imageSize, CV_32FC1);
cv::Mat R = cv::Mat::eye(3, 3, CV_32F);
std::cout << "显示矫正图像" << endl;
for (int i = 0; i != imageCount; i++)
{
std::cout << "Frame #" << i + 1 << "..." << endl;
//计算图片畸变矫正的映射矩阵mapx、mapy(不进行立体校正、立体校正需要使用双摄)
initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cameraMatrix, imageSize, CV_32FC1, mapx, mapy);
//读取一张图片
Mat imageSource = imread(filenames[i]);
Mat newimage = imageSource.clone();
//另一种不需要转换矩阵的方式
//undistort(imageSource,newimage,cameraMatrix,distCoeffs);
//进行校正
remap(imageSource, newimage, mapx, mapy, INTER_LINEAR);
imshow("原始图像", imageSource);
imshow("矫正后图像", newimage);
waitKey();
}
//释放资源
fin.close();
fout.close();
system("pause");
}
}
上面有两个函数需要单独介绍一下:
CV_EXPORTS_W void initUndistortRectifyMap( InputArray cameraMatrix, InputArray distCoeffs,
InputArray R, InputArray newCameraMatrix,
Size size, int m1type, OutputArray map1, OutputArray map2 );
函数功能:该函数功能是计算畸变矫正和摄像机立体校正的映射变换矩阵。为了重映射,将结果以映射的形式表达。无畸变的图像看起来和原始的图像一样,就像这个图像是用内参为newCameraMatrix
的且无畸变的相机采集得到的。该函数实际上为反向映射算法构建映射,供反向映射使用。也就是,对于已经修正畸变的图像中的每个像素$(u,v)$,该函数计算原来图像(从相机中获得的原始图像)中对应的坐标系。
参数说明:
-
cameraMatrix:输入相机内参矩阵
-
distCoeffs:输入参数,相机的畸变系数
有4,5,8,12或14个元素。如果这个向量是空的,就认为是零畸变系数。
-
RR:可选的立体修正变换矩阵,是个3×33×3的矩阵。
在单目相机例子中,$R$就设置为单位矩阵cv::Mat R = cv::Mat::eye(3, 3, CV_32F),表示不进行立体校正。
在双目相机例子中,newCameraMatrix一般是用cv::stereoRectify()计算而来的,设置为R1R1或R2R2(左右相机平面行对准的校正旋转矩阵)。此外,根据RR,新的相机在坐标空间中的取向是不同的。例如,它帮助配准双目相机的两个相机方向,从而使得两个图像的极线是水平的,且yy坐标相同(在双目相机的两个相机谁水平放置的情况下)。
-
newCameraMatrix:新的相机内参矩阵
在单目相机例子中,newCameraMatrix一般和cameraMatrix相等,或者可以用cv::getOptimalNewCameraMatrix()来计算,获得一个更好的有尺度的控制结果。
在双目相机例子中,newCameraMatrix一般是用cv::stereoRectify()计算而来的,设置为P1或P2(左右相机把空间3D点的坐标转换到图像的2D点的坐标的投影矩阵)。
-
size:未畸变的图像尺寸。
-
m1type:第一个输出的映射的类型,可以为 CV_32FC1, CV_32FC2或CV_16SC2,参见cv::convertMaps。
-
map1:第一个输出映射。
-
map2:第二个输出映射。
void remap(InputArray src, OutputArray dst, InputArray map1, InputArray
map2, int interpolation,int borderMode=BORDER_CONSTANT, const Scalar&
borderValue=Scalar())
函数功能:重映射:就是把一幅图像中某位置的像素放置到另一个图片指定位置的过程。
参数说明:
-
src:输入图像,即原图像,需要单通道8位或者浮点类型的图像
-
dst:输出图像,即目标图像,需和原图形一样的尺寸和类型
-
map1:它有两种可能表示的对象:(1)表示点(x,y)的第一个映射;(2)表示CV_16SC2,CV_32FC1等
-
map2:它有两种可能表示的对象:(1)若map1表示点(x,y)时,这个参数不代表任何值;(2)表示CV_16UC1,CV_32FC1类型的Y值
-
interpolation:插值方式,有四中插值方式:
(1)INTER_NEAREST——最近邻插值
(2)INTER_LINEAR——双线性插值(默认)
(3)INTER_CUBIC——双三样条插值(默认)
(4)INTER_LANCZOS4——lanczos插值(默认)
-
borderMode:边界模式,默认BORDER_CONSTANT
-
borderValue:边界颜色,默认Scalar()黑色
程序运行后,把相机内部参数和外部参数保存在caliberation_result.txt文件中,内容如下
每幅图像的标定误差:
第1幅图像的平均误差:0.0644823像素
第2幅图像的平均误差:0.0769712像素
第3幅图像的平均误差:0.057877像素
第4幅图像的平均误差:0.0596713像素
第5幅图像的平均误差:0.0625956像素
第6幅图像的平均误差:0.0658863像素
第7幅图像的平均误差:0.0568134像素
第8幅图像的平均误差:0.0643699像素
第9幅图像的平均误差:0.058048像素
第10幅图像的平均误差:0.0565483像素
第11幅图像的平均误差:0.0590138像素
第12幅图像的平均误差:0.0569968像素
第13幅图像的平均误差:0.0698826像素
总体平均误差:0.0622428像素
相机内参数矩阵:
[530.5277314196954, 0, 338.8371277433631;
0, 530.5883296858968, 231.5390118666163;
0, 0, 1]
畸变系数:
[-0.2581406917163123, -0.11124480187392, 0.0004630258905514519, -0.0009475605555950018, 0.413646790569884]
第1幅图像的旋转向量:
[-75.22204622827574;
-109.7328226714255;
412.7511174854986]
第1幅图像的旋转矩阵:
[0.9927105083879407, -0.1161407096490343, -0.03220531164846807;
0.1168004495051158, 0.9929655913965856, 0.01941621224214358;
0.02972375365863362, -0.02303627280285992, 0.999292664139887]
第1幅图像的平移向量:
[-1.985720132175791;
-2.010141521348128;
0.1175016759367312]
第2幅图像的旋转向量:
[-57.88571684656549;
88.73102475029921;
365.4767680110305]
第2幅图像的旋转矩阵:
[-0.880518198944593, 0.2965025784551226, -0.36982958548071;
-0.4330747951156081, -0.8203927789645991, 0.3733656519530371;
-0.192701642865192, 0.4889191233652108, 0.8507785655767596]
第2幅图像的平移向量:
[-2.431974050326802;
-0.2015324617416875;
0.2103186188188722]
第3幅图像的旋转向量:
[-38.96229403649615;
-101.619482335263;
328.7991741655258]
第3幅图像的旋转矩阵:
[0.7229826652152683, -0.6501194230369263, -0.2337537199455046;
0.6686409526220074, 0.7435854196067706, -1.49985835111166e-05;
0.1738256088007802, -0.1562864662674188, 0.9722958388199968]
第3幅图像的平移向量:
[1.726707502757928;
2.49410066154742;
-0.5169212442744683]
第4幅图像的旋转向量:
[-99.94408740929534;
-67.11904896100746;
341.7035262057663]
第4幅图像的旋转矩阵:
[-0.4166240767662854, 0.8762113538151707, -0.2422355095852507;
-0.7194830230098562, -0.4806860756468779, -0.5012834290895748;
-0.5556694685325433, -0.03456240912595265, 0.8306845861192869]
第4幅图像的平移向量:
[-2.144507828065959;
-2.137658756455213;
0.3861555312888436]
第5幅图像的旋转向量:
[63.1817601794685;
-117.2855578733511;
327.5340459209377]
第5幅图像的旋转矩阵:
[-0.1237680939389874, -0.9830519969136794, -0.1352413778646805;
0.8454470843144938, -0.03311262698003439, -0.5330316890754268;
0.5195196690663707, -0.1803117447603135, 0.8352167312468426]
第5幅图像的平移向量:
[-0.3394208745634724;
-2.941274925899604;
0.7239987875443074]
第6幅图像的旋转向量:
[176.6380486063267;
-65.02048705679623;
345.2669628180993]
第6幅图像的旋转矩阵:
[-0.4823787195065527, 0.3144101256594393, 0.8175922234525194;
-0.5902636261183672, -0.8063068742380883, -0.03818476447485269;
0.6472245534965549, -0.5010144682933011, 0.5745301383843724]
第6幅图像的平移向量:
[0.144403698794371;
-2.686413562533621;
-0.08279238304814077]
第7幅图像的旋转向量:
[23.37912628758978;
-71.28708027930361;
401.7783087659996]
第7幅图像的旋转矩阵:
[0.950756682549477, -0.3056521783663705, -0.05136610212392408;
0.3046663933949521, 0.9520979509442887, -0.02622747687825021;
0.05692204602107398, 0.009286423831555549, 0.9983354361181394]
第7幅图像的平移向量:
[0.4433620069430767;
-2.778035766165631;
0.1565310822654871]
第8幅图像的旋转向量:
[84.53413910746443;
-88.75268154189268;
326.4489757550855]
第8幅图像的旋转矩阵:
[-0.882333219506006, -0.1387045774185431, 0.4497211691251699;
-0.1080922696912742, -0.870309912144045, -0.4804963247068739;
0.4580438308602738, -0.4725692510383723, 0.7529104541603049]
第8幅图像的平移向量:
[0.3026042878663719;
-2.832559861959414;
0.5197600078874884]
第9幅图像的旋转向量:
[-66.87955552666558;
-81.79728232518671;
287.3798612501427]
第9幅图像的旋转矩阵:
[-0.06408698919457989, 0.997286705569611, 0.03622270986668297;
-0.8668814706204128, -0.03765202403427882, -0.4970903750638435;
-0.4943777641752957, -0.06325782149453277, 0.8669423708118097]
第9幅图像的平移向量:
[1.918018245182696;
2.198445482038513;
0.6398190872020209]
第10幅图像的旋转向量:
[51.38889872566385;
-112.4792732922813;
348.8614284720838]
第10幅图像的旋转矩阵:
[0.8410751829508221, 0.5075468667660225, 0.1870527055678015;
-0.521221221444936, 0.852916565973049, 0.0293559159998552;
-0.1446408481020841, -0.1221863720908967, 0.9819111546039054]
第10幅图像的平移向量:
[0.2388869800501047;
2.534868757127185;
0.05816455567725017]
第11幅图像的旋转向量:
[55.25157597573984;
-103.974863603741;
332.3331998859927]
第11幅图像的旋转矩阵:
[0.7603104175748064, -0.6302201082550355, -0.1573235013538499;
0.6075084686586226, 0.7756458925501082, -0.1711926104661106;
0.2299163531271294, 0.0345841657577196, 0.9725957053388442]
第11幅图像的平移向量:
[-0.02801590475009446;
-3.011578659457537;
0.5796308944847007]
第12幅图像的旋转向量:
[37.20265745451167;
-92.46700742075161;
299.3885458741333]
第12幅图像的旋转矩阵:
[0.1968247409885918, -0.9604756585987335, -0.1968413843024444;
0.9041946443200382, 0.2554459280495449, -0.3423148010616344;
0.3790673640894628, -0.1106069034112951, 0.9187350251296783]
第12幅图像的平移向量:
[-0.4442257873668548;
-2.891665626351126;
-0.7306268697464358]
第13幅图像的旋转向量:
[49.15686896201693;
-109.7597615043953;
322.2472823512488]
第13幅图像的旋转矩阵:
[-0.02527960043733595, 0.888126856668879, 0.4589026348422781;
-0.9835935284565535, 0.05992383782219021, -0.170155530145356;
-0.1786189031992861, -0.4556751256368033, 0.8720409779911538]
第13幅图像的平移向量:
[0.2685697410235677;
2.70549028727733;
0.2575020268614151]
下面在附上一份来自于其他博客的源码:
/*************************************************************************************
*
* Description:相机标定,张氏标定法 单目标定,一次只能标定一个相机
OPENCV3.0 单目摄像头标定(使用官方自带的标定图片)
https://blog.csdn.net/zc850463390zc/article/details/48946855
* Author :JNU
* Data :2018.7.22
*
************************************************************************************/
#include <opencv2/opencv.hpp>
#include <highgui.hpp>
#include "cv.h"
#include <cv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
//程序运行之前需要更改的参数
//使用官方标定图片集?
//#define SAMPLE
#define MY_DATA
#ifdef SAMPLE
/* 官方数据集 */
const int imageWidth = 640; //摄像头的分辨率
const int imageHeight = 480;
const int boardWidth = 9; //横向的角点数目
const int boardHeight = 6; //纵向的角点数据
const int boardCorner = boardWidth * boardHeight; //总的角点数据
const int frameNumber = 13; //相机标定时需要采用的图像帧数
const int squareSize = 20; //标定板黑白格子的大小 单位mm
const Size boardSize = Size(boardWidth, boardHeight);
const char imageFilePathFormat[] = "sample/right%02d.jpg"; //用于标定的图片路径,格式化字符串sample/left%02d.bmp表明图片路径为 sample/left01.bmp - sample/leftxx.bmp
#elif defined MY_DATA
//自己的数据
const int imageWidth = 1600; //摄像头的分辨率
const int imageHeight = 1200;
const int boardWidth = 9; //横向的角点数目
const int boardHeight = 6; //纵向的角点数据
const int boardCorner = boardWidth * boardHeight; //总的角点数据
const int frameNumber = 10; //相机标定时需要采用的图像帧数
const int squareSize = 30; //标定板黑白格子的大小 单位mm
const Size boardSize = Size(boardWidth, boardHeight);
Size imageSize = Size(imageWidth, imageHeight);
const char imageFilePathFormat[] = "image/right/%d.bmp";
#endif // SAMPLE
Mat intrinsic; //相机内参数
Mat distortion_coeff; //相机畸变参数
vector<Mat> rvecs; //旋转向量
vector<Mat> tvecs; //平移向量
vector<vector<Point2f>> corners; //各个图像找到的角点的集合 和objRealPoint 一一对应
vector<vector<Point3f>> objRealPoint; //各副图像的角点的实际物理坐标集合
vector<Point2f> corner; //某一副图像找到的角点
Mat rgbImage, grayImage;
/*计算标定板上模块的实际物理坐标*/
void calRealPoint(vector<vector<Point3f>>& obj, int boardwidth, int boardheight, int imgNumber, int squaresize)
{
// Mat imgpoint(boardheight, boardwidth, CV_32FC3,Scalar(0,0,0));
vector<Point3f> imgpoint;
for (int rowIndex = 0; rowIndex < boardheight; rowIndex++)
{
for (int colIndex = 0; colIndex < boardwidth; colIndex++)
{
// imgpoint.at<Vec3f>(rowIndex, colIndex) = Vec3f(rowIndex * squaresize, colIndex*squaresize, 0);
imgpoint.push_back(Point3f(rowIndex * squaresize, colIndex * squaresize, 0));
}
}
for (int imgIndex = 0; imgIndex < imgNumber; imgIndex++)
{
obj.push_back(imgpoint);
}
}
/*设置相机的初始参数 也可以不估计*/
void guessCameraParam(void)
{
/*分配内存*/
intrinsic.create(3, 3, CV_64FC1);
distortion_coeff.create(5, 1, CV_64FC1);
/*
fx 0 cx
0 fy cy
0 0 1
*/
intrinsic.at<double>(0, 0) = 256.8093262; //fx
intrinsic.at<double>(0, 2) = 160.2826538; //cx
intrinsic.at<double>(1, 1) = 254.7511139; //fy
intrinsic.at<double>(1, 2) = 127.6264572; //cy
intrinsic.at<double>(0, 1) = 0;
intrinsic.at<double>(1, 0) = 0;
intrinsic.at<double>(2, 0) = 0;
intrinsic.at<double>(2, 1) = 0;
intrinsic.at<double>(2, 2) = 1;
/*
k1 k2 p1 p2 p3
*/
distortion_coeff.at<double>(0, 0) = -0.193740; //k1
distortion_coeff.at<double>(1, 0) = -0.378588; //k2
distortion_coeff.at<double>(2, 0) = 0.028980; //p1
distortion_coeff.at<double>(3, 0) = 0.008136; //p2
distortion_coeff.at<double>(4, 0) = 0; //p3
}
void outputCameraParam(void)
{
/*保存数据*/
//cvSave("cameraMatrix.xml", &intrinsic);
//cvSave("cameraDistoration.xml", &distortion_coeff);
//cvSave("rotatoVector.xml", &rvecs);
//cvSave("translationVector.xml", &tvecs);
/*保存数据*/
/*输出数据*/
FileStorage fs("intrinsics.yml", FileStorage::WRITE);
if (fs.isOpened())
{
fs << "intrinsic" << intrinsic << "distortion_coeff" << distortion_coeff ;
fs.release();
}
else
{
cout << "Error: can not save the intrinsics!!!!!" << endl;
}
fs.open("extrinsics.yml", FileStorage::WRITE);
if (fs.isOpened())
{
fs << "rvecs" << rvecs << "tvecs" << tvecs;
fs.release();
}
else
{
cout << "Error: can not save the extrinsics parameters\n";
}
/*输出数据*/
cout << "fx :" << intrinsic.at<double>(0, 0) << endl << "fy :" << intrinsic.at<double>(1, 1) << endl;
cout << "cx :" << intrinsic.at<double>(0, 2) << endl << "cy :" << intrinsic.at<double>(1, 2) << endl;
cout << "k1 :" << distortion_coeff.at<double>(0, 0) << endl;
cout << "k2 :" << distortion_coeff.at<double>(1, 0) << endl;
cout << "p1 :" << distortion_coeff.at<double>(2, 0) << endl;
cout << "p2 :" << distortion_coeff.at<double>(3, 0) << endl;
cout << "p3 :" << distortion_coeff.at<double>(4, 0) << endl;
}
void main(char *args)
{
Mat img;
int goodFrameCount = 0;
namedWindow("chessboard");
cout << "按Q退出 ..." << endl;
while (goodFrameCount < frameNumber)
{
char filename[100];
//sprintf_s(filename, "image/right/%d.bmp", goodFrameCount + 1);
sprintf_s(filename, imageFilePathFormat, goodFrameCount + 1);
// cout << filename << endl;
rgbImage = imread(filename, CV_LOAD_IMAGE_COLOR);
cvtColor(rgbImage, grayImage, CV_BGR2GRAY);
imshow("Camera", grayImage);
bool isFind = findChessboardCorners(rgbImage, boardSize, corner, 0);
if (isFind == true) //所有角点都被找到 说明这幅图像是可行的
{
/*
Size(5,5) 搜索窗口的一半大小
Size(-1,-1) 死区的一半尺寸
TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 20, 0.1)迭代终止条件
*/
cornerSubPix(grayImage, corner, Size(5, 5), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 20, 0.1));
drawChessboardCorners(rgbImage, boardSize, corner, isFind);
imshow("chessboard", rgbImage);
corners.push_back(corner);
//string filename = "res\\image\\calibration";
//filename += goodFrameCount + ".jpg";
//cvSaveImage(filename.c_str(), &IplImage(rgbImage)); //把合格的图片保存起来
goodFrameCount++;
cout << "The image is good" << endl;
}
else
{
cout << goodFrameCount+1 <<" The image is bad please try again" << endl;
}
// cout << "Press any key to continue..." << endl;
// waitKey(0);
if (waitKey(10) == 'q')
{
break;
}
// imshow("chessboard", rgbImage);
}
/*
图像采集完毕 接下来开始摄像头的校正
calibrateCamera()
输入参数 objectPoints 角点的实际物理坐标
imagePoints 角点的图像坐标
imageSize 图像的大小
输出参数
cameraMatrix 相机的内参矩阵
distCoeffs 相机的畸变参数
rvecs 旋转矢量(外参数)
tvecs 平移矢量(外参数)
*/
/*设置实际初始参数 根据calibrateCamera来 如果flag = 0 也可以不进行设置*/
guessCameraParam();
cout << "guess successful" << endl;
/*计算实际的校正点的三维坐标*/
calRealPoint(objRealPoint, boardWidth, boardHeight, frameNumber, squareSize);
cout << "cal real successful" << endl;
/*标定摄像头*/
calibrateCamera(objRealPoint, corners, Size(imageWidth, imageHeight), intrinsic, distortion_coeff, rvecs, tvecs, 0);
cout << "calibration successful" << endl;
/*保存并输出参数*/
outputCameraParam();
cout << "out successful" << endl;
/*显示畸变校正效果*/
Mat cImage;
undistort(rgbImage, cImage, intrinsic, distortion_coeff);
imshow("Corret Image", cImage);
cout << "Correct Image" << endl;
cout << "Wait for Key" << endl;
waitKey(0);
system("pause");
}