4.从单应矩阵中分离得到内参和外参(需要拍摄n>=3张标定图片)

本文解析了优快云上一篇关于算法标注工程师职责的博客,深入探讨了算法标注流程与技巧,为读者提供了宝贵的行业洞见。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#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); // 计算正交性(理想情况下rightdown向量应垂直) 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; }如何修改此代码能适合用于长焦的标定,并且误差尽可能的小
最新发布
05-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值