OpenCV 相机标定流程指南

OpenCV 相机标定流程指南

https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html

https://learnopencv.com/camera-calibration-using-opencv/

前置准备

  1. 制作标定板:生成高精度棋盘格或圆点标定板。
  2. 采集标定板图像:在不同角度、距离和光照条件下采集多张标定板图像。

OpenCV 官方标定板生成脚本使用教程
!OpenCV 官方标定板脚本下载

访问我的源代码仓库下载已经生成的矢量棋盘网格,使用打印机打印出来即可进行图像标定采集工作。

标定流程

使用 CameraCalib 类进行相机标定:

  1. 添加图像样本:将采集的标定板图像导入标定系统。
  2. 并发检测角点:利用多线程技术并行检测图像中的角点或特征点。
  3. 相机标定:基于检测到的角点,计算相机内参(焦距、主点坐标)和外参(旋转矩阵、平移向量),并优化畸变系数。

结果输出与验证

  1. 打印标定结果:输出相机内参、外参及畸变系数。
  2. 测试图像标定:使用标定结果对测试图像进行畸变校正,验证标定精度。

建议

可信误差:重投影误差应小于 0.5 像素,最大不超过 1.0 像素。

采集夹角要求:摄像头与标定板平面的夹角应控制在 30°~60° 之间,避免极端角度。

1\] https://www.microsoft.com/en-us/research/publication/a-flexible-new-technique-for-camera-calibration/ ### 源代码 ```cpp #include #include #include #include #include #include #include class CameraCalib { public: // 校准模式 enum class Pattern : uint32_t { CALIB_SYMMETRIC_CHESSBOARD_GRID, // 规则排列的棋盘网格 // chessboard CALIB_MARKER_CHESSBOARD_GRID, // 额外标记的棋盘网格 // marker chessboard CALIB_SYMMETRIC_CIRCLES_GRID, // 规则排列的圆形网格 // circles CALIB_ASYMMETRIC_CIRCLES_GRID, // 交错排列的圆形网格 // acircles CALIB_PATTERN_COUNT, // 标定模式的总数量 用于 for 循环遍历 std::to_underlying(Pattern::CALIB_PATTERN_COUNT); }; struct CameraCalibrationResult { cv::Mat cameraMatrix; // 相机矩阵(内参数) cv::Mat distortionCoefficients; // 畸变系数 double reprojectionError; // 重投影误差(标定精度指标) std::vector rotationVectors; // 旋转向量(外参数) std::vector translationVectors; // 平移向量(外参数) }; explicit CameraCalib(int columns, int rows, double square_size /*mm*/, Pattern pattern) : patternSize_(columns, rows) , squareSize_(square_size) , pattern_(pattern) { // 构造一个与标定板对应的真实的世界角点数据 for(int y = 0; y < patternSize_.height; ++y) { for(int x = 0; x < patternSize_.width; ++x) { realCorners_.emplace_back(x * square_size, y * square_size, 0.0f); } } } void addImageSample(const cv::Mat &image) { samples_.emplace_back(image); } void addImageSample(const std::string &filename) { cv::Mat mat = cv::imread(filename, cv::IMREAD_COLOR); if(mat.empty()) { std::println(stderr, "can not load filename: {}", filename); return; } addImageSample(mat); } bool detectCorners(const cv::Mat &image, std::vector &corners) { bool found; switch(pattern_) { using enum Pattern; case CALIB_SYMMETRIC_CHESSBOARD_GRID: detectSymmetricChessboardGrid(image, corners, found); break; case CALIB_MARKER_CHESSBOARD_GRID: detectMarkerChessboardGrid(image, corners, found); break; case CALIB_SYMMETRIC_CIRCLES_GRID: detectSymmetricCirclesGrid(image, corners, found); break; case CALIB_ASYMMETRIC_CIRCLES_GRID: detectAsymmetricCirclesGrid(image, corners, found); break; default: break; } return found; } std::vector> detect() { std::vector> detectedCornerPoints; std::mutex mtx; // 使用 mutex 来保护共享资源 std::atomic count; std::for_each(samples_.cbegin(), samples_.cend(), [&](const cv::Mat &image) { std::vector corners; bool found = detectCorners(image, corners); if(found) { count++; std::lock_guard lock(mtx); // 使用 lock_guard 来保护共享资源 detectedCornerPoints.push_back(corners); } }); std::println("Detection successful: {} corners, total points: {}", int(count), detectedCornerPoints.size()); return detectedCornerPoints; } std::unique_ptr calib(std::vector> detectedCornerPoints, int width, int height) { // 准备真实角点的位置 std::vector> realCornerPoints; for(size_t i = 0; i < detectedCornerPoints.size(); ++i) { realCornerPoints.emplace_back(realCorners_); } cv::Size imageSize(width, height); // 初始化相机矩阵和畸变系数 cv::Mat cameraMatrix = cv::Mat::eye(3, 3, CV_64F); cv::Mat distCoeffs = cv::Mat::zeros(5, 1, CV_64F); std::vector rvecs, tvecs; // 进行相机标定 double reproError = cv::calibrateCamera(realCornerPoints, detectedCornerPoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs, cv::CALIB_FIX_K1 + cv::CALIB_FIX_K2 + cv::CALIB_FIX_K3 + cv::CALIB_FIX_K4 + cv::CALIB_FIX_K5); // 将标定结果存储到结构体中 auto result = std::make_unique(); result->cameraMatrix = cameraMatrix; result->distortionCoefficients = distCoeffs; result->reprojectionError = reproError; result->rotationVectors = rvecs; result->translationVectors = tvecs; return result; } // 打印标定结果 void print(const std::unique_ptr &result) { std::cout << "重投影误差: " << result->reprojectionError << std::endl; std::cout << "相机矩阵:\n" << result->cameraMatrix << std::endl; std::cout << "畸变系数:\n" << result->distortionCoefficients << std::endl; } // 进行畸变校正测试 void test(const std::string &filename, const std::unique_ptr ¶m) { // 读取一张测试图像 cv::Mat image = cv::imread(filename); if(image.empty()) { std::println("can not load filename"); return; } cv::Mat undistortedImage; cv::undistort(image, undistortedImage, param->cameraMatrix, param->distortionCoefficients); // 显示原图和校准后的图 cv::namedWindow("Original Image", cv::WINDOW_NORMAL); cv::namedWindow("Undistorted Image", cv::WINDOW_NORMAL); cv::imshow("Original Image", image); cv::imshow("Undistorted Image", undistortedImage); // 等待用户输入任意键 cv::waitKey(0); } private: void dbgView(const cv::Mat &image, const std::vector &corners, bool &found) { if(!found) { std::println("Cannot find corners in the image"); } // Debug and view detected corner points in images if constexpr(false) { cv::drawChessboardCorners(image, patternSize_, corners, found); cv::namedWindow("detectCorners", cv::WINDOW_NORMAL); cv::imshow("detectCorners", image); cv::waitKey(0); cv::destroyAllWindows(); } } void detectSymmetricChessboardGrid(const cv::Mat &image, std::vector &image_corners, bool &found) { if(found = cv::findChessboardCorners(image, patternSize_, image_corners); found) { cv::Mat gray; cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); cv::cornerSubPix(gray, image_corners, cv::Size(11, 11), cv::Size(-1, -1), cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.01)); dbgView(image, image_corners, found); } } void detectMarkerChessboardGrid(const cv::Mat &image, std::vector &image_corners, bool &found) { if(found = cv::findChessboardCornersSB(image, patternSize_, image_corners); found) { dbgView(image, image_corners, found); } } void detectSymmetricCirclesGrid(const cv::Mat &image, std::vector &image_corners, bool &found) { if(found = cv::findCirclesGrid(image, patternSize_, image_corners, cv::CALIB_CB_SYMMETRIC_GRID); found) { cv::Mat gray; cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); cv::cornerSubPix(gray, image_corners, cv::Size(11, 11), cv::Size(-1, -1), cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.01)); dbgView(image, image_corners, found); } } void detectAsymmetricCirclesGrid(const cv::Mat &image, std::vector &image_corners, bool &found) { cv::SimpleBlobDetector::Params params; params.minThreshold = 8; params.maxThreshold = 255; params.filterByArea = true; params.minArea = 50; // 适当降低,以便检测小圆点 params.maxArea = 5000; // 适当降低,以避免误检大区域 params.minDistBetweenBlobs = 10; // 调小以适应紧密排列的圆点 params.filterByCircularity = false; // 允许更圆的形状 params.minCircularity = 0.7; // 只有接近圆的目标才被识别 params.filterByConvexity = true; params.minConvexity = 0.8; // 只允许较凸的形状 params.filterByInertia = true; params.minInertiaRatio = 0.1; // 适应不同形状 params.filterByColor = false; // 关闭颜色过滤,避免黑白检测问题 auto blobDetector = cv::SimpleBlobDetector::create(params); if(found = cv::findCirclesGrid(image, patternSize_, image_corners, cv::CALIB_CB_ASYMMETRIC_GRID | cv::CALIB_CB_CLUSTERING, blobDetector); found) { cv::Mat gray; cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); cv::cornerSubPix(gray, image_corners, cv::Size(11, 11), cv::Size(-1, -1), cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.01)); dbgView(image, image_corners, found); } } private: cv::Size patternSize_; double squareSize_; Pattern pattern_; std::vector realCorners_; std::vector samples_; }; // 测试函数 static void test_CameraCalib() { // 创建一个 CameraCalib 对象,指定标定板大小、每个方格的边长和校准模式 CameraCalib calib(14, 9, 12.1, CameraCalib::Pattern::CALIB_MARKER_CHESSBOARD_GRID); // 加载图像样本 std::vector result; cv::glob("calibration_images/*.png", result, false); for (auto &&filename : result) { calib.addImageSample(filename); } // 检测角点 auto detectedCornerPoints = calib.detect(); // 进行相机标定 std::string filename = "calibration_images/checkerboard_radon.png"; cv::Mat image = cv::imread(filename); if (image.empty()) { std::println("can not load image"); return; } auto param = calib.calib(detectedCornerPoints, image.cols, image.cols); // 打印标定结果 calib.print(param); // 测试函数 calib.test(filename, param); } ``` 运行测试函数,输出结果如下所示: Detection successful: 2 corners, total points: 2 重投影误差: 0.0373256 相机矩阵: [483030.3184975122, 0, 1182.462802265994; 0, 483084.13533141, 1180.358683128085; 0, 0, 1] 畸变系数: [0; 0; -0.002454905573938355; 9.349667940808669e-05; 0] // 保存标定结果 cv::FileStorage fs("calibration_result.yml", cv::FileStorage::WRITE); fs << "camera_matrix" << result.cameraMatrix; fs << "distortion_coefficients" << result.distCoeffs; fs << "image_size" << result.imageSize; fs.release();

相关推荐
lboyj1 小时前
新能源汽车电控系统的大尺寸PCB需求:猎板PCB的技术突围
大数据·网络·人工智能
HABuo1 小时前
【YOLOv8】YOLOv8改进系列(5)----替换主干网络之EfficientFormerV2
人工智能·深度学习·yolo·目标检测·计算机视觉
訾博ZiBo2 小时前
AI日报 - 2025年3月16日
人工智能
(initial)2 小时前
大型语言模型与强化学习的融合:迈向通用人工智能的新范式——基于基础复现的实验平台构建
人工智能·强化学习
subject625Ruben2 小时前
Matlab多种算法解决未来杯B的多分类问题
人工智能·算法·机器学习·数学建模·matlab·分类·未来杯
Liudef063 小时前
文生图技术的演进、挑战与未来:一场重构人类创造力的革命
人工智能·stable diffusion·重构
RestCloud3 小时前
AI大模型本地化&谷云科技全域集成能力重构企业数智化生态
人工智能·ai·数智化·智能体·aiagent·deepseek·集成平台
小和尚同志3 小时前
Trae 重构的笔记应用完成啦~
人工智能·aigc
二川bro3 小时前
模拟类似 DeepSeek 的对话
前端·人工智能
Elastic 中国社区官方博客3 小时前
Elasticsearch:语义文本 - 更简单、更好、更精炼、更强大 8.18
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索