事先需要把标定图片calib01.jpeg放在主程序cpp同一个目录下:
calibdata.txt的内容是标定图片的路径+图片文件名称(如果标定图片在cpp文件下则只需要填图片名称,如下:)
希望对大家有帮助!!!(目前我使用的VS是2019版本,opencv4_1_2)。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main()
{
string dir = "E:\\myProgame\\CameraCalibration\\CameraCalibration\\"; // 标定图片所在文件夹
ifstream fin(dir + "calibdata.txt"); // 读取标定图片的路径,与cpp程序在同一路径下
if (!fin)
{
cerr << "没有找到文件" << endl; // 检测是否读取到文件,以输入方式打开文件
return -1;
}
ofstream fout(dir + "calibration_result.txt"); // 输出结果保存在此文本文件下,以输出方式打开文件
cout << "开始提取角点……" << endl;
int image_nums = 0; // 图片数量
Size image_size; // 图片尺寸
int points_per_row = 9; //每行的内点数
int points_per_col = 6; //每列的内点数
Size corner_size = Size(points_per_row, points_per_col); // 标定板每行每列角点个数,共9 * 6个角点
vector points_per_image; // 缓存每幅图检测到的角点
points_per_image.clear(); // 初始化为空
vector> points_all_images; // 用一个二维数组保存检测到的所有角点
string image_file_name; // 声明一个文件名的字符串
while (getline(fin, image_file_name))
{
image_nums++;
Mat image_raw = imread(image_file_name);
if (image_nums == 1)
{
cout << "channels = " << image_raw.channels() << endl; // 图像的通道数
cout << "image type = " << image_raw.type() << endl; // 数据类型,CV_8UC3
image_size.width = image_raw.cols; // 图像的宽,对应着列数(x)
image_size.height = image_raw.rows; // 图像的高,对应着行数(y)
cout << "image_size.width = " << image_size.width << endl;
cout << "image_size.height = " << image_size.height << endl;
}
Mat image_gray; // 存储灰度图的矩阵
cvtColor(image_raw, image_gray, COLOR_BGR2GRAY); // 将BGR图转化为灰度图
bool success = findChessboardCorners(image_gray, corner_size, points_per_image); // 角点检测
if (!success)
{
cout << "can not find the corners!" << endl;
exit(1);
}
else
{
find4QuadCornerSubpix(image_gray, points_per_image, Size(5, 5)); // 亚像素角点,也可使用cornerSubPix()
points_all_images.push_back(points_per_image); // 保存亚像素角点
drawChessboardCorners(image_raw, corner_size, points_per_image, success); // 将角点连线
}
}
cout << "image_sum_nums = " << image_nums << endl; // 输出图像数目
//开始相机标定
Size block_size(60, 60); // 每个小方格实际大小60mm, 只会影响最后求解的平移向量t
Mat camera_K(3, 3, CV_32FC1, Scalar::all(0)); // 内参矩阵3*3
Mat distCoeffs(1, 5, CV_32FC1, Scalar::all(0)); // 畸变矩阵1*5,既考虑径向畸变,又考虑切向
vector rotationMat; // 旋转矩阵
vector translationMat; // 平移矩阵
vector points3D_per_image; // 初始化角点三维坐标,从左到右,从上到下
for (int i = 0; i < corner_size.height; i++)
{
for (int j = 0; j < corner_size.width; j++)
{
points3D_per_image.push_back(Point3f(block_size.width * j, block_size.height * i, 0));
}
}
vector> points3D_all_images(image_nums, points3D_per_image); // 保存所有图像角点的三维坐标, z=0
int point_counts = corner_size.area(); // 每张图片上角点个数 (width*height)
/**
* points3D_all_images: 真实三维坐标
* points_all_images: 提取的角点
* image_size: 图像尺寸
* camera_K : 内参矩阵K
* distCoeffs: 畸变参数,径向畸变k1,k2,切向畸变p1,p2,径向畸变k3
* rotationMat: 每个图片的旋转向量
* translationMat: 每个图片的平移向量
* */
// step4 标定。points3D_all_images:角点对应的3D点坐标;points_all_images:从图像中提取的角点坐标
calibrateCamera(points3D_all_images, points_all_images, image_size, camera_K, distCoeffs, rotationMat, translationMat, 0);
// step5 对标定结果进行评价
double total_err = 0.0; // 所有图像平均误差总和
double err = 0.0; // 每幅图像的平均误差
vector points_reproj; // 重投影点
fout << "计算每幅图像的标定误差:\n";
for (int i = 0; i < image_nums; i++)
{
vector detect_points = points_all_images[i]; // 提取到的图像角点
vector points3D_per_image = points3D_all_images[i]; // 第i张图像中角点的真实世界坐标
projectPoints(points3D_per_image, rotationMat[i], translationMat[i], camera_K, distCoeffs, points_reproj); // 重投影
Mat detect_points_Mat = Mat(1, detect_points.size(), CV_32FC2); // 变为1*S的矩阵,2通道保存提取角点的像素坐标
Mat points_reproj_Mat = Mat(1, points_reproj.size(), CV_32FC2); // 变为1*S的矩阵,2通道保存投影角点的像素坐标
for (int j = 0; j < detect_points.size(); j++)
{
detect_points_Mat.at(0, j) = Vec2f(detect_points[j].x, detect_points[j].y);
points_reproj_Mat.at(0, j) = Vec2f(points_reproj[j].x, points_reproj[j].y);
}
err = norm(points_reproj_Mat, detect_points_Mat, NormTypes::NORM_L2); // 计算两者之间的误差
total_err += err /= point_counts;
fout << "第" << i + 1 << "幅图像的平均误差为: " << err << "像素" << endl;
}
fout << "总体平均误差为: " << total_err / image_nums << "像素" << endl << endl;
// 将标定结果写入txt文件
fout << "相机内参数矩阵:" << endl << camera_K << endl << endl;
fout << "畸变系数:" << endl << distCoeffs << endl << endl;
Mat rotate_Mat = Mat(3, 3, CV_32FC1, Scalar::all(0)); // 保存旋转矩阵
for (int i = 0; i < image_nums; i++)
{
Rodrigues(rotationMat[i], rotate_Mat); // 将旋转向量通过罗德里格斯公式转换为旋转矩阵
fout << "第" << i + 1 << "幅图像的旋转矩阵为:" << endl << rotate_Mat << endl << endl;
fout << "第" << i + 1 << "幅图像的平移向量为:" << endl << translationMat[i] << endl << endl;
}
fout << endl;
fout.close();
return 0;
}
拍摄图像示例
标定结果保存在一个txt文件中:
bool success = findChessboardCorners(image_gray, corner_size, points_per_image);
输入:灰度图像image_gray;角点个数大小corner_size,如Size(9,6);
输出:所有角点在图像上的像素坐标points_per_image
find4QuadCornerSubpix(image_gray, points_per_image, Size(5, 5));
输入:灰度图像image_gray;角点像素坐标points_per_image
输出:角点亚像素坐标points_per_image
calibrateCamera(points3D_all_images, points_all_images, image_size, camera_K, distCoeffs, rotationMat, translationMat, 0);
输入:
* points3D_all_images: 真实三维坐标,格式vector
* points_all_images: 提取的角点,格式vector
* image_size: 图像尺寸
输出:
* camera_K : 内参矩阵K,格式Mat(3,3)
* distCoeffs: 畸变参数,径向畸变k1,k2,切向畸变p1,p2,径向畸变k3,格式Mat(1,5)
* rotationMat: 每个图片的旋转向量,格式vector
* translationMat: 每个图片的平移向量,格式vector
projectPoints(points3D_per_image, rotationMat[i], translationMat[i], camera_K, distCoeffs, points_reproj);
输入:图像中角点的真实世界坐标points3D_per_image,格式vector
输出:重投影后像素点坐标points_reproj,格式vector
参考:相机标定(六)—— 张正友标定法__归尘_的博客-CSDN博客_张正友标定法