当前位置: 首页 > news >正文

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();

相关文章:

OpenCV 相机标定流程指南

OpenCV 相机标定流程指南 前置准备标定流程结果输出与验证建议源代码 OpenCV 相机标定流程指南 https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html https://learnopencv.com/camera-calibration-using-opencv/ 前置准备 制作标定板&#xff1a;生成高精度棋…...

项目场景拷打

补偿事务解决超卖 通过补偿事务避免超卖问题&#xff0c;可以通过以下几种方式实现&#xff1a; 1. 使用数据库事务与锁机制 事务管理&#xff1a;将库存扣减和订单生成操作放在同一个数据库事务中&#xff0c;确保操作的原子性。如果事务中任何一个步骤失败&#xff0c;则整…...

Vue2生命周期面试题

在 Vue 2 中&#xff0c;this.$el 和 this.$data 都是 Vue 实例的属性&#xff0c;代表不同的内容。 1. this.$el this.$el 是 Vue 实例的根 DOM 元素&#xff0c;它指向 Vue 实例所控制的根节点元素。在 Vue 中&#xff0c;el 是在 Vue 实例创建时&#xff0c;指定的根元素&…...

【每日一题 | 2025】2.3 ~ 2.9

个人主页&#xff1a;GUIQU. 归属专栏&#xff1a;每日一题 文章目录 1. 【2.3】P8784 [蓝桥杯 2022 省 B] 积木画2. 【2.4】P8656 [蓝桥杯 2017 国 B] 对局匹配3. 【2.5】[ABC365D] AtCoder Janken 34. 【2.6】P8703 [蓝桥杯 2019 国 B] 最优包含5. 【2.7】P8624 [蓝桥杯 2015…...

使用OpenGL自己定义一个button,响应鼠标消息:掠过、点击、拖动

button需要有一个外观 外观 大小跟随窗口改变&#xff0c;采用纯色背景、纯色文字 文字 大小跟随窗口改变 button需要获得鼠标消息 掠过 鼠标掠过时 button 出现阴影&#xff0c;鼠标掠过后 button 阴影消失 点击 点击后进入相应事件 拖动 改变图标所在位置 需要在g…...

C# 上位机--变量

C# 上位机--变量 在 C# 上位机开发领域&#xff0c;变量是构建程序逻辑的基础元素之一。它就像是一个容器&#xff0c;用于存储各种类型的数据&#xff0c;从简单的数值到复杂的对象。正确理解和使用变量&#xff0c;对于开发出高效、稳定且易于维护的上位机程序至关重要。本文…...

网络安全检查漏洞内容回复 网络安全的漏洞

的核心目标是保障业务系统的可持续性和数据的安全性&#xff0c;而这两点的主要威胁来自于蠕虫的暴发、黑客的攻击、拒绝服务攻击、木马。蠕虫、黑客攻击问题都和漏洞紧密联系在一起&#xff0c;一旦有重大安全漏洞出现&#xff0c;整个互联网就会面临一次重大挑战。虽然传统木…...

【GIS】本地部署nominatim地理编码服务

参考&#xff1a;https://www.cnblogs.com/nonkicat/p/17222677.html docker 部署命令 4.5 版本 docker 用不了&#xff0c;需要用 4.0 版本 docker run -it -e PBF_PATH/data/你的osm文件.osm.pbf -e FREEZEtrue -e POSTGRES_MAX_CONNECTIONS100 -p 6666:8080 --…...

HTML之JavaScript对象声明

HTML之JavaScript对象声明 常用&#xff1a;方式1&#xff1a;new Object() 创建一个空对象方式2&#xff1a;{属性名:属性值,属性名:属性值,...函数名:function(){}} 创建一个对象<!DOCTYPE html> <html lang"en"> <head><meta charset&quo…...

PyCharm结合DeepSeek-R1

PyCharm结合DeepSeek-R1&#xff0c;打造专属 AI 编程助手 在程序员的日常工作中&#xff0c;提高编程效率、快速解决代码问题是重中之重。今天给大家分享一个强强联合的组合 ——PyCharm 插件 Continue 与 DeepSeek-R1&#xff0c;它们能帮你打造出强大的个人 AI 编程助手。 …...

innovus如何分步长func和dft时钟

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

1.1 单元测试核心原则

单元测试核心原则 单元测试是软件质量保障的第一道防线&#xff0c;其核心目标是通过最小粒度的测试验证代码逻辑的正确性。以下是编写高质量单元测试必须遵循的六大原则&#xff0c;结合Mockito的应用场景进行解析&#xff1a; 1. 快速反馈&#xff08;Fast&#xff09; 原则…...

前端智能识别解析粘贴板内容

原理分析 说白了就是解析特定格式的文字&#xff0c;并将处理好的内容回填到需要的表单中。 为了程序的健壮性&#xff0c;我们解析时需要考虑多种情况。 1、文字行数 单行和多行的解析可以分开 单行的情况如下面这种&#xff0c; 姓名: 七七 电话&#xff1a;788 邮箱&…...

AI工具发展全景分析与战略展望

AI工具发展全景分析与战略展望 本文基于本人最近整理并开发的AI工具推荐平台软件及相关的资料信息整理。 一、产业现状深度解析 &#xff08;一&#xff09;市场格局三维透视 #mermaid-svg-YLeCfJwoWDOd32wZ {font-family:"trebuchet ms",verdana,arial,sans-seri…...

(定时器,绘制事件,qt简单服务器的搭建)2025.2.11

作业 笔记&#xff08;复习补充&#xff09; 1> 制作一个闹钟软件 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> //按钮类 #include <QTimer> //定时器类 #include <QTime> //…...

C++17十大常用特性

玩转cpp小项目星球3周年了&#xff01; 今天分享两个知识点&#xff1a; C17我常用的十大特性。git am与git apply对patch处理的不同。...

【机器学习】超参数的选择,以kNN算法为例

分类准确度 一、摘要二、超参数的概念三、调参的方法四、实验搜索超参数五、扩展搜索范围六、考虑距离权重的kNN算法七、距离的计算方法及代码实现八、明可夫斯基距离的应用九、网格搜索超参数 一、摘要 本博文讲解了机器学习中的超参数问题&#xff0c;以K近邻算法为例&#…...

【RabbitMQ的监听器容器Simple和Direct】 实现和场景区别

在Spring Boot中&#xff0c;RabbitMQ的两种监听器容器&#xff08;SimpleMessageListenerContainer和DirectMessageListenerContainer&#xff09;在实现机制和使用场景上有显著差异。以下是它们的核心区别、配置方式及最佳实践&#xff1a; Simple类型 Direct类型 一、核心…...

NO.13十六届蓝桥杯备战|条件操作符|三目操作符|逻辑操作符|!||||(C++)

条件操作符 条件操作符介绍 条件操作符也叫三⽬操作符&#xff0c;需要接受三个操作数的&#xff0c;形式如下&#xff1a; exp1 ? exp2 : exp3条件操作符的计算逻辑是&#xff1a;如果 exp1 为真&#xff0c; exp2 计算&#xff0c; exp2 计算的结果是整个表达式的结果&am…...

2025.1.8(qt图形化界面之消息框)

笔记&#xff08;后期复习补充&#xff09; 作业 1> 手动将登录项目实现&#xff0c;不要使用拖拽编程 并且&#xff0c;当点击登录按钮时&#xff0c;后台会判断账号和密码是否相等&#xff0c;如果相等给出登录成功的提示&#xff0c;并且关闭当前界面&#xff0c;发射一…...

旅游行业内容管理系统CMS提升网站建设效率与体验

内容概要 在如今快速发展的互联网时代&#xff0c;旅游行业对网站的要求越来越高&#xff0c;内容管理系统&#xff08;CMS&#xff09;的应用不可或缺。以 Baklib 为代表的先进CMS可显著提高旅游网站的建设效率与用户体验。为了满足不断变化的市场需求&#xff0c;这些系统通…...

使用 Scrapy 抓取网页数据

1. Scrapy 简介 Scrapy 是一个流行的 Python 爬虫框架&#xff0c;提供了强大的工具和灵活的扩展机制&#xff0c;用于高效抓取和处理网页数据。它支持异步 I/O&#xff0c;速度快且资源消耗低&#xff0c;非常适合大规模爬取任务。 2. 安装 Scrapy 确保你的 Python 环境版本…...

C# OpenCV机器视觉:SoftNMS非极大值抑制

嘿&#xff0c;你知道吗&#xff1f;阿强最近可忙啦&#xff01;他正在处理一个超级棘手的问题呢&#xff0c;就好像在一个混乱的战场里&#xff0c;到处都是乱糟糟的候选框&#xff0c;这些候选框就像一群调皮的小精灵&#xff0c;有的重叠在一起&#xff0c;让阿强头疼不已。…...

kamailio关于via那点事

如果kamailio作为代理服务器&#xff0c;在转到目的路由时 不删除原始的via信息 会造成信息泄露 如果 Kamailio 作为代理服务器&#xff08;SIP Proxy&#xff09;在转发 SIP 请求时不删除原始的 Via 信息&#xff0c;这确实可能会造成信息泄露。 &#x1f4cc; 为什么不删除 …...

[MFC] 使用控件

介绍如何使用控件&#xff0c;以及如何获取控件中的数值 check Box 添加点击事件&#xff0c;即选中和取消选中触发的事件 第一种方式是按照如下方式第二种方式是直接双击点击进去 void CMFCApplication1Dlg::OnBnClickedCheckSun() {// TODO: 在此添加控件通知处理程序代…...

【探索未来科技】2025年国际学术会议前瞻

【探索未来科技】2025年国际学术会议前瞻 【探索未来科技】2025年国际学术会议前瞻 文章目录 【探索未来科技】2025年国际学术会议前瞻前言1. 第四届电子信息工程、大数据与计算机技术国际学术会议&#xff08; EIBDCT 2025&#xff09;代码示例&#xff1a;机器学习中的线性回…...

使用wpa_supplicant和wpa_cli 扫描wifi热点及配网

一&#xff1a;简要说明 交叉编译wpa_supplicant工具后会有wpa_supplicant和wpa_cli两个程序生产&#xff0c;如果知道需要连接的wifi热点及密码的话不需要遍历及查询所有wifi热点的名字及信号强度等信息的话&#xff0c;使用wpa_supplicant即可&#xff0c;否则还需要使用wpa_…...

Sealos的k8s高可用集群搭建

Sealos 介绍](https://sealos.io/zh-Hans/docs/Intro) Sealos 是一个 Go 语言开发的简单干净且轻量的 Kubernetes 集群部署工具&#xff0c;能很好的支持在生产环境中部署高可用的 Kubernetes 集群。 Sealos 特性与优势 支持离线安装&#xff0c;工具与部署资源包分离&#…...

Android和DLT日志系统

1 Linux Android日志系统 1.1 内核logger机制 drivers/staging/android/logger.c static size_t logger_offset( struct logger_log *log, size_t n) { return n & (log->size - 1); } 写的off存在logger_log中&#xff08;即内核内存buffer&#xff09;&am…...

【openresty服务器】:源码编译openresty支持ssl,增加service系统服务,开机启动,自己本地签名证书,配置https访问

1&#xff0c;openresty 源码安装&#xff0c;带ssl模块 https://openresty.org/cn/download.html &#xff08;1&#xff09;PCRE库 PCRE库支持正则表达式。如果我们在配置文件nginx.conf中使用了正则表达式&#xff0c;那么在编译Nginx时就必须把PCRE库编译进Nginx&#xf…...