棋盘格标定程序及源码
-
**相机标定的目的:**获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的旋转和平移矩阵),
内参和外参系数可以对之后相机拍摄的图像就进行矫正,得到畸变相对很小的图像。
-
**相机标定的输入:**标定图像上所有内角点的图像坐标,标定板图像上所有内角点的空间三维坐标(一般情况下假定图像位于Z=0平面上)。对于每张拍摄的棋盘图片,检测图片中所有棋盘格的特征点(内角点,也就是黑白棋盘交叉点)。我们定义打印的棋盘图纸位于世界坐标系zw=0的平面上,世界坐标系的原点位于棋盘图纸的固定一角。像素坐标系原点位于图片左上角。
-
**相机标定的输出:**摄像机的内参、外参系数。
-
标定方法: 单目相机标定,张氏标定法
-
标定图片: opencv的安装路径下opencv\sources\samples\data
-
Progress:
step1. 准备标定图片
step2. 对每一张标定图片,提取角点信息
step3. 对每一张标定图片进一步提取亚像素角点信息
step4. 在期盼标定图上绘制找到的内角点(仅用作显示)
step5. 进行标定,并对标定结果进行评价
step6. 对比标定前后的图片
其中main()函数中 string infilename 为filenameLeft.txt文档所在路径,该文本文件存储的是每一张图片的路径(注意文档中的空白行)
string outfilename为保存标定结果的文本文档路径
/*************************************************************************************
************************************************************************************/
#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 = "C:/Users/TXL/Desktop/work/0805minDistanceMeasure/cameraCalibration/filenameLeft.txt"; //存放图片路径的txt文件
string outfilename = "C:/Users/TXL/Desktop/work/0805minDistanceMeasure/cameraCalibration/left_caliberation_result.txt";
//标定所用图片文件的路径,每一行保存一个标定图片的路径 ifstream 是从硬盘读到内存
ifstream fin(infilename);
//保存标定的结果 ofstream 是从内存写到硬盘
ofstream fout(outfilename);
/*
1.读取毎一幅图像,从中提取出角点,然后对角点进行亚像素精确化、获取每个角点在像素坐标系中的坐标
像素坐标系(u,v)的原点位于图像的左上角
*/
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);//将图片的每一行路径存到filenames向量里
//读取图片
cv::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
{
cv::Mat viewGray;
//转换为灰度图片
cv::cvtColor(imageInput, viewGray, cv::COLOR_BGR2GRAY);
//亚像素精确化 对粗提取的角点进行精确化
cv::find4QuadCornerSubpix(viewGray, imagePointsBuf, cv::Size(5, 5));//参数2,初始的角点向量,同时作为亚像素角点坐标位置输出
//保存亚像素点
imagePointsSeq.push_back(imagePointsBuf);//将每一张图片的亚像素角点向量存到imagePointsSeq向量序列中
//在图像上显示角点位置
cv::drawChessboardCorners(viewGray, boardSize, imagePointsBuf, true);
//显示图片
cv::imshow("Camera Calibration", viewGray);
//cv::imwrite("test.jpg", viewGray);
//等待0.5s
cv::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;//mun = 所有图片总角点数/每张图片的角点数,获得图片的张数
int p = i % cornerNum;//p = 所有图片总角点个数%每张图片角点个数,用于检测一张图片中的角点是否输出完毕
//cornerNum是每幅图片的角点个数,此判断语句是为了输出,便于调试
if (p == 0)
{
//新的一张图片
std::cout << "\n第 " << num + 1 << "张图片的数据 -->: " << std::endl;
}
//输出所有的角点
std::cout.setf(ios::right);//设置对齐方式
std::cout.width(1);//设置输出宽度(间隔宽度1字节)
std::cout.flags(ios::fixed);
std::cout.precision(6);//设置输出精度
std::cout<< p + 1 << ":(" << imagePointsSeq[num][p].x << ","
<<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);//表示一个格子大小为26mm*26mm
//毎幅图片角点数量
std::vector<int> pointCounts;
//保存标定板上角点的三维坐标
std::vector<std::vector<cv::Point3f>> objectPoints;//每张图片存进一个vector中
//摄像机内参数矩阵 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++)//imageCount为图片数量
{
std::vector<cv::Point3f> tempPointSet;//标定板的三维坐标,左上角第一个内角点坐标为(0,0,0)
//行数
for (i = 0; i < boardSize.height; i++)//boardSize.height角点行数
{
//列数
for (j = 0; j < boardSize.width; j++)//boardSize.width角点列数
{
cv::Point3f realPoint;
//假设标定板放在世界坐标系中z=0的平面上。
realPoint.x = i * squareSize.width;
realPoint.y = j * squareSize.height;
realPoint.z = 0;
tempPointSet.push_back(realPoint);
}
}
//标定板每次的三维原点可以不同,但是这里我们都是以标定板的左上角第一个内角点为3D原点
objectPoints.push_back(tempPointSet);
}
//初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板
for (i = 0; i < imageCount; i++)
{
pointCounts.push_back(boardSize.width*boardSize.height);
}
//开始标定param1:3D点 param2:2D点 param3:图像的像素尺寸大小 param4:相机内参数矩阵 param5:畸变矩阵
//param6:旋转向量 param7:平移向量 param8: 标定时采用的算法,有默认值
cv::calibrateCamera(objectPoints, imagePointsSeq, imageSize, cameraMatrix, distCoeffs, rvecsMat, tvecsMat);
std::cout << "标定完成" << std::endl;
/*
对标定结果进行评价的方法是通过得到的相机内外参数,对空间的三维点进行重新投影计算,得到空间三维点
在图像上新的投影点坐标,计算投影坐标和亚像素角点坐标之间的偏差,偏差越小,标定的结果越好
对空间三维点进行反向投影的函数是projectPoints()
*/
//对标定结果进行评价
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];//totalErr = totalErr+err; err = 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(不进行立体校正、立体校正需要使用双摄)
cv::initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cameraMatrix, imageSize, CV_32FC1, mapx, mapy);
//读取一张图片
cv::Mat imageSource = imread(filenames[i]);
cv::Mat newimage = imageSource.clone();
//另一种不需要转换矩阵的方式
//undistort(imageSource,newimage,cameraMatrix,distCoeffs);
//进行校正
cv::remap(imageSource, newimage, mapx, mapy, INTER_LINEAR);
cv::imshow("原始图像", imageSource);
cv::imshow("矫正后图像", newimage);
cv::waitKey(500);
}
//释放资源
fin.close();
fout.close();
system("pause");
}
}
//Reference https://www.cnblogs.com/zyly/p/9366080.html
百度云链接
链接:https://pan.baidu.com/s/10fMazQohubCB8ZPG91JiPA
提取码:qdhw