OpenCVSharp:ArUco 标记检测与透视变换

作者:mingupup日期:2025/11/13

前言

对于.NET开发者而言,入门OpenCV的一个很舒适的方式就是先去使用OpenCVSharp,它是 OpenCV 的 .NET 封装,而且作者还开源了一个示例库,可以通过示例库进行入门学习。

OpenCVSharp仓库地址:github.com/shimat/open…

opencvsharp_samples仓库地址:github.com/shimat/open…

作者提供了几十个可以直接运行的示例代码,一开始可以先大概运行一下这些示例,看一下用这个库可以实现哪些功能。

入门第一步就是先学会用,那些视觉算法的原理可以先不懂,大概了解一下就够了,等后面真的需要你深入了解的时候再去了解也不迟,现在深入理解原理容易让小白放弃,刚开始入门我们就当一名踏踏实实的“掉包侠”。

Aruco 标记检测与透视变换

第一个例子是关于Aruco 标记检测和透视变换的。

第一步先运行起来,看一下实现了什么效果?

首先原图是这样的:

注意到上面有4个有点奇怪的四边形。

然后识别这几个四边形的区域:

然后再进行一下透视变换:

刚刚看到的这些四边形就是Aruco标记,它是拿来干嘛的呢?我的简单理解就是拿来标记用的,一个经典的应用就是替换相框中的图片。

OpenCVSharp好像还没有提供生成Aruco标记的功能,但是已经有了识别的功能,让我们看看这个效果是如何实现的吧!!

1 // The locations of the markers in the image at FilePath.Image.Aruco.
2 const int upperLeftMarkerId = 160;
3 const int upperRightMarkerId = 268;
4 const int lowerRightMarkerId = 176;
5 const int lowerLeftMarkerId = 168;
6
7 using var src = Cv2.ImRead(ImagePath);
8
9 var detectorParameters = new DetectorParameters();
10 detectorParameters.CornerRefinementMethod = CornerRefineMethod.Subpix;
11 detectorParameters.CornerRefinementWinSize = 9;
12
13 using var dictionary = CvAruco.GetPredefinedDictionary(PredefinedDictionaryName.Dict4X4_1000);
14
15 CvAruco.DetectMarkers(src, dictionary, out var corners, out var ids, detectorParameters, out var rejectedPoints);
16

每个Aruco标记都有一个确定的ID,然后根据路径读取图片。

1var detectorParameters = new DetectorParameters();
2detectorParameters.CornerRefinementMethod = CornerRefineMethod.Subpix;
3detectorParameters.CornerRefinementWinSize = 9;
4

进行检测器参数配置:

DetectorParameters: 创建ArUco检测器的参数对象,用于控制标记检测的精度和行为

CornerRefinementMethod.Subpix: 设置角点细化方法为子像素级别,提高角点检测精度

CornerRefinementWinSize = 9: 设置角点细化窗口大小为9x9像素,用于角点周围的子像素级优化

1using var dictionary = CvAruco.GetPredefinedDictionary(PredefinedDictionaryName.Dict4X4_1000);
2

进行字典配置:

CvAruco.GetPredefinedDictionary: 获取OpenCV预定义的ArUco标记字典

PredefinedDictionaryName.Dict4X4_1000: 选择4x4位编码、包含1000个不同标记的字典类型

1CvAruco.DetectMarkers(src, dictionary, out var corners, out var ids, detectorParameters, out var rejectedPoints);
2

这就在进行ArUco标记检测了,主要知道一下参数是什么意思就行了。

src - 输入图像,包含要检测ArUco标记的源图像

dictionary - 标记字典,预定义的ArUco标记字典(前面配置的Dict4X4_1000)

corners - 检测到的标记角点(输出参数),每个标记的4个角点坐标,按顺时针顺序存储(从左上角开始)

ids - 检测到的标记ID(输出参数),每个检测到的标记对应的ID编号

detectorParameters - 检测参数,前面配置的检测器参数(包含角点细化等设置)

rejectedPoints - 被拒绝的候选标记(输出参数),检测过程中被识别为候选但最终被拒绝的标记角点

自己再稍微打断点加深一下印象:

确实是,每一项都有四个点。

检测出了ArUco标记的ID。

确实有一组被拒绝的候选标记。

1using var detectedMarkers = src.Clone();
2CvAruco.DrawDetectedMarkers(detectedMarkers, corners, ids, Scalar.Crimson);
3

在图像上绘制区域与ID。

1            // Find the index of the four markers in the ids array. We'll use this same index into the
2            // corners array to find the corners of each marker.
3            var upperLeftCornerIndex = Array.FindIndex(ids, id => id == upperLeftMarkerId);
4            var upperRightCornerIndex = Array.FindIndex(ids, id => id == upperRightMarkerId);
5            var lowerRightCornerIndex = Array.FindIndex(ids, id => id == lowerRightMarkerId);
6            var lowerLeftCornerIndex = Array.FindIndex(ids, id => id == lowerLeftMarkerId);
7
8            // Make sure we found all four markers.
9            if (upperLeftCornerIndex < 0 || upperRightCornerIndex < 0
10                 || lowerRightCornerIndex < 0 || lowerLeftCornerIndex < 0)
11            {
12                return;
13            }
14
15            // Marker corners are stored clockwise beginning with the upper-left corner.
16            // Get the first (upper-left) corner of the upper-left marker.
17            var upperLeftPixel = corners[upperLeftCornerIndex][0];
18            // Get the second (upper-right) corner of the upper-right marker.
19            var upperRightPixel = corners[upperRightCornerIndex][1];
20            // Get the third (lower-right) corner of the lower-right marker.
21            var lowerRightPixel = corners[lowerRightCornerIndex][2];
22            // Get the fourth (lower-left) corner of the lower-left marker.
23            var lowerLeftPixel = corners[lowerLeftCornerIndex][3];
24
25            // Create coordinates for passing to GetPerspectiveTransform
26            var sourceCoordinates = new List<Point2f>
27            {
28                upperLeftPixel, upperRightPixel, lowerRightPixel, lowerLeftPixel
29            };
30

就是确保都找到了这些ID,然后确定了一个区域,就是这么一个区域:

这个区域由第一个ArUco标记的左上角点、第二个右上角点、第三个左下角点与第四个右下角点组成。

1var destinationCoordinates = new List<Point2f>
2{
3    new Point2f(0, 0),
4    new Point2f(1024, 0),
5    new Point2f(1024, 1024),
6    new Point2f(0, 1024),
7};
8
9

首先进行目标坐标定义,定义了变换后的标准矩形区域,创建一个1024×1024像素的正方形。

1using var transform = Cv2.GetPerspectiveTransform(sourceCoordinates, destinationCoordinates);
2

然后进行计算透视变换矩阵:

sourceCoordinates: 从检测到的4个ArUco标记角点提取的源坐标

destinationCoordinates: 目标标准矩形坐标

返回值: 3×3的透视变换矩阵,用于将源四边形映射到目标矩形

1using var normalizedImage = new Mat();
2Cv2.WarpPerspective(src, normalizedImage, transform, new Size(1024, 1024));
3

应用透视变换:

src: 原始输入图像

normalizedImage: 输出的标准化图像

transform: 透视变换矩阵

new Size(1024, 1024): 输出图像尺寸

这样就得到了最后的那张图片。


OpenCVSharp:ArUco 标记检测与透视变换》 是转载文章,点击查看原文


相关推荐


🚀 MateChat发布V1.10.0版本,支持附件上传及体验问题修复,欢迎体验~
2025/11/12

✨ 本期亮点 最新发布的 MateChat V1.10.0 版本新增文件列表组件和重新生成功能等特性,希望这个版本为你带来全新的体验! 🎯 核心功能升级(新特性) 🔄 新增文件列表组件 1、基本用法 McFileList 组件的核心功能是接收一个文件对象数组,并将它们渲染为信息卡片。通过 fileItems 属性传入数据,并可使用 context 属性控制其在不同场景下的外观,详情点击文件列表组件Demo 2、不同上下文与状态 McFileList 提供了两种上下文模式和多种文件状态,以适


Service Worker 深度解析:让你的 Web 应用离线也能飞
前端嘿起2025/11/10

在现代 Web 开发中,用户体验已经成为了衡量一个应用成功与否的重要标准。用户不仅希望网站加载速度快,还希望即使在网络不稳定或完全断网的情况下也能正常使用应用。这就引出了我们今天的主角——Service Worker。 前言 Service Worker 是一种在浏览器后台运行的脚本,它独立于网页主线程,可以拦截网络请求、缓存资源,甚至在离线状态下也能提供完整的用户体验。它是实现 PWA(渐进式 Web 应用)的核心技术之一,为 Web 应用带来了原生应用般的离线能力。 在本文中,我们将从基础


Thread.sleep 与 Task.sleep 终极对决:Swift 并发世界的 “魔法休眠术” 揭秘
大熊猫侯佩2025/11/8

📜 引子:霍格沃茨的 “并发魔咒” 危机 在霍格沃茨城堡顶层的 “魔法程序与咒语实验室” 里,金色的阳光透过彩绘玻璃洒在悬浮的魔法屏幕上。哈利・波特正对着一段闪烁着蓝光的 Swift 代码抓耳挠腮,罗恩在一旁急得直戳魔杖 —— 他们负责的 “魁地奇赛事实时计分器” 又卡住了。 赫敏抱着厚厚的《Swift 并发魔法指南》凑过来,眉头紧锁:“肯定是上次加的‘休眠咒语’出了问题!我早就说过 Thread.sleep 像‘摄魂怪的拥抱’,会吸干线程的活力,你们偏不信!” 这时,实验室的门 “吱呀”


Godot游戏开发——C# (一)
云缘若仙2025/11/6

1. 素材管理 核心内容:明确游戏开发所需基础素材类型,为场景与节点提供资源支撑,具体包括: AssetBundle:资源打包容器,用于统一管理与加载资源; Audio 音频素材:提供游戏音效、背景音乐等音频资源; Sprites 精灵图片素材:提供角色、道具、场景元素等可视化图片资源。 2. 场景树与核心节点 节点类型 功能描述 Root Node(根节点) 场景树顶层节点,所有子节点均嵌套于其下,构成场景层级框架的基础。


高并发电商架构设计与落地:从微服务拆分到全链路优化
kennylee262025/10/31

一、交易核心 - 高并发订单的生成与落地 1.1 引言:为什么“收单”是系统的生命线 在电商体系中,交易是核心,而订单是起点。一个高效、稳定的收单系统,决定了平台的承载能力与用户体验。在高并发场景(如秒杀、大促)下,系统的挑战早已超越传统的“增删改查”,转向对性能极限、数据一致性与弹性扩展的全面考验。本章将解析如何通过微服务拆分与架构优化,构建一个能从容应对瞬时流量洪峰的订单处理系统。 1.2 架构总览:微服务拆分与职责边界 微服务架构的核心价值在于解耦、弹性伸缩与容错。在订单处理流程中


SpringBoot 时间轮实现延时任务
风象南2025/10/30

传统方案的困境 在日常开发中,我们经常需要处理各种定时任务:用户注册后的欢迎邮件、订单超时自动取消、缓存定期刷新等。传统的定时器方案在面对大规模定时任务时往往力不从心: 性能瓶颈日益凸显 ScheduledExecutor在处理上千个任务时性能急剧下降 Timer类不仅线程不安全,还存在单点故障风险 每次调度都要在堆中查找最小元素,时间复杂度O(log n) 频繁的GC压力导致系统吞吐量受限 业务需求日益复杂 消息重试需要指数退避策略 分布式系统需要精确的延迟调度 会话管理需要动态添加删除


BSON vs JSON:不只是"二进制"这么简单
风象南2025/10/27

前言 当今项目开发,大多以JSON作为各个场景的标准数据格式。从 REST API 到配置文件,从 NoSQL 数据库到日志记录,JSON 几乎无处不在。然而,在 MongoDB 等 NoSQL 数据库的生态系统中,我们经常听到另一个名词:BSON。 很多人对 BSON 的理解停留在"二进制的 JSON"这个层面,认为它只是 JSON 的二进制编码版本。但实际上,BSON 的设计理念和实现细节远比这个简单的描述要丰富和深刻得多。 JSON 的优势与局限 JSON 的优势 JSON 之所以能够成为


ES6+革命:8大特性让你的JavaScript代码质量翻倍
良山有风来2025/10/24

最近review代码的时候,看到一些还在用var声明变量、用function写满屏回调的代码,我真的有点头疼。 你是不是也遇到过这样的困扰:代码写着写着就乱了,变量莫名其妙被修改,回调嵌套到怀疑人生?其实这些问题,ES6+早就给出了优雅的解决方案。 今天我就带你彻底告别老旧的JS写法,用8个核心特性让你的代码质量直接翻倍!每个特性我都会配上详细注释的代码示例,保证你能立刻上手。 let和const:告别变量提升的噩梦 还记得用var时那些诡异的现象吗?变量莫名其妙被提升,循环计数器失效... l


STM32学习(MCU控制)(GPIO)
D.....l2025/10/22

文章目录 MCU 和 GPIO1. 单片机 MCU1.1 单片机和嵌入式系统1.2 MCU1.3 ARM 公司1.4 市场主流 32 芯片1.5 STM32 开发版概述 2. GPIO2.1 GPIO 概述2.2 STM32F103ZET6 GPIO 相关内容2.3 GPIO 开发流程2.4 GPIO 控制 LED 灯2.5 GPIO 端口内部基本电路情况**2.5.1. 浮空输入模式(Floating Input)****2.5.2. 上拉输入模式(Pull - up Inpu


【Node】认识multer库
你的人类朋友2025/10/21

前言 在 Node.js 中处理文件上传是一个常见的需求,比如用户上传头像或文档。 那么,如何简单、安全地接收并保存这些文件呢? 本文说说 multer 的库,它可以帮助我们快速实现这一功能。 小问题:为什么使用 multer 而不是 Node.js 原生模块来处理文件上传。 🤔 补充知识:原生模块如何实现文件上传? 使用 Node.js 原生模块实现文件上传需要手动解析 multipart/form-data 格式的请求体,处理数据流并识别边界符。 然后逐个字段提取文件内容和元数据,最后通

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0