#include <opencv2/opencv.hpp>
#include <iostream>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <vector>
#include <direct.h> // 用于目录创建 (Windows)
#include <cmath> // 用于数学计算
#include <numeric>
using namespace std;
using namespace cv;
// 定义棋盘格内角点数量(宽高方向)
const int CHECKERBOARD_COLS = 18; // 横向角点数(高)
const int CHECKERBOARD_ROWS = 15; // 纵向角点数(宽)
const double SQUARE_SIZE = 60.0; //棋盘格单个格子边长
// 获取当前时间字符串(格式:YYYY-MM-DD HH:MM:SS)
string getCurrentTimeString() {
auto now = chrono::system_clock::now();
time_t time = chrono::system_clock::to_time_t(now);
tm localTime;
// 跨平台时间转换
#ifdef _WIN32
localtime_s(&localTime, &time);
#else
localtime_r(&time, &localTime);
#endif
stringstream ss;
ss << put_time(&localTime, "%Y-%m-%d %H:%M:%S");
return ss.str();
}
// 创建目录(Windows专用)
void createDirectoryIfNotExists(const string& path) {
#ifdef _WIN32
_mkdir(path.c_str());
#else
mkdir(path.c_str(), 0777); // Linux需包含<sys/stat.h>
#endif
}
//图像预处理
Mat enhancedPreprocessing(Mat& src) {
Mat gray, equalized;
// 转换为灰度图
cvtColor(src, gray, COLOR_BGR2GRAY);
//自适应直方图均衡化
Ptr<CLAHE> clahe = createCLAHE(1.0, Size(4, 4));
clahe->apply(gray, gray);
//高斯模糊降噪
GaussianBlur(gray, gray, Size(3, 3), 1.2);
return gray;
}
//多阶段亚像素优化
void multiStageRefinement(Mat& gray, vector<Point2f>& corners) {
//动态窗口调整策略
TermCriteria criteria1(TermCriteria::EPS + TermCriteria::COUNT, 50, 0.005);
// 第一阶段:大窗口快速收敛
cornerSubPix(gray, corners, Size(21, 21), Size(-1, -1), criteria1);
// 第二阶段:中窗口精细调整
TermCriteria criteria2(TermCriteria::EPS + TermCriteria::COUNT, 30, 0.0005);
cornerSubPix(gray, corners, Size(17, 17), Size(-1, -1), criteria2);
// 第三阶段:小窗口精细调整
TermCriteria criteria3(TermCriteria::EPS + TermCriteria::COUNT, 20, 0.00005);
cornerSubPix(gray, corners, Size(9, 9), Size(-1, -1), criteria3);
}
//方向向量计算函数实现
void calcChessboardDirections(const vector<Point2f>& corners,
Size boardSize, // 正确尺寸:(宽, 高)即横向×纵向角点数
vector<Vec2f>& directions,
bool useEdgePoints = false) {
directions.clear();
int pointCount = corners.size();
// 将一维角点数组转换为二维网格
vector<vector<Point2f>> grid(boardSize.height); // height对应纵向角点数(行)
for (int i = 0; i < boardSize.height; ++i) {
grid[i] = vector<Point2f>(
corners.begin() + i * boardSize.width, // width对应横向角点数(列)
corners.begin() + (i + 1) * boardSize.width
);
}
// 计算每个内点的方向向量
for (int i = 0; i < boardSize.height; ++i) {
for (int j = 0; j < boardSize.width; ++j) {
// 忽略边缘点(保留内部点,边缘点无右侧或下侧点)
if (j >= boardSize.width - 1 || i >= boardSize.height - 1) continue;
// 计算右侧和下方相邻点的向量
Vec2f right_vec = grid[i][j + 1] - grid[i][j];
Vec2f down_vec = grid[i + 1][j] - grid[i][j];
// 单位化向量(避免长度影响方向判断)
right_vec /= norm(right_vec);
down_vec /= norm(down_vec);
directions.push_back(right_vec);
directions.push_back(down_vec);
}
}
}
//方向一致性验证函数
bool validateDirectionConsistency(const vector<Vec2f>& directions,
float angleThreshold = 3.0f,
float magThreshold = 0.05f) {
if (directions.empty()) return false;
// 统计主方向
Vec2f avgRight(0, 0), avgDown(0, 0);
for (size_t i = 0; i < directions.size(); i += 2) {
avgRight += directions[i];
avgDown += directions[i + 1];
}
avgRight /= static_cast<float>(directions.size() / 2);
avgDown /= static_cast<float>(directions.size() / 2);
// 计算正交性(理想情况下right和down向量应垂直)
float ortho = avgRight.dot(avgDown);
if (fabs(ortho) > cos((90 - angleThreshold) * CV_PI / 180.0)) {
cerr << "正交性不满足: " << ortho << endl;
return false;
}
// 检查每个方向向量与主方向的一致性
for (size_t i = 0; i < directions.size(); ++i) {
Vec2f ref = (i % 2 == 0) ? avgRight : avgDown; // 偶索引为横向方向,奇索引为纵向方向
Vec2f vec = directions[i];
// 计算角度偏差(向量夹角)
float dotProduct = vec.dot(ref);
float normProduct = norm(vec) * norm(ref);
if (normProduct == 0) continue; // 避免除零错误(理论上不会发生)
float angle = acos(dotProduct / normProduct) * 180.0 / CV_PI;
if (angle > angleThreshold) {
cerr << "方向偏差过大: " << angle << "度" << endl;
return false;
}
// 检查向量长度变化(单位化后理论上应为1,允许一定误差)
if (fabs(norm(vec) - norm(ref)) > magThreshold) {
cerr << "向量长度变化异常" << endl;
return false;
}
}
return true;
}
// 计算投影误差
vector<vector<double>> calculateReprojectionErrors(
const vector<vector<Point3f>>& objPoints,
const vector<vector<Point2f>>& imgPoints,
const vector<Mat>& rvecs,
const vector<Mat>& tvecs,
const Mat& cameraMatrix,
const Mat& distCoeffs) {
vector<vector<double>> reprojErrs(objPoints.size());
vector<Point2f> reprojPoints;
for (size_t i = 0; i < objPoints.size(); i++) {
// 投影三维点到图像平面(考虑畸变)
projectPoints(Mat(objPoints[i]), rvecs[i], tvecs[i],
cameraMatrix, distCoeffs, reprojPoints);
// 计算每个点的欧氏距离误差
for (size_t j = 0; j < objPoints[i].size(); j++) {
double dx = reprojPoints[j].x - imgPoints[i][j].x;
double dy = reprojPoints[j].y - imgPoints[i][j].y;
double error = sqrt(dx * dx + dy * dy);
reprojErrs[i].push_back(error);
}
// 计算单图像平均误差(输出到控制台)
double meanErr = accumulate(reprojErrs[i].begin(), reprojErrs[i].end(), 0.0) / reprojErrs[i].size();
cout << "图像 " << i + 1 << " 平均投影误差: " << fixed << setprecision(3)
<< meanErr << " 像素" << endl;
}
return reprojErrs;
}
const int calibrationFlags = CALIB_RATIONAL_MODEL | CALIB_USE_LU | CALIB_FIX_ASPECT_RATIO | CALIB_ZERO_TANGENT_DIST | CALIB_FIX_K3 | CALIB_FIX_K4 | CALIB_FIX_K5 | CALIB_FIX_K6;
int main() {
// ================== 初始化设置 ==================
vector<vector<Point3f>> objPoints; // 世界坐标系3D点(每个图像对应的棋盘格点)
vector<vector<Point2f>> imgPoints; // 图像坐标系2D点(检测到的角点)
// 生成世界坐标系点(棋盘格每个角点的3D坐标,z=0表示在XY平面)
vector<Point3f> objp;
for (int i = 0; i < CHECKERBOARD_ROWS; ++i) { // 纵向循环(行)
for (int j = 0; j < CHECKERBOARD_COLS; ++j) { // 横向循环(列)
objp.emplace_back(j * SQUARE_SIZE, i * SQUARE_SIZE, 0.0f);
}
}
// ================== 图像读取处理 ==================
vector<String> imagePaths;
string imageFolder = "D:\\VSproject\\Project\\Project1\\Project1\\images\\2\\*.png";
glob(imageFolder, imagePaths);
if (imagePaths.empty()) {
cerr << "错误:未找到任何图像文件!请检查路径:" << imageFolder << endl;
return -1;
}
Mat frame, processed;
Size imageSize; // 图像尺寸(宽×高)
int validImageCount = 0; // 有效检测到棋盘格的图像数量
for (size_t i = 0; i < imagePaths.size(); ++i) {
frame = imread(imagePaths[i]);
if (frame.empty()) {
cerr << "警告:无法读取图像 " << imagePaths[i] << endl;
continue;
}
// 首次获取图像尺寸(所有图像尺寸需一致)
if (imageSize.empty()) {
imageSize = frame.size();
cout << "检测到图像尺寸:" << imageSize << endl;
}
// 图像预处理(转换为灰度图)
processed = enhancedPreprocessing(frame);
// ================== 角点检测 ==================
vector<Point2f> corners; // 当前图像检测到的角点
// 第一阶段:粗检测棋盘格角点
bool success = findChessboardCornersSB(
processed,
Size(CHECKERBOARD_COLS, CHECKERBOARD_ROWS),
corners,
CALIB_CB_ACCURACY | CALIB_CB_EXHAUSTIVE
);
if (!success) {
success = findChessboardCornersSB(
processed,
Size(CHECKERBOARD_COLS, CHECKERBOARD_ROWS),
corners,
CALIB_CB_ACCURACY | CALIB_CB_EXHAUSTIVE | CALIB_CB_NORMALIZE_IMAGE
);
}
// 多阶段验证和优化
if (success) {
// 方向一致性验证(确保角点排列符合棋盘格结构)
vector<Vec2f> directions;
calcChessboardDirections(corners, Size(CHECKERBOARD_COLS, CHECKERBOARD_ROWS), directions); // 修正此处
success = validateDirectionConsistency(directions);
// 亚像素级精度优化(提高角点定位精度)
if (success) {
multiStageRefinement(processed, corners);
// 保存有效角点和对应的世界坐标
objPoints.push_back(objp);
imgPoints.push_back(corners);
validImageCount++;
}
// 可视化检测结果
Mat display;
cvtColor(processed, display, COLOR_GRAY2BGR); // 转换为BGR用于绘制颜色
drawChessboardCorners(display, Size(CHECKERBOARD_COLS, CHECKERBOARD_ROWS), corners, success); // 正确尺寸
putText(display, success ? "Detected" : "Failed", Point(20, 30),
FONT_HERSHEY_SIMPLEX, 1, success ? Scalar(0, 255, 0) : Scalar(0, 0, 255), 2);
// 显示进度信息
string status = format("图像 %zu/%zu [有效:%d]", i + 1, imagePaths.size(), validImageCount);
putText(display, status, Point(20, display.rows - 20),
FONT_HERSHEY_PLAIN, 1.5, Scalar(255, 255, 0), 2);
imshow("校准进度", display);
if (waitKey(500) == 27) break; // 按ESC键退出
}
// 销毁窗口(避免内存泄漏)
destroyAllWindows();
}
// ================== 标定验证 ==================
if (objPoints.size() < 5) { // 至少需要5张有效图像以保证标定精度
cerr << "\n错误:有效图像不足(需要至少5张,当前:" << objPoints.size() << ")" << endl;
return -1;
}
// ================== 执行相机标定 ==================
Mat cameraMatrix = Mat::eye(3, 3, CV_64F); // 初始内参矩阵(单位矩阵)
Mat distCoeffs = Mat::zeros(8, 1, CV_64F); // 畸变系数(8参数模型)
vector<Mat> rvecs, tvecs; // 旋转向量和平移向量(每个图像对应一组)
double rms = calibrateCamera(
objPoints, imgPoints, imageSize,
cameraMatrix, distCoeffs,
rvecs, tvecs,
calibrationFlags
);
cout << "\n===== 标定结果 =====" << endl;
cout << "RMS重投影误差: " << rms << " 像素" << endl;
cout << "内参矩阵:\n" << cameraMatrix << endl;
cout << "畸变系数: " << distCoeffs.t() << endl;
// ================== 计算投影误差 ==================
vector<vector<double>> reprojErrs = calculateReprojectionErrors(
objPoints, imgPoints, rvecs, tvecs, cameraMatrix, distCoeffs
);
// 总体误差统计
double totalError = 0.0, totalPoints = 0.0;
for (const auto& imgErrs : reprojErrs) {
for (double err : imgErrs) {
totalError += err * err;
totalPoints++;
}
}
double overallRmsError = sqrt(totalError / totalPoints);
cout << "\n===== 投影误差统计 =====" << endl;
cout << "总有效角点数: " << static_cast<int>(totalPoints) << endl;
cout << "总体RMS误差: " << fixed << setprecision(4) << overallRmsError << " 像素" << endl;
cout << "(该值应与calibrateCamera返回的RMS基本一致)" << endl;
// ================== 保存标定结果 ==================
string savePath = "D:/CameraCalibrationResults/calibration.yml";
try {
// 创建保存目录
string dirPath = savePath.substr(0, savePath.find_last_of("/\\"));
createDirectoryIfNotExists(dirPath);
// 写入YAML文件
FileStorage fs(savePath, FileStorage::WRITE);
if (!fs.isOpened()) throw runtime_error("文件创建失败,请检查路径权限");
// 基本信息
fs << "calibration_date" << getCurrentTimeString();
fs << "image_resolution" << format("%dx%d", imageSize.width, imageSize.height);
fs << "checkerboard_pattern" << format("%dx%d", CHECKERBOARD_COLS, CHECKERBOARD_ROWS);
fs << "square_size" << "12.0 mm";
// 内参矩阵(3x3)
fs << "camera_matrix" << "[";
for (int i = 0; i < 3; i++) {
fs << "[";
for (int j = 0; j < 3; j++) {
fs << cameraMatrix.at<double>(i, j) << " ";
}
fs << "] ";
}
fs << "]";
// 畸变系数(k1,k2,p1,p2,k3,k4,k5,k6)
fs << "distortion_coefficients" << "[";
for (int i = 0; i < 8; i++) {
fs << distCoeffs.at<double>(i) << " ";
}
fs << "]";
// 误差信息
fs << "rms_reprojection_error" << rms;
fs << "overall_reprojection_rms" << overallRmsError;
fs << "valid_image_count" << static_cast<int>(objPoints.size());
cout << "\n标定参数已成功保存至:" << savePath << endl;
}
catch (const exception& e) {
cerr << "\n保存失败:" << e.what() << endl;
return -1;
}
// ================== 去畸变示例 ==================
if (!imagePaths.empty()) {
Mat distorted = imread(imagePaths[0]);
Mat undistorted;
undistort(distorted, undistorted, cameraMatrix, distCoeffs);
imshow("原始图像", distorted);
imshow("去畸变图像", undistorted);
waitKey(0);
}
return 0;
}如何修改此代码能适合用于长焦的标定,并且误差尽可能的小
最新发布