当前位置:网站首页>基于OpenCV的相机标定流程
基于OpenCV的相机标定流程
2022-07-30 05:44:00 【柠檬甜瓜】
1、根据真实世界与图像坐标角点坐标对应关系计算相机相机内参矩阵与相机外参矩阵的积,即矩阵H;
2、根据图像的单应性矩阵构建点对应关系求解相机内参(理论至少需要三张图,因为内参矩阵构建的对称矩阵B有6个自由度,一张图只能提供两个方程);此处可参考:https://blog.csdn.net/weixin_41480034/article/details/121759128中(三,2)
3、求解相机外参
4、求解相机畸变因子
#include <iostream>
#include <fstream>
#include <string>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char **argv)
{
string dir = "C:\\Users\\15734\\Desktop\\Reconstruction\\calibration\\calibration\\calibration\\"; //标定图片所在文件夹
ifstream fin(dir + "calibdata.txt"); //读取标定图片的路径,与cpp程序在同一路径下
if (!fin) //检测是否读取到文件,以输入方式打开文件
{
cerr << "没有找到文件" << endl;
return -1;
}
ofstream fout(dir + "calibration_result.txt"); //输出结果保存在此文本文件下,,以输出方式打开文件
//依次读取每一幅图片,从中提取角点
cout << "开始提取角点……" << endl;
int image_nums = 0; //图片数量
cv::Size image_size; //图片尺寸
int points_per_row = 11; //每行的内点数
int points_per_col = 8; //每列的内点数
cv::Size corner_size = cv::Size(points_per_row, points_per_col); //标定板每行每列角点个数,共points_per_row * points_per_col个角点
vector<cv::Point2f> points_per_image; //缓存每幅图检测到的角点
points_per_image.clear();
vector<vector<cv::Point2f>> points_all_images; //用一个二维数组保存检测到的所有角点
string image_file_name; //声明一个文件名的字符串
while (getline(fin, image_file_name)) //逐行读取,将行读入字符串,第三个参数在不设置的情况下系统默认该字符为'\n'
{
image_nums++;
//读入图片
string tmp = dir + image_file_name;
cv::Mat image_raw = cv::imread(image_file_name); //image_file_name是读取的.txt文件里面的一行
if (image_nums == 1)
{
// cout<<"channels = "<<image_raw.channels()<<endl;
// cout<<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;
}
//角点检测
cv::Mat image_gray; //存储灰度图的矩阵
cv::cvtColor(image_raw, image_gray, COLOR_BGR2GRAY); //将BGR图转化为灰度图
// cout<<"image_gray.type() = "<<image_gray.type()<<endl; //CV_8UC1
//step1 提取角点
bool success = cv::findChessboardCorners(image_gray, corner_size, points_per_image); //corner_size = points_per_row * points_per_col,每一行内点数乘以每一列内点数
if (!success)
{
cout << "can not find the corners " << endl;
exit(1);
}
else
{
//亚像素精确化(两种方法)
//step2 亚像素角点
cv::find4QuadCornerSubpix(image_gray, points_per_image, cv::Size(5, 5));
//cornerSubPix(image_gray,points_per_image,Size(5,5));
points_all_images.push_back(points_per_image); //保存亚像素角点
//在图中画出角点位置
//step3 角点可视化
cv::drawChessboardCorners(image_raw, corner_size, points_per_image, success); //将角点连线
//cv::imshow("Camera calibration", image_raw);
cv::waitKey(0); //等待按键输入
}
}
cv::destroyAllWindows();
//输出图像数目
int image_sum_nums = points_all_images.size();
cout << "image_sum_nums = " << image_sum_nums << endl;
//开始相机标定
cv::Size block_size(45, 45); //每个小方格实际大小, 只会影响最后求解的平移向量t
cv::Mat camera_K(3, 3, CV_32FC1, cv::Scalar::all(0)); //内参矩阵3*3
cv::Mat distCoeffs(1, 5, CV_32FC1, cv::Scalar::all(0)); //畸变矩阵1*5,既考虑径向畸变,又考虑切向
vector<cv::Mat> rotationMat; //旋转矩阵
vector<cv::Mat> translationMat; //平移矩阵
//初始化角点三维坐标,从左到右,从上到下!!!
vector<cv::Point3f> 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(cv::Point3f(block_size.width * j, block_size.height * i, 0));
}
}
//都是以第一个角点作为真实世界原点,创建Vector容器points3D_all_images时就进行初始化,image_nums个points3D_per_image元素
vector<vector<cv::Point3f>> 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从图像中提取的角点坐标
cv::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<cv::Point2f> points_reproject; //重投影点
cout << "\n\t每幅图像的标定误差:\n";
fout << "每幅图像的标定误差:\n";
for (int i = 0; i < image_nums; i++)
{
vector<cv::Point3f> points3D_per_image = points3D_all_images[i]; //第i张图像中角点的真实世界坐标
//通过之前标定得到的相机内外参,对三维点进行重投影,三维到二维图像
cv::projectPoints(points3D_per_image, rotationMat[i], translationMat[i], camera_K, distCoeffs, points_reproject);
//计算两者之间的误差
vector<cv::Point2f> detect_points = points_all_images[i]; //提取到的图像角点
cv::Mat detect_points_Mat = cv::Mat(1, detect_points.size(), CV_32FC2); //变为1*detect_points.size()的矩阵,2通道保存提取角点的像素坐标
cv::Mat points_reproject_Mat = cv::Mat(1, points_reproject.size(), CV_32FC2); //2通道保存投影角点的像素坐标
for (int j = 0; j < detect_points.size(); j++)
{
detect_points_Mat.at<cv::Vec2f>(0, j) = cv::Vec2f(detect_points[j].x, detect_points[j].y);
points_reproject_Mat.at<cv::Vec2f>(0, j) = cv::Vec2f(points_reproject[j].x, points_reproject[j].y);
}
err = cv::norm(points_reproject_Mat, detect_points_Mat, cv::NormTypes::NORM_L2);
total_err += err /= point_counts;
cout << "第" << i + 1 << "幅图像的平均误差为: " << err << "像素" << endl;
fout << "第" << i + 1 << "幅图像的平均误差为: " << err << "像素" << endl;
}
cout << "总体平均误差为: " << total_err / image_nums << "像素" << endl;
fout << "总体平均误差为: " << total_err / image_nums << "像素" << endl;
cout << "评价完成!" << endl;
//将标定结果写入txt文件
cv::Mat rotate_Mat = cv::Mat(3, 3, CV_32FC1, cv::Scalar::all(0)); //保存旋转矩阵
cout << "\n相机内参数矩阵:" << endl;
cout << camera_K << endl << endl;
fout << "\n相机内参数矩阵:" << endl;
fout << camera_K << endl << endl;
cout << "畸变系数:\n";
cout << distCoeffs << endl << endl << endl;
fout << "畸变系数:\n";
fout << distCoeffs << endl << endl << endl;
//cout << "第一张图像法旋转向量 = " << rotationMat[0] << endl;
//cv::Mat tmp = rotationMat[0].clone();
for (int i = 0; i < image_nums; i++)
{
cv::Rodrigues(rotationMat[i], rotate_Mat); //将旋转向量通过罗德里格斯公式转换为旋转矩阵,旋转方向与旋转轴一致,而长度等于旋转角
fout << "第" << i + 1 << "幅图像的旋转矩阵为:" << endl;
fout << rotate_Mat << endl;
fout << "第" << i + 1 << "幅图像的平移向量为:" << endl;
fout << translationMat[i] << endl
<< endl;
}
fout << endl;
fout.close();
return 0;
}
参考:
https://blog.csdn.net/qq_41253960/article/details/124928140
《OpenCV4.3快速入门》
边栏推荐
猜你喜欢
常用损失函数(二):Dice Loss
Flink CDC implements Postgres to MySQL streaming processing transmission case
八、Kotlin基础学习:1、数据类;2、单例;3、伴生对象;4、密封类;
七、Kotlin基础学习:1、创建类;2、构造函数;3、继承;4、封装;5、抽象类;6、接口;7、嵌套类;8、内部类;9、枚举类
Jackson serialization failure problem - oracle data return type can't find the corresponding Serializer
二十一、Kotlin进阶学习:实现简单的网络访问封装
MySQL 5.7 installation tutorial (all steps, nanny tutorials)
Self-augmented Unpaired Image Dehazing via Density and Depth Decomposition程序运行记录
mysql delete duplicate data in the table, (retain only one row)
Trust anchor for certification path not found. Exception solution.
随机推荐
十一、Kotlin进阶学习:1、集合;2、List操作;3、可变集合——MutableList;4、Set;5、Map;6、MutableMap;
常用损失函数(二):Dice Loss
SQL Server Installation Tutorial
Reasons and solutions for Invalid bound statement (not found)
Servlet基本原理与常见API方法的应用
Servlet basic principles and application of common API methods
AAcell五号文档室——跨平台文件传输的小室一间一间的
MySQL special statement and optimizer
基于遥感解译与GIS技术环境影响评价图件制作(最新导则)
十、Kotlin基础学习:1、延迟加载;2、异常处理;3、使用 throw 主动抛出异常;4、自定义异常;
R语言 生态环境领域应用
Mysql 客户端常见异常分析
使用kotlin扩展插件/依赖项简化代码(在最新版本4.0以后,此插件已被弃用,故请选择性学习,以了解为主。)
MySQL - Multi-table query and case detailed explanation
Flink-stream/batch/OLAP integrated to get Flink engine
Using PyQt5 to add an interface to YoloV5 (1)
SQL Server安装教程
Kaggle-M5
nodejs PM2监控及报警邮件发送(二)
MySQL 特殊语句及优化器