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

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;生成高精度棋…...

redis专栏解读

本篇起导读、目录的作用&#xff0c;介绍redis专栏涉及的内容以及目录。 redis是我们日常开发中常用的NOSQL数据库&#xff0c;本专栏主要讲解redis的内部实现原理&#xff0c;不会侧重于API的使用&#xff0c;遇到API的使用会简单概括。本专栏大致会分为基础部分、redis内部实…...

网络在线考试|基于vue的网络在线考试系统的设计与实现(源码+数据库+文档)

网络在线考试系统 目录 基于SSM&#xff0b;vue的网络在线考试系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1功能页面实现 2系统功能模块 3管理员功能模块 4学生功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八…...

Spring Boot从入门到精通:核心知识点+实战指南

目录 一、Spring Boot 是什么&#xff1f;为什么它如此流行&#xff1f; 二、快速创建你的第一个Spring Boot应用 2.1 使用Spring Initializr生成项目 2.2 核心代码示例 三、深度解析Spring Boot核心机制 3.1 自动配置原理揭秘 3.2 自定义Starter实战 四、生产环境必备…...

DEEPSEEK与GPT等AI技术在机床数据采集与数字化转型中的应用与影响

随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;深度学习、自然语言处理等先进技术开始广泛应用于各行各业。在制造业尤其是机床行业&#xff0c;AI技术的融合带来了巨大的变革&#xff0c;尤其在机床数据采集与机床数字化方面的应用。本文将探讨DEEPSEEK、…...

【文本处理】如何在批量WORD和txt文本提取手机号码,固话号码,提取邮箱,删除中文,删除英文,提取车牌号等等一些文本提取固定格式的操作,基于WPF的解决方案

企业的应用场景 数据清洗&#xff1a;在进行数据导入或分析之前&#xff0c;往往需要对大量文本数据进行预处理&#xff0c;比如去除文本中的无关字符&#xff08;中文、英文&#xff09;&#xff0c;只保留需要的联系信息&#xff08;手机号码、固话号码、邮箱&#xff09;。…...

Docker从入门到精通- 容器化技术全解析

第一章&#xff1a;Docker 入门 一、什么是 Docker&#xff1f; Docker 就像一个超级厉害的 “打包神器”。它能帮咱们把应用程序和它运行所需要的东东都整整齐齐地打包到一起&#xff0c;形成一个独立的小盒子&#xff0c;这个小盒子在 Docker 里叫容器。以前呢&#xff0c;…...

17vue3实战-----使用配置文件生成简易页面

17vue3实战-----使用配置文件生成简易页面 1.写在前面2.背景3.实现3.1界面效果3.2新建config配置文件3.3封装组件3.4使用组件 1.写在前面 后台管理系统的开发很简单。无论是用户模块、部门模块、角色模块还是其它模块,界面和业务逻辑都相对比较简单&#xff0c;我会省略这些模…...

计算机视觉的研究方向、发展历程、发展前景介绍

以下将分别从图像分类、目标检测、语义分割、图像分割&#xff08;此处应主要指实例分割&#xff09;四个方面&#xff0c;为你介绍研究生人工智能计算机视觉领域的应用方向、发展历程以及发展前景。 文章目录 1.图像分类应用方向发展历程发展前景 2.目标检测应用方向发展历程…...

“mysqld --initialize --console ”执行不成功情况总结和解决措施

我的MYSQL版本是9.0.1出现类似下列的报错&#xff1a; 2024-10-29T01:09:55.942951Z 0 [System] [MY-015017] [Server] MySQL Server Initialization - start. 2024-10-29T01:09:55.950379Z 0 [Warning] [MY-010915] [Server] NO_ZERO_DATE, NO_ZERO_IN_DATE and ERROR_FOR_DIV…...

深入探索现代CSS:从基础到未来趋势

引言&#xff1a;CSS的进化之路 CSS&#xff08;层叠样式表&#xff09;自1996年诞生以来&#xff0c;已从简单的样式描述语言发展为构建现代Web体验的核心技术。截至2023年&#xff0c;超过98%的网站使用CSS3技术&#xff0c;其发展历程见证了Web从静态文档到富交互应用的蜕变…...

STM32 Unix时间戳

Unix时间戳 Unix 时间戳&#xff08;Unix Timestamp&#xff09;定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数&#xff0c;不考虑闰秒 时间戳存储在一个秒计数器中&#xff0c;秒计数器为32位/64位的整型变量 世界上所有时区的秒计数器相同&#xff0c;不同时区通过…...

SpringSecurity高级用法

SpringSecurity的高级用法&#xff0c;包括自定义loginUrl携带参数&#xff0c;自定义认证校验逻辑&#xff0c;自定义权限校验逻辑。 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-advanced-security 一、概述 在项目实际开发过程中&#xff0c;Spr…...

qwen2.5-vl-7B视觉大模型 私有化部署webUI

服务器选用&#xff1a;算力云 部署qwen2.5-vl-7B&#xff0c;24g显卡跑不起图&#xff0c;单问问题就占20g左右。有能力可以用大点的显卡 一、下载模型 Qwen2.5-VL-7B-Instruct 有conda &#xff0c;可以在conda下操作&#xff0c;不知道conda的同学可以参考本博主之前的文章…...

java安全中的类加载

java安全中的类加载 提前声明: 本文所涉及的内容仅供参考与教育目的&#xff0c;旨在普及网络安全相关知识。其内容不代表任何机构、组织或个人的权威建议&#xff0c;亦不构成具体的操作指南或法律依据。作者及发布平台对因使用本文信息直接或间接引发的任何风险、损失或法律纠…...

如何在Windows中配置MySQL?

MySQL是一个广泛使用的开源关系型数据库管理系统&#xff0c;它支持多种操作系统平台&#xff0c;其中包括Windows。无论是开发者进行本地开发&#xff0c;还是管理员为应用程序配置数据库&#xff0c;MySQL都是一个非常流行的选择。本篇文章将详细介绍如何在Windows操作系统中…...

Docker Desktop 镜像源配置

1 打开配置页面 2 docker engine 镜像配置位置 3、替换镜像内容 {"registry-mirrors": ["https://hub-mirror.c.163.com","https://mirror.ccs.tencentyun.com","https://05f073ad3c0010ea0f4bc00b7105ec20.mirror.swr.myhuaweicloud.c…...

125,【1】攻防世界unserialize3

进入靶场 代码 <?php // 定义一个名为 xctf 的类 class xctf {// 定义一个公共属性 $flag&#xff0c;初始值为字符串 111public $flag 111;// 定义 __wakeup() 魔术方法// 当使用 unserialize() 函数反序列化对象时&#xff0c;会自动调用 __wakeup() 方法// 在这个方法…...

2025年数据资产管理解决方案:资料合集,从基础知识到行业应用的全面解析

在数字化时代&#xff0c;数据已成为企业最宝贵的资产之一。如何有效地管理和利用这些数据&#xff0c;将其转化为实际的经济价值&#xff0c;已成为企业面临的重要课题。 本文将通过数据资产解决方案、数据资产行业报告白皮书、数据资产政策汇编、数据资产基础知识以及数据资…...

Python与R机器学习(1)支持向量机

以下是对Python与R在支持向量机&#xff08;SVM&#xff09;实现上的核心区别分析及完整示例代码&#xff1a; 一、核心差异对比 特征Python (scikit-learn)R (e1071/kernlab)核心库sklearn.svm.SVC/SVRe1071::svm() 或 kernlab::ksvm()语法范式面向对象&#xff08;先初始化模…...

Render上后端部署Springboot + 前端Vue 问题及解决方案汇总

有一个 Vue 前端 和 Spring Boot 后端的动态网页游戏&#xff0c;当前在本地的 5173 端口和运行。你希望生成一个公开链接&#xff0c;让所有点击链接的人都能访问并玩这个游戏。由于游戏原本需要在本地执行 npm install 后才能启动&#xff0c;你现在想知道在部署时是选择 Ren…...

朝天椒USB服务器:解决加密狗远程连接

本文探讨朝天椒USB服务器用Usb Over Network技术&#xff0c;解决加密狗在虚拟机、云主机甚至异地的远程连接问题。 在企业数字化转型的浪潮中&#xff0c;加密狗作为防止软件盗版的重要手段&#xff0c;广泛应用于各类软件授权场景。然而&#xff0c;随着企业超融合进程不断加…...

Unity Shader Feature

Shader Feature 设置Keyword //0:Red 1:Green 2:Blue Mat.SetInt(“_Color”,0); 需要在创建时进行设置&#xff0c;运行时不可设置 Shader "Unlit/KeywordEnum" {Properties{[KeywordEnum(Red,Green,Blue)] _Color("Color",int) 0}SubShader{Pass{HLSL…...

前端开发环境

vscde nrm 切换源管理 nvm 切换node版本工具 nodemon node运行js文件热更新 pxcook 易用的自动标注工具, 生成前端代码, 设计研发协作利器,比PS轻量 TypeScript 安装tsc 它的作用就是将ts文件编译为js文件 npm i typescript -g 输入tsc -v能够看到东西&#xff0c;就说明好了 …...

c语言判断一个文件的文件格式

在 Linux 下使用 C 语言判断一个文件的文件格式&#xff0c;通常需要检查文件的头信息&#xff08;也称为“幻数”或“魔数”&#xff09;。不同的文件格式在文件头有特定的字节序列&#xff0c;这些字节序列可以用来确定文件的类型。以下是一个基本的示例&#xff0c;展示了如…...

厘米和磅的转换关系

在排版和设计领域&#xff0c;厘米&#xff08;cm&#xff09;和磅&#xff08;pt&#xff09;都是常用的长度度量单位&#xff0c;它们之间的转换关系基于特定的换算标准&#xff0c;下面为你详细介绍&#xff1a; 基本换算关系 磅是印刷行业常用的长度单位&#xff0c;1英寸…...

汽车与AI深度融合:CES Asia 2025前瞻

在科技飞速发展的当下&#xff0c;汽车与AI的融合正成为行业变革的关键驱动力。近日&#xff0c;吉利、极氪、岚图、智己等多家车企纷纷官宣与DeepSeek模型深度融合&#xff0c;其中岚图知音更是将成为首个搭载该模型的量产车型&#xff0c;这无疑是汽车智能化进程中的重要里程…...

用easyExcel如何实现?

要使提供的 ExcelModelListener 类来解析 Excel 文件并实现批量存储数据库的功能&#xff0c;需要结合 EasyExcel 库来读取 Excel 数据。具体来说&#xff0c;可以使用 EasyExcel.read() 方法来读取 Excel 文件&#xff0c;并指定 ExcelModelListener 作为事件监听器。 下面是…...

停止回答 TypeError: (0 , _vue.defineComponent) is not a function

在 Vue.js 中遇到 TypeError: (0 , _vue.defineComponent) is not a function 错误通常意味着 defineComponent 函数没有被正确导入或者你的 Vue 版本不支持该函数。 解决步骤 检查 Vue 版本 defineComponent 是 Vue 3 中的一个功能&#xff0c;用于创建组件。确保你正在使用…...

数据结构与算法-单链表

链表 参考学习&#xff1a;B站-逊哥带你学编程 单链表 单链表-存储结构 typedef int ElemType;typedef struct node{ElemType data;struct node *next; }Node;单链表-初始化 Node *initList() {Node *head (Node *)malloc(sizeof(Node));head->data 0;head->next …...