OpenCV 相机标定流程指南

  • 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/

源代码

#include <opencv2/opencv.hpp>
#include <algorithm>
#include <memory>
#include <vector>
#include <string>
#include <print>
#include <iostream>class CameraCalib
{
public:// 校准模式enum class Pattern : uint32_t {CALIB_SYMMETRIC_CHESSBOARD_GRID,  // 规则排列的棋盘网格 // chessboardCALIB_MARKER_CHESSBOARD_GRID,     // 额外标记的棋盘网格 // marker chessboardCALIB_SYMMETRIC_CIRCLES_GRID,     // 规则排列的圆形网格 // circlesCALIB_ASYMMETRIC_CIRCLES_GRID,    // 交错排列的圆形网格 // acirclesCALIB_PATTERN_COUNT,              // 标定模式的总数量 用于 for 循环遍历 std::to_underlying(Pattern::CALIB_PATTERN_COUNT);};struct CameraCalibrationResult {cv::Mat cameraMatrix;                     // 相机矩阵(内参数)cv::Mat distortionCoefficients;           // 畸变系数double reprojectionError;                 // 重投影误差(标定精度指标)std::vector<cv::Mat> rotationVectors;     // 旋转向量(外参数)std::vector<cv::Mat> 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<cv::Point2f> &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<std::vector<cv::Point2f>> detect() {std::vector<std::vector<cv::Point2f>> detectedCornerPoints;std::mutex mtx;  // 使用 mutex 来保护共享资源std::atomic<int> count;std::for_each(samples_.cbegin(), samples_.cend(), [&](const cv::Mat &image) {std::vector<cv::Point2f> corners;bool found = detectCorners(image, corners);if(found) {count++;std::lock_guard<std::mutex> lock(mtx);  // 使用 lock_guard 来保护共享资源detectedCornerPoints.push_back(corners);}});std::println("Detection successful: {} corners, total points: {}", int(count), detectedCornerPoints.size());return detectedCornerPoints;}std::unique_ptr<CameraCalibrationResult> calib(std::vector<std::vector<cv::Point2f>> detectedCornerPoints, int width, int height) {// 准备真实角点的位置std::vector<std::vector<cv::Point3f>> 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<cv::Mat> 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<CameraCalibrationResult>();result->cameraMatrix           = cameraMatrix;result->distortionCoefficients = distCoeffs;result->reprojectionError      = reproError;result->rotationVectors        = rvecs;result->translationVectors     = tvecs;return result;}// 打印标定结果void print(const std::unique_ptr<CameraCalibrationResult> &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<CameraCalibrationResult> &param) {// 读取一张测试图像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<cv::Point2f> &corners, bool &found) {if(!found) {std::println("Cannot find corners in the image");}// Debug and view detected corner points in imagesif 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<cv::Point2f> &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<cv::Point2f> &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<cv::Point2f> &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<cv::Point2f> &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<cv::Point3f> realCorners_;std::vector<cv::Mat> samples_;
};// 测试函数
static void test_CameraCalib() {// 创建一个 CameraCalib 对象,指定标定板大小、每个方格的边长和校准模式CameraCalib calib(14, 9, 12.1, CameraCalib::Pattern::CALIB_MARKER_CHESSBOARD_GRID);// 加载图像样本std::vector<cv::String> 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();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/69734.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

没有服务器和显卡电脑如何本地化使用deepseek|如何通过API使用满血版deepseek

目录 一、前言二、使用siliconflow硅基流动 API密钥1、注册硅基流动2、创建API密钥3、下载AI客户端4、使用API密钥5、效果演示 三、使用deepseek官方API密钥1、创建API密钥2、使用API密钥3、效果演示 四、总结 一、前言 上篇文章我介绍了如何通过云服务器或者显卡电脑来本地化…

python+unity落地方案实现AI 换脸融合

先上效果再说技术结论&#xff0c;使用的是自行搭建的AI人脸融合库&#xff0c;可以离线不受限制无限次生成&#xff0c;有需要的可以后台私信python ai换脸融合。 TODO 未来的方向&#xff1a;3D人脸融合和AI数据训练 这个技术使用的是openvcinsighface&#xff0c;openvc…

windows + visual studio 2019 使用cmake 编译构建静、动态库并调用详解

环境 windows visual studio 2019 visual studio 2019创建cmake工程 1. 静态库.lib 1.1 静态库编译生成 以下是我创建的cmake工程文件结构&#xff0c;只关注高亮文件夹部分 libout 存放编译生成的.lib文件libsrc 存放编译用的源代码和头文件CMakeLists.txt 此次编译CMak…

【前端】几种常见的跨域解决方案代理的概念

几种常见的跨域解决方案&代理的概念 一、常见的跨域解决方案1. 服务端配置CORS&#xff08;Cross-Origin Resource Sharing&#xff09;&#xff1a;2. Nginx代理3. Vue CLI配置代理&#xff1a;4 .uni-app在manifest.json中配置代理来解决&#xff1a;5. 使用WebSocket通讯…

Git 分布式版本控制工具使用教程

1.关于Git 1.1 什么是Git Git是一款免费、开源的分布式版本控制工具&#xff0c;由Linux创始人Linus Torvalds于2005年开发。它被设计用来处理从很小到非常大的项目&#xff0c;速度和效率都非常高。Git允许多个开发者几乎同时处理同一个项目而不会互相干扰&#xff0c;并且在…

基于java手机销售网站设计和实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

GitHub Pages + Jekyll 博客搭建指南(静态网站搭建)

目录 &#x1f680; 静态网站及其生成工具指南&#x1f30d; 什么是静态网站&#xff1f;&#x1f4cc; 静态网站的优势⚖️ 静态网站 VS 动态网站 &#x1f680; 常见的静态网站生成器对比&#x1f6e0;️ 使用 GitHub Pages Jekyll 搭建个人博客&#x1f4cc; 1. 创建 GitHu…

1.【线性代数】——方程组的几何解释

一 方程组的几何解释 概述举例举例一1. matrix2.row picture3.column picture 概述 三种表示方法 matrixrow picturecolumn picture 举例 举例一 { 2 x − y 0 − x 2 y 3 \begin{cases} 2x - y 0 \\ -x 2y 3 \end{cases} {2x−y0−x2y3​ 1. matrix [ 2 − 1 − 1 …

ZZNUOJ(C/C++)基础练习1091——1100(详解版)⭐

目录 1091 : 童年生活二三事&#xff08;多实例测试&#xff09; C C 1092 : 素数表(函数专题&#xff09; C C 1093 : 验证哥德巴赫猜想&#xff08;函数专题&#xff09; C C 1094 : 统计元音&#xff08;函数专题&#xff09; C C 1095 : 时间间隔&#xff08;多…

innovus如何分步长func和dft时钟

在Innovus工具中&#xff0c;分步处理功能时钟&#xff08;func clock&#xff09;和DFT时钟&#xff08;如扫描测试时钟&#xff09;需要结合设计模式&#xff08;Function Mode和DFT Mode&#xff09;进行约束定义、时钟树综合&#xff08;CTS&#xff09;和时序分析。跟随分…

java高级知识之集合

前言 集合是java开发中的重点内容&#xff0c;需要掌握的东西很多&#xff0c;面试中可问的东西很多&#xff0c;无论是深度还是广度。集合框架中Collection对应的实现类如下所示&#xff0c;这些都是要完全掌握&#xff0c;一个可以分为三大类List集合、Set‘集合以及Map集合…

51c自动驾驶~合集49

我自己的原文哦~ https://blog.51cto.com/whaosoft/13164876 #Ultra-AV 轨迹预测新基准&#xff01;清华开源&#xff1a;统一自动驾驶纵向轨迹数据集 自动驾驶车辆在交通运输领域展现出巨大潜力&#xff0c;而理解其纵向驾驶行为是实现安全高效自动驾驶的关键。现有的开…

Unity-Mirror网络框架-从入门到精通之MultipleMatches示例

文章目录 前言MultipleMatchesLobbyViewRoomViewMatchGUIPlayerGUI总结前言 在现代游戏开发中,网络功能日益成为提升游戏体验的关键组成部分。本系列文章将为读者提供对Mirror网络框架的深入了解,涵盖从基础到高级的多个主题。Mirror是一个用于Unity的开源网络框架,专为多人…

VMware Workstation创建虚拟机

目录 创建新的虚拟机 虚拟机快照功能 虚拟机添加空间 其他注意事项 创建新的虚拟机 打开VMware Workstation&#xff1a;启动软件后&#xff0c;点击“创建新的虚拟机”。 选择安装方式&#xff1a; 典型安装&#xff1a;适合大多数用户&#xff0c;会自动完成大部分配置…

DeepSeek AI R1推理大模型API集成文档

DeepSeek AI R1推理大模型API集成文档 引言 随着自然语言处理技术的飞速发展&#xff0c;大语言模型在各行各业的应用日益广泛。DeepSeek R1作为一款高性能、开源的大语言模型&#xff0c;凭借其强大的文本生成能力、高效的推理性能和灵活的接口设计&#xff0c;吸引了大量开发…

活泼瘤胃球菌(Ruminococcus gnavus)——多种疾病风险的潜在标志物

​ 前几日&#xff0c;南方医科大学深圳医院院长周宏伟教授团队在国际顶尖医学期刊《Nature Medicine》上发表了一项重要研究。首次揭示一种名为活泼瘤胃球菌(Ruminococcus gnavus)的细菌产生的物质——苯乙胺&#xff0c;在肝性脑病发生中的关键作用。 ​ 同时谷禾的人群检测数…

8.flask+websocket

http是短连接&#xff0c;无状态的。 websocket是长连接&#xff0c;有状态的。 flask中使用websocket from flask import Flask, request import asyncio import json import time import websockets from threading import Thread from urllib.parse import urlparse, pars…

qiime2:安装与使用

试一下docker安装 docker pull quay.io/qiime2/amplicon:2024.10 docker images docker run -v {挂载的目录}:/data quay.io/qiime2/amplicon:2024.10 qiime -h使用 import.txt docker run -v ~/diarrhoea/MJ/qingzhu:/data quay.io/qiime2/amplicon:2024.10 qiime tools imp…

技术实战|ELF 2学习板本地部署DeepSeek-R1大模型的完整指南(一)

DeepSeek作为国产AI大数据模型的代表&#xff0c;凭借其卓越的推理能力和高效的文本生成技术&#xff0c;在全球人工智能领域引发广泛关注。DeepSeek-R1作为该系列最新迭代版本&#xff0c;实现了长文本处理效能跃迁、多模态扩展规划、嵌入式适配等技术维度的突破。 RK3588作为…

DeepSeek本地部署_桌面版AnythingLLM本地知识库搭建

一.DeepSeek本地部署 1.下载并安装&#xff1a;ollama Download Ollama on macOSDownload Ollama for macOShttps://ollama.com/download 安装是否成功确认&#xff0c;管理员权限运行PowerShell&#xff1a; ollama -h 2.下载安装DeepSeek 管理员方式运行PowerShell&#…