ArUco与OpenCV_aruco.hpp-程序员宅基地

技术标签: OpenCV  学习之旅  无人机  定位  Aruco  

目录

生成标记

检测标记

完整代码

一些链接

代码片段记录

创建Aruco的Board板

检测Board板



        ArUco标记可以用于增强现实、相机姿势估计和相机校准等应用场景,具体如无人机的自主降落地标、机器人定位。标记中白色部分为唯一标识的二进制编码。

生成标记

        通过为每个码生成唯一标记,可以获取到更丰富的信息。在OpenCV中有25个预定义的标记字典。字典中的所有标记都包含相同数量的块或位(4×4、5×5、6×6 或 7×7),并且每个字典包含固定数量的标记(50、100、250 或 1000)。

C++:

#include <opencv2/highgui.hpp>
#include <opencv2/core.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>

// 导入aruco库
#include <opencv2/aruco.hpp>

using namespace std;
using namespace cv;

Mat markerImage;
// 加载预定义字典
Ptr<cv::aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);

// 生成标记
int id = 33;  // 唯一标记
int pixsize = 200;  // 图像的像素大小
aruco::drawMarker(dictionary, id, pixsize , markerImage, 1);

imshow("markerImage", markerImage);
waitKey();

Python:

import cv2 as cv
import numpy as np

# Load the predefined dictionary
dictionary = cv.aruco.Dictionary_get(cv.aruco.DICT_6X6_250)

# Generate the marker
ixsize = 200  # 图像的像素大小
id = 33 # 唯一标记
markerImage = np.zeros((ixsize , ixsize ), dtype=np.uint8)
markerImage = cv.aruco.drawMarker(dictionary, id, ixsize , markerImage, 1);

cv.imwrite("marker33.png", markerImage);

对于cv.aruco.drawMarker函数:

  • 第一个参数视预定义字典。
  • 第二个参数标识唯一标记,允许从 id 从 0 到 249 的 250 个标记的集合中选择具有给定 id 的标记。
  • 第三个参数决定了生成的标记的大小。在上面的示例中,它将生成一个具有 200×200 像素的图像。
  • 第四个参数表示将存储生成的标记的对象(上面的标记图像)。
  • 第五个参数是厚度参数,它决定了应该将多少块作为边界添加到生成的二进制模式中。在上面的示例中,将在 6×6 生成的图案周围添加 1 位的边界,以在 200×200 像素的图像中生成具有 7×7 位的图像。

检测标记

C++:

// 加载用于生成标记的字典。
Ptr<cv::aruco::Dictionary> dictionary = getPredefinedDictionary(cv::aruco::DICT_6X6_250);
// 使用默认值初始化检测器参数
Ptr<cv::aruco::DetectorParameters> parameters = cv::aruco::DetectorParameters::create();

// 声明将包含检测到的标记角和被拒绝的候选标记的向量
vector<vector<Point2f>> markerCorners, rejectedCandidates;
// 检测到的标记的id存储在一个向量中
vector<int> markerIds;
// 检测标记
detectMarkers(markerImage, dictionary, markerCorners, markerIds, parameters, rejectedCandidates);
    

Python:

#Load the dictionary that was used to generate the markers.
dictionary = cv.aruco.Dictionary_get(cv.aruco.DICT_6X6_250)

# Initialize the detector parameters using default values
parameters =  cv.aruco.DetectorParameters_create()

# Detect the markers in the image
markerCorners, markerIds, rejectedCandidates = cv.aruco.detectMarkers(frame, dictionary, parameters=parameters)

        对于每个成功的标记物检测,将按左上角、右上角、右下角、左下角的顺序检测标记的四个角点。在C++中,这4个检测到的角点被存储为点的向量,并且图像中的多个标记一起存储在点的向量向量中。在Python中,它们被存储为数组的Numpy数组。

        在打印、剪切和放置场景中的标记时,重要的是在标记的黑色边界周围保留一些白色边框,以便可以轻松检测到它们。因此对于上面生成的Aruco图不能直接用,可以使用以下代码来增加白边

完整代码

#include <iostream>
#include <opencv2/highgui.hpp>
#include <opencv2/core.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/aruco.hpp>

using namespace std;
using namespace cv;

vector<Mat> generateAruco(int nums, int pixSize=200, int border=1) {
    vector<Mat> markerImages;
    Ptr<cv::aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
    for (int i = 0; i < nums; ++i) {
        Mat tempImage;
        aruco::drawMarker(dictionary, i, pixSize, tempImage, border);
        markerImages.push_back(tempImage);
    }
    return markerImages;
}

void detectAruco(const Mat& markerImage, vector<vector<Point2f>> &markerCorners, vector<vector<Point2f>> &rejectedCandidates, vector<int> &markerIds) {
    // 加载用于生成标记的字典。
    Ptr<cv::aruco::Dictionary> dictionary = getPredefinedDictionary(cv::aruco::DICT_6X6_250);
    // 使用默认值初始化检测器参数
    Ptr<cv::aruco::DetectorParameters> parameters = cv::aruco::DetectorParameters::create();
    // 检测标记
    detectMarkers(markerImage, dictionary, markerCorners, markerIds, parameters, rejectedCandidates);
}


int main(int argc, char* argv[])
{
    vector<Mat> markerImages = generateAruco(1);
    Mat markerImage = markerImages[0];
    vector<vector<Point2f>> markerCorners;
    vector<vector<Point2f>> rejectedCandidates;
    vector<int> markerIds;
    cv::copyMakeBorder(markerImage, markerImage, 5, 5, 5, 5, cv::BORDER_CONSTANT, Scalar(255,0,0));
    detectAruco(markerImage, markerCorners, rejectedCandidates, markerIds);
    cout << markerIds.size() << endl;

    imshow("markerImage", markerImage);
    waitKey();

    return 0;
}

一些链接

OpenCV:检测dArUco标记(单个)

在OpenCV中使用ArUco Markers的增强现实(C++ / Python)

ArUco: a minimal library for Augmented Reality applications based on OpenCV

OpenCV:检测ArUco板(多个)

代码片段记录

创建Aruco的Board板

更:这个好像不对??)创建Aruco中Board(与GridBoard不同,Board不限于网格形,可以是任意排列的2D、3D图形)时,出现类型错误objPoints.type() == CV_32FC3 || objPoints.type() == CV_32FC1 in function 'create',需要转换类型:

if (markerCorners.empty()) {
    cout<<"无可用Marker"<<endl;
    return 0;
}

std::vector< std::vector< Point3f > > objPoints;
for (auto corner: markerCorners) {
    vector< Point3f > temp;
    for (int i = 0; i < 4; ++i) {
        temp.emplace_back(Point3f(corner[i].x, corner[i].y, 0));
    }
    objPoints.emplace_back(temp);
}
cv::Ptr<cv::aruco::Board> board = cv::aruco::Board::create(objPoints, dictionary, markerIds);

而对于创建GridBoard时候不需要转换,只用提供额外参数就行(毕竟已经是网格形了):

cv::Ptr<cv::aruco::GridBoard> board =  
    cv::aruco::GridBoard::create(5,             //每行多少个Marker
                                 7,             //每列多少个Marker
                                 0.04,          //marker长度
                                 0.01,          //marker之间的间隔
                                 dictionary);   //字典

检测Board板

相对于上面创建的Board,此处有两种方式来检测:单次检测、一起检测。

        对于单次检测,是分开每次检测一个,分别估计位姿

// 估计相机位姿(相对于每一个marker)
cv::aruco::estimatePoseSingleMarkers(markerCorners, 0.055, cameraMatrix, distCoeffs, rvecs, tvecs);

 输出:

R_{camera<---marker} :[-0.0003413119903118433, 0.9999500428020713, 0.009989765075384124;
 0.9998031880569347, 0.0001430703615183937, 0.0198384647103005;
 0.01983604439689155, 0.00999457007618388, -0.9997532895228086]
t_{camera<---marker} :[0.236643, 0.554003, 1.73982]

R_{camera<---marker} :[-0.9987024032747158, -0.04667896450031111, 0.02036133508603012;
 -0.03554963247130007, 0.9252917858620086, 0.3775861949905609;
 -0.03646550869605953, 0.3763724024014953, -0.9257505503028689]
t_{camera<---marker} :[-0.353789, 0.2201, 1.68946]

R_{camera<---marker} :[0.9926712669967025, 0.0045609525196265, 0.120759899765092;
 0.01132149090445043, -0.998402461553577, -0.05535655884769745;
 0.1203145025458768, 0.05631804751473418, -0.9911370732654825]
t_{camera<---marker} :[0.164678, 0.105936, 1.38113]

R_{camera<---marker} :[0.974149731293016, -0.09977617778369052, -0.2026746539866854;
 -0.04192481488856337, -0.961439121983884, 0.2718034668936692;
 -0.2219788524123525, -0.2562801768879826, -0.9407687601190632]
t_{camera<---marker} :[0.745065, 0.110989, 1.73927]

R_{camera<---marker} :[0.001805456772273173, -0.995139605508609, -0.09845763491986455;
 -0.9661010203335827, -0.02715349161609959, 0.2567323633737377;
 -0.2581580113733717, 0.09465650237159776, -0.9614544127115553]
t_{camera<---marker} :[0.153552, -0.265153, 1.90376]

        而一起检测,是对所有的一起检测后,算一个总的位姿:(比较适用于需要高精度、有遮挡、有形变的情况)

// 估计相机位姿(相对于 aruco 板)
int valid = cv::aruco::estimatePoseBoard(markerCorners, markerIds, board, cameraMatrix, distCoeffs, rvec, tvec);

 输出:

R_{camera<---marker} :[0.009618842263444671, -0.07316045611916067, -0.997273796674688;
 -0.7388014820617763, -0.672599545516235, 0.04221636501022974;
 -0.6738544709184865, 0.7363812864486647, -0.06052068234393715]
t_{camera<---marker} :[-5.07066, 571.684, -24.9751]

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sxf1061700625/article/details/124310648

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签