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

【ChArUco Marker】标定板检测

目录

  • 1.ChArUco介绍
  • 2.源码分析(opencv 4.5.4)
  • 3.ChAruco板创建(opencv 4.5.4)
  • 4.Charuco板检测(opencv 4.5.4)
    • ChArUco检测源码(不使用标定参数)
  • 5.ChArUco姿态估计(opencv 4.5.4)
    • ChArUco检测与姿态估计源码
  • 6.源码分析(opencv 4.10.0)
    • 6.1 ChAruco板创建
      • 源码
    • 6.2 ChArUco板检测
      • 源码
    • 6.3 ChArUco 姿态估计

https://docs.opencv.org/4.5.4/df/d4a/tutorial_charuco_detection.html

1.ChArUco介绍

ArUco标记和板非常有用,因为它们检测速度快且用途广泛。然而,ArUco标记的一个问题是,即使在应用亚像素精炼之后,其角位置的准确性也不是很高。
相反,棋盘格图案的角点可以更准确地精炼,因为每个角点都被两个黑色方块包围。然而,寻找棋盘格图案不如寻找ArUco板那么通用:它必须完全可见,且不允许有遮挡。
ChArUco试图结合这两种方法的优点:
在这里插入图片描述
ArUco部分用于插值棋盘角的位置,因此它具有标记板的多功能性,因为它允许遮挡或部分视图。此外,由于插值的角属于棋盘,因此在亚像素精度方面非常准确。
在需要高精度的情况下,例如在相机校准中,Charuco 棋盘比标准的 Aruco 棋盘更好。

2.源码分析(opencv 4.5.4)

//! [charucohdr]
#include <opencv2/aruco/charuco.hpp>
//! [charucohdr]
#include <opencv2/highgui.hpp>
#include <iostream>
#include <string>namespace {
const char *about ="A tutorial code on charuco board creation and detection of charuco board with and without camera caliberation";
const char *keys = "{c        |       | Put value of c=1 to create charuco board;\nc=2 to detect charuco board without ""camera calibration;\nc=3 to detect charuco board with camera calibration and Pose Estimation}";
} // namespacevoid createBoard();
void detectCharucoBoardWithCalibrationPose();
void detectCharucoBoardWithoutCalibration();static bool readCameraParameters(std::string filename, cv::Mat &camMatrix, cv::Mat &distCoeffs)
{cv::FileStorage fs(filename, cv::FileStorage::READ);if (!fs.isOpened())return false;fs["camera_matrix"] >> camMatrix;fs["distortion_coefficients"] >> distCoeffs;return (camMatrix.size() == cv::Size(3, 3));
}void createBoard()
{cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);//! [createBoard]cv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(5, 7, 0.04f, 0.02f, dictionary);cv::Mat boardImage;board->draw(cv::Size(600, 500), boardImage, 10, 1);//! [createBoard]cv::imwrite("BoardImage.jpg", boardImage);
}//! [detwcp]
void detectCharucoBoardWithCalibrationPose()
{cv::VideoCapture inputVideo;inputVideo.open(0);//! [matdiscoff]cv::Mat cameraMatrix, distCoeffs;std::string filename = "calib.txt";bool readOk          = readCameraParameters(filename, cameraMatrix, distCoeffs);//! [matdiscoff]if (!readOk) {std::cerr << "Invalid camera file" << std::endl;} else {//! [dictboard]cv::Ptr<cv::aruco::Dictionary> dictionary     = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);cv::Ptr<cv::aruco::CharucoBoard> board        = cv::aruco::CharucoBoard::create(5, 7, 0.04f, 0.02f, dictionary);cv::Ptr<cv::aruco::DetectorParameters> params = cv::aruco::DetectorParameters::create();//! [dictboard]while (inputVideo.grab()) {//! [inputImg]cv::Mat image;//! [inputImg]cv::Mat imageCopy;inputVideo.retrieve(image);image.copyTo(imageCopy);//! [midcornerdet]std::vector<int> markerIds;std::vector<std::vector<cv::Point2f>> markerCorners;cv::aruco::detectMarkers(image, board->dictionary, markerCorners, markerIds, params);//! [midcornerdet]// if at least one marker detectedif (markerIds.size() > 0) {cv::aruco::drawDetectedMarkers(imageCopy, markerCorners, markerIds);//! [charidcor]std::vector<cv::Point2f> charucoCorners;std::vector<int> charucoIds;cv::aruco::interpolateCornersCharuco(markerCorners,markerIds,image,board,charucoCorners,charucoIds,cameraMatrix,distCoeffs);//! [charidcor]// if at least one charuco corner detectedif (charucoIds.size() > 0) {cv::Scalar color = cv::Scalar(255, 0, 0);//! [detcor]cv::aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, color);//! [detcor]cv::Vec3d rvec, tvec;//! [pose]// cv::aruco::estimatePoseCharucoBoard(charucoCorners, charucoIds, board, cameraMatrix, distCoeffs,// rvec, tvec);//! [pose]bool valid = cv::aruco::estimatePoseCharucoBoard(charucoCorners,charucoIds,board,cameraMatrix,distCoeffs,rvec,tvec);// if charuco pose is validif (valid)cv::aruco::drawAxis(imageCopy, cameraMatrix, distCoeffs, rvec, tvec, 0.1f);}}cv::imshow("out", imageCopy);char key = (char)cv::waitKey(30);if (key == 27)break;}}
}
//! [detwcp]//! [detwc]
void detectCharucoBoardWithoutCalibration()
{cv::VideoCapture inputVideo;inputVideo.open(0);cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);cv::Ptr<cv::aruco::CharucoBoard> board    = cv::aruco::CharucoBoard::create(5, 7, 0.04f, 0.02f, dictionary);cv::Ptr<cv::aruco::DetectorParameters> params = cv::aruco::DetectorParameters::create();params->cornerRefinementMethod                = cv::aruco::CORNER_REFINE_NONE;while (inputVideo.grab()) {cv::Mat image, imageCopy;inputVideo.retrieve(image);image.copyTo(imageCopy);std::vector<int> markerIds;std::vector<std::vector<cv::Point2f>> markerCorners;cv::aruco::detectMarkers(image, board->dictionary, markerCorners, markerIds, params);// or// cv::aruco::detectMarkers(image, dictionary, markerCorners, markerIds, params);//  if at least one marker detectedif (markerIds.size() > 0) {cv::aruco::drawDetectedMarkers(imageCopy, markerCorners, markerIds);//! [charidcorwc]std::vector<cv::Point2f> charucoCorners;std::vector<int> charucoIds;cv::aruco::interpolateCornersCharuco(markerCorners, markerIds, image, board, charucoCorners, charucoIds);//! [charidcorwc]// if at least one charuco corner detectedif (charucoIds.size() > 0)cv::aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));}cv::imshow("out", imageCopy);char key = (char)cv::waitKey(30);if (key == 27)break;}
}
//! [detwc]int main(int argc, char *argv[])
{cv::CommandLineParser parser(argc, argv, keys);parser.about(about);if (argc < 2) {parser.printMessage();return 0;}int choose = parser.get<int>("c");switch (choose) {case 1:createBoard();std::cout << "An image named BoardImg.jpg is generated in folder containing this file" << std::endl;break;case 2:detectCharucoBoardWithoutCalibration();break;case 3:detectCharucoBoardWithCalibrationPose();break;default:break;}return 0;
}
  • 执行生成charuco图案:./可执行程序 -c=1

3.ChAruco板创建(opencv 4.5.4)

aruco模块提供了cv::aruco::CharucoBoard类,该类表示一个Charuco板,并且继承自Board类。
此类,以及ChArUco功能的其余部分,定义在:

#include <opencv2/aruco/charuco.hpp>

要定义一个 CharucoBoard,需要:

  • X 方向的棋盘格数量。
  • Y 方向的棋盘格数量。
  • 方块边长。
  • 标记边长。
  • 标记字典。
  • 所有标记的 ID。

至于 GridBoard 对象,aruco 模块提供了一个函数来轻松创建 CharucoBoards。这个函数是静态函数 cv::aruco::CharucoBoard::create()

cv::aruco::CharucoBoard board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);
// 第一个参数:表示X方向上的方块数量
// 第二个参数:表示Y方向上的方块数量
// 第三个参数:表示方块的长度即每个棋盘格方块的边长,这里是0.04m
// 第四个参数:表示标记的长度即每个aruco方块的边长,这里是0.02m
// 第五个参数:提供标记的字典。

每个标记的ID默认按升序分配,从0开始,类似于GridBoard::create()。可以通过访问board.ids中的ID向量轻松自定义,类似于Board父类。

一旦我们拥有了CharucoBoard对象,就可以创建一个图像以便打印。这可以通过CharucoBoard::draw()方法来完成:

cv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(5, 7, 0.04f, 0.02f, dictionary);
cv::Mat boardImage;
board->draw(cv::Size(600, 500), boardImage, 10, 1);
// 第一个参数:表示输出图像的大小,以像素为单位。在这种情况下为600x500像素。如果这个尺寸与棋盘的尺寸不成比例,它将被居中显示在图像上。  
// 第二个参数:boardImage表示输出的带有棋盘的图像。  
// 第三个参数:(可选的)边距,以像素为单位,以确保没有标记触碰到图像边缘。在这种情况下,边距为10。  
// 第四个参数:表示标记边框的大小,类似于drawMarker()函数。默认值为1。

输出图像如下:
在这里插入图片描述
完整的工作示例包含在模块/aruco/samples/create_board_charuco.cpp中的create_board_charuco.cpp文件内。
注意:create_board_charuco.cpp 现在通过 OpenCV 命令行解析器接受命令行输入。对于这个文件,示例参数将如下所示。

"_ output path_/chboard.png" -w=5 -h=7 -sl=200 -ml=120 -d=10

4.Charuco板检测(opencv 4.5.4)

当你检测到一个ChArUco板时,实际上是在检测板上的每个棋盘角点。

ChArUco板上的每个角点都有一个唯一的标识符(id)分配。这些id从0到板上角点的总数。ChArUco板检测的步骤可以分解为以下几步:

  • 1.输入图像
cv::Mat image;

原始图像,在该图像中将检测标记。此图像在ChArUco角点中执行亚像素精细化是必要的。

  • 2.读取相机标定参数(仅用于带有相机标定的检测)
cv::Mat cameraMatrix, distCoeffs;
std::string filename = "calib.txt";
bool readOk = readCameraParameters(filename, cameraMatrix, distCoeffs);
// readCameraParameters 的参数如下:
// filename:这是 caliberation.txt 文件的路径,该文件是由 calibrate_camera_charuco.cpp 生成的输出文件
// cameraMatrix 和 distCoeffs:可选的相机校准参数
// return:
// 此函数将这些参数作为输入,并返回一个布尔值,指示相机校准参数是否有效。对于未进行校准的角点检测,此步骤不是必需的。
  • 3.检测标记
cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
cv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(5, 7, 0.04f, 0.02f, dictionary);
cv::Ptr<cv::aruco::DetectorParameters> params = cv::aruco::DetectorParameters::create();std::vector<int> markerIds;
std::vector<std::vector<cv::Point2f> > markerCorners;
cv::aruco::detectMarkers(image, board->dictionary, markerCorners, markerIds, params);
// detectMarkers的参数如下:
// image:输入图像。
// dictionary:指向将被搜索的字典/标记集的指针。
// markerCorners:检测到的标记角点的向量。
// markerIds:检测到的标记的标识符向量。
// params:标记检测参数 ChArUco角点的检测是基于先前检测到的标记。因此,首先检测标记,然后从标记中插值计算ChArUco角点。
  • 4.从标记插值计算charuco角点

带标定的检测

std::vector<cv::Point2f> charucoCorners;
std::vector<int> charucoIds;
cv::aruco::interpolateCornersCharuco(markerCorners, markerIds, image, board, charucoCorners, charucoIds, cameraMatrix, distCoeffs);

不带标定的检测

std::vector<cv::Point2f> charucoCorners;
std::vector<int> charucoIds;
cv::aruco::interpolateCornersCharuco(markerCorners, markerIds, image, board, charucoCorners, charucoIds);

检测 ChArUco 角点的函数是 cv::aruco::interpolateCornersCharuco()。该函数返回插值的 Charuco 角点的数量。
std::vectorcv::Point2f charucoCorners : 检测到的角点的图像位置列表。
std::vector<int> charucoIds : charucoCorners 中每个检测到的角点的 ID。

如果提供了标定参数,则通过首先从 ArUco 标记估计一个粗略的姿态,然后将 ChArUco 角点重新投影回图像来插值 ChArUco 角点。

另一方面,如果未提供标定参数,则通过计算 ChArUco 平面和 ChArUco 图像投影之间的相应单应性来插值 ChArUco 角点。

使用单应性的主要问题是插值对图像畸变更加敏感。实际上,单应性仅通过每个 ChArUco 角的最近标记来执行,以减少畸变的影响。

在检测 ChArUco 板的标记时,特别是在使用单应性时,建议禁用标记的角点优化。原因在于,由于棋盘格方块的相邻性,子像素处理可能在角点位置产生重要偏差,这些偏差会传播到 ChArUco 角点的插值,导致结果不佳。

此外,仅返回其两个周围标记都被找到的角点。如果两个周围标记中有一个没有被检测到,通常意味着该区域存在某种遮挡或图像质量不佳。在任何情况下,最好不要考虑该角点,因为我们希望确保插值后的 ChArUco 角点非常准确。

在 ChArUco 角点插值后,进行子像素优化。

一旦我们插值了ChArUco角点,我们可能希望绘制它们以查看检测是否正确。这可以使用drawDetectedCornersCharuco()函数轻松完成:

cv::aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, color);
// imageCopy:是将绘制角落的图像(通常是检测到角落的同一图像)。  
// outputImage:是 inputImage 的克隆,并绘制了角落。  
// charucoCorners 和 charucoIds 是从 interpolateCornersCharuco() 函数中检测到的 Charuco 角落。  
// 最后一个参数是我们想要绘制角落的(可选)颜色,类型为 cv::Scalar。

在这里插入图片描述
在这里插入图片描述
在存在遮挡的情况下,如下图所示,尽管某些角落明显可见,但由于遮挡,并非所有周围标记都已被检测到,因此它们未被插值。
在这里插入图片描述

ChArUco检测源码(不使用标定参数)

最后,这是一个ChArUco检测的完整示例(未使用标定参数):

void detectCharucoBoardWithoutCalibration()
{cv::VideoCapture inputVideo;inputVideo.open(0);cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);cv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(5, 7, 0.04f, 0.02f, dictionary);cv::Ptr<cv::aruco::DetectorParameters> params = cv::aruco::DetectorParameters::create();params->cornerRefinementMethod = cv::aruco::CORNER_REFINE_NONE;while (inputVideo.grab()) {cv::Mat image, imageCopy;inputVideo.retrieve(image);image.copyTo(imageCopy);std::vector<int> markerIds;std::vector<std::vector<cv::Point2f> > markerCorners;cv::aruco::detectMarkers(image, board->dictionary, markerCorners, markerIds, params);//or//cv::aruco::detectMarkers(image, dictionary, markerCorners, markerIds, params);// if at least one marker detectedif (markerIds.size() > 0) {cv::aruco::drawDetectedMarkers(imageCopy, markerCorners, markerIds);std::vector<cv::Point2f> charucoCorners;std::vector<int> charucoIds;cv::aruco::interpolateCornersCharuco(markerCorners, markerIds, image, board, charucoCorners, charucoIds);// if at least one charuco corner detectedif (charucoIds.size() > 0)cv::aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));}cv::imshow("out", imageCopy);char key = (char)cv::waitKey(30);if (key == 27)break;}
}

完整的工作示例包含在 modules/aruco/samples/detect_board_charuco.cpp 中的 detect_board_charuco.cpp 文件中。
在这里插入图片描述

注意:示例现在通过 OpenCV 命令行解析器通过命令行输入。对于此文件,示例参数将如下所示:

-c="_path_/calib.txt" -dp="_path_/detector_params.yml" -w=5 -h=7 -sl=0.04 -ml=0.02 -d=10

这里的 calib.txt 是由 calibrate_camera_charuco.cpp 生成的输出文件。

5.ChArUco姿态估计(opencv 4.5.4)

ChArUco板的最终目标是以非常高的精度找到角点,用于高精度校准或姿态估计。
Aruco模块提供了一个函数,可以轻松执行ChArUco姿态估计。与GridBoard一样,CharucoBoard的坐标系位于板平面上,Z轴向外指向,并以板的左下角为中心。
用于姿态估计的函数是 estimatePoseCharucoBoard():

cv::aruco::estimatePoseCharucoBoard(charucoCorners, charucoIds, board, cameraMatrix, distCoeffs, rvec, tvec);
// charucoCorners 和 charucoIds 参数是从 interpolateCornersCharuco() 函数检测到的 Charuco 角点。
// 第三个参数是 CharucoBoard 对象。
// cameraMatrix 和 distCoeffs 是相机校准参数,对于姿态估计是必要的。
// rvec 和 tvec 参数是 Charuco 板的姿态输出。
// return:
// 如果姿态正确估计,则函数返回 true,否则返回 false。失败的主要原因是没有足够的角点用于姿态估计或它们在同一条线上。

可以使用 drawAxis() 绘制坐标轴,以检查姿态是否正确估计。结果将是:(X: 红色, Y: 绿色, Z: 蓝色)
在这里插入图片描述

ChArUco检测与姿态估计源码

void detectCharucoBoardWithCalibrationPose()
{cv::VideoCapture inputVideo;inputVideo.open(0);cv::Mat cameraMatrix, distCoeffs;std::string filename = "calib.txt";bool readOk = readCameraParameters(filename, cameraMatrix, distCoeffs);if (!readOk) {std::cerr << "Invalid camera file" << std::endl;} else {cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);cv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(5, 7, 0.04f, 0.02f, dictionary);cv::Ptr<cv::aruco::DetectorParameters> params = cv::aruco::DetectorParameters::create();while (inputVideo.grab()) {cv::Mat image;cv::Mat imageCopy;inputVideo.retrieve(image);image.copyTo(imageCopy);std::vector<int> markerIds;std::vector<std::vector<cv::Point2f> > markerCorners;cv::aruco::detectMarkers(image, board->dictionary, markerCorners, markerIds, params);// if at least one marker detectedif (markerIds.size() > 0) {cv::aruco::drawDetectedMarkers(imageCopy, markerCorners, markerIds);std::vector<cv::Point2f> charucoCorners;std::vector<int> charucoIds;cv::aruco::interpolateCornersCharuco(markerCorners, markerIds, image, board, charucoCorners, charucoIds, cameraMatrix, distCoeffs);// if at least one charuco corner detectedif (charucoIds.size() > 0) {cv::Scalar color = cv::Scalar(255, 0, 0);cv::aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, color);cv::Vec3d rvec, tvec;// cv::aruco::estimatePoseCharucoBoard(charucoCorners, charucoIds, board, cameraMatrix, distCoeffs, rvec, tvec);bool valid = cv::aruco::estimatePoseCharucoBoard(charucoCorners, charucoIds, board, cameraMatrix, distCoeffs, rvec, tvec);// if charuco pose is validif (valid)cv::aruco::drawAxis(imageCopy, cameraMatrix, distCoeffs, rvec, tvec, 0.1f);}}cv::imshow("out", imageCopy);char key = (char)cv::waitKey(30);if (key == 27)break;}}
}

modules/aruco/samples/detect_board_charuco.cpp 中包含了完整的 detect_board_charuco.cpp 示例。
注意:现在示例通过 OpenCV 命令行解析器从命令行接收输入。对于此文件,示例参数将如下所示:

"_path_/calib.txt" -dp="_path_/detector_params.yml" -w=5 -h=7 -sl=0.04 -ml=0.02 -d=10

6.源码分析(opencv 4.10.0)

https://docs.opencv.org/4.10.0/df/d4a/tutorial_charuco_detection.html

可以在 samples/cpp/tutorial_code/objectDetection/detect_board_charuco.cpp 中找到此代码。

功能包含:
1.创建一个 Charuco 板
2.在不进行相机标定的情况下检测 Charuco 角点
3.在进行相机标定和姿态估计的情况下检测 Charuco 角点

在这里插入图片描述

#include <opencv2/highgui.hpp>
//! [charucohdr]
#include <opencv2/objdetect/charuco_detector.hpp>
//! [charucohdr]
#include <vector>
#include <iostream>
#include "aruco_samples_utility.hpp"using namespace std;
using namespace cv;namespace {
const char* about = "Pose estimation using a ChArUco board";
const char* keys  ="{w        |       | Number of squares in X direction }""{h        |       | Number of squares in Y direction }""{sl       |       | Square side length (in meters) }""{ml       |       | Marker side length (in meters) }""{d        |       | dictionary: DICT_4X4_50=0, DICT_4X4_100=1, DICT_4X4_250=2,""DICT_4X4_1000=3, DICT_5X5_50=4, DICT_5X5_100=5, DICT_5X5_250=6, DICT_5X5_1000=7, ""DICT_6X6_50=8, DICT_6X6_100=9, DICT_6X6_250=10, DICT_6X6_1000=11, DICT_7X7_50=12,""DICT_7X7_100=13, DICT_7X7_250=14, DICT_7X7_1000=15, DICT_ARUCO_ORIGINAL = 16}""{cd       |       | Input file with custom dictionary }""{c        |       | Output file with calibrated camera parameters }""{v        |       | Input from video or image file, if ommited, input comes from camera }""{ci       | 0     | Camera id if input doesnt come from video (-v) }""{dp       |       | File of marker detector parameters }""{rs       |       | Apply refind strategy }";
}int main(int argc, char *argv[]) {CommandLineParser parser(argc, argv, keys);parser.about(about);if(argc < 6) {parser.printMessage();return 0;}//! [charuco_detect_board_full_sample]int squaresX = parser.get<int>("w");int squaresY = parser.get<int>("h");float squareLength = parser.get<float>("sl");float markerLength = parser.get<float>("ml");bool refine = parser.has("rs");int camId = parser.get<int>("ci");string video;if(parser.has("v")) {video = parser.get<string>("v");}Mat camMatrix, distCoeffs;readCameraParamsFromCommandLine(parser, camMatrix, distCoeffs);aruco::DetectorParameters detectorParams = readDetectorParamsFromCommandLine(parser);aruco::Dictionary dictionary = readDictionatyFromCommandLine(parser);if(!parser.check()) {parser.printErrors();return 0;}VideoCapture inputVideo;int waitTime = 0;if(!video.empty()) {inputVideo.open(video);} else {inputVideo.open(camId);waitTime = 10;}float axisLength = 0.5f * ((float)min(squaresX, squaresY) * (squareLength));// create charuco board objectaruco::CharucoBoard charucoBoard(Size(squaresX, squaresY), squareLength, markerLength, dictionary);// create charuco detectoraruco::CharucoParameters charucoParams;charucoParams.tryRefineMarkers = refine; // if tryRefineMarkers, refineDetectedMarkers() will be used in detectBoard()charucoParams.cameraMatrix = camMatrix; // cameraMatrix can be used in detectBoard()charucoParams.distCoeffs = distCoeffs; // distCoeffs can be used in detectBoard()aruco::CharucoDetector charucoDetector(charucoBoard, charucoParams, detectorParams);double totalTime = 0;int totalIterations = 0;while(inputVideo.grab()) {//! [inputImg]Mat image, imageCopy;inputVideo.retrieve(image);//! [inputImg]double tick = (double)getTickCount();vector<int> markerIds, charucoIds;vector<vector<Point2f> > markerCorners;vector<Point2f> charucoCorners;Vec3d rvec, tvec;//! [interpolateCornersCharuco]// detect markers and charuco cornerscharucoDetector.detectBoard(image, charucoCorners, charucoIds, markerCorners, markerIds);//! [interpolateCornersCharuco]//! [poseCharuco]// estimate charuco board posebool validPose = false;if(camMatrix.total() != 0 && distCoeffs.total() != 0 && charucoIds.size() >= 4) {Mat objPoints, imgPoints;charucoBoard.matchImagePoints(charucoCorners, charucoIds, objPoints, imgPoints);validPose = solvePnP(objPoints, imgPoints, camMatrix, distCoeffs, rvec, tvec);}//! [poseCharuco]double currentTime = ((double)getTickCount() - tick) / getTickFrequency();totalTime += currentTime;totalIterations++;if(totalIterations % 30 == 0) {cout << "Detection Time = " << currentTime * 1000 << " ms "<< "(Mean = " << 1000 * totalTime / double(totalIterations) << " ms)" << endl;}// draw resultsimage.copyTo(imageCopy);if(markerIds.size() > 0) {aruco::drawDetectedMarkers(imageCopy, markerCorners);}if(charucoIds.size() > 0) {//! [drawDetectedCornersCharuco]aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));//! [drawDetectedCornersCharuco]}if(validPose)cv::drawFrameAxes(imageCopy, camMatrix, distCoeffs, rvec, tvec, axisLength);imshow("out", imageCopy);if(waitKey(waitTime) == 27) break;}//! [charuco_detect_board_full_sample]return 0;
}

6.1 ChAruco板创建

aruco模块提供了cv::aruco::CharucoBoard类,该类代表一个Charuco Board,并继承自cv::aruco::Board类。

该类和其他ChArUco功能的定义在:

#include <opencv2/objdetect/charuco_detector.hpp>
要定义一个 cv::aruco::CharucoBoard,必须提供:1、X 和 Y 方向上的棋盘格数量。2、方块边长。3、标记边长。4、标记字典。5、所有标记的 ID。

至于 cv::aruco::GridBoard 对象,aruco 模块提供了创建 cv::aruco::CharucoBoard 的方法。可以使用 cv::aruco::CharucoBoard 构造函数轻松地从这些参数创建此对象:

aruco::Dictionary dictionary = readDictionatyFromCommandLine(parser);
cv::aruco::CharucoBoard board(Size(squaresX, squaresY), (float)squareLength, (float)markerLength, dictionary);
// 第一个参数:X和Y方向上棋盘格方块的数量。
// 第二个和第三个参数:分别是棋盘格方块的长度和aruco标记的长度。它们可以以任何单位提供,但要记住,这个板的估计姿态将以相同的单位进行测量(通常使用米)。
// 最后,提供了标记的字典。

每个标记的 ID 默认按升序分配,从 0 开始,就像在 cv::aruco::GridBoard 构造函数中一样。这可以通过访问 board.ids 中的 ids 向量轻松自定义,就像在 cv::aruco::Board 父类中一样。

  • 创建
    一旦我们有了cv::aruco::CharucoBoard对象,我们就可以创建一个图像来打印它。有两种方法可以做到这一点:
  • 1.使用脚本doc/patter_tools/gen_pattern.py,参见创建校准图案。
    PS:操作说明:https://docs.opencv.org/4.10.0/da/d0d/tutorial_camera_calibration_pattern.html
    在这里插入图片描述
  • 2.使用函数cv::aruco::CharucoBoard::generateImage()
    函数cv::aruco::CharucoBoard::generateImage()cv::aruco::CharucoBoard类中提供,可以使用以下代码调用:https://docs.opencv.org/4.10.0/d0/d3c/classcv_1_1aruco_1_1CharucoBoard.html
Mat boardImage;
Size imageSize;
imageSize.width = squaresX * squareLength + 2 * margins;
imageSize.height = squaresY * squareLength + 2 * margins;
board.generateImage(imageSize, boardImage, margins, borderBits);
// 第一个参数是输出图像的像素大小。如果这与棋盘格尺寸不成比例,它将居中显示在图像上。
// 第二个参数是带有 Charuco 棋盘的输出图像。
// 第三个参数是(可选的)像素边距,以确保没有任何标记接触图像边界。
// 最后,标记边框的大小类似于 cv::aruco::generateImageMarker() 函数。默认值为 1。

源码

samples/cpp/tutorial_code/objectDetection/ 中的 create_board_charuco.cpp 包含了一个完整的示例。
现在,create_board_charuco.cpp 通过命令行使用 cv::CommandLineParser 接收输入。对于此文件,示例参数如下:

"_output_path_/chboard.png" -w=5 -h=7 -sl=100 -ml=60 -d=10

在这里插入图片描述

#include <opencv2/highgui.hpp>
#include <opencv2/objdetect/charuco_detector.hpp>
#include <iostream>namespace {
const char *about = "Create a ChArUco board image";
//! [charuco_detect_board_keys]
const char *keys = "{@outfile |res.png| Output image }""{w        |  5    | Number of squares in X direction }""{h        |  7    | Number of squares in Y direction }""{sl       |  100  | Square side length (in pixels) }""{ml       |  60   | Marker side length (in pixels) }""{d        |       | dictionary: DICT_4X4_50=0, DICT_4X4_100=1, DICT_4X4_250=2,""DICT_4X4_1000=3, DICT_5X5_50=4, DICT_5X5_100=5, DICT_5X5_250=6, DICT_5X5_1000=7, ""DICT_6X6_50=8, DICT_6X6_100=9, DICT_6X6_250=10, DICT_6X6_1000=11, DICT_7X7_50=12,""DICT_7X7_100=13, DICT_7X7_250=14, DICT_7X7_1000=15, DICT_ARUCO_ORIGINAL = 16}""{cd       |       | Input file with custom dictionary }""{m        |       | Margins size (in pixels). Default is (squareLength-markerLength) }""{bb       | 1     | Number of bits in marker borders }""{si       | false | show generated image }";
} // namespace
//! [charuco_detect_board_keys]// 生成 5x7 标定板,每格边长 3.2mm,aruco边长 1.6mm
void createChArucoBoard()
{int squaresX     = 5;                           // X方向上棋盘格方块的数量int squaresY     = 7;                           // Y方向上棋盘格方块的数量int squareLength = 100;                         // 棋盘格方块的长度,单位:像素int markerLength = 60;                          // aruco标记的长度int margins      = squareLength - markerLength; // 格子和标记之间的边距int borderBits = 1;    // 标记边框的大小类似于 cv::aruco::generateImageMarker() 函数,默认值为 1bool showImage = true; // 是否显示生成的图像std::string pathOutImg = "chboard.png";//! [create_charucoBoard]cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);cv::aruco::CharucoBoard board(cv::Size(squaresX, squaresY), (float)squareLength, (float)markerLength, dictionary);//! [create_charucoBoard]// show created board//! [generate_charucoBoard]cv::Mat boardImage;cv::Size imageSize;imageSize.width  = squaresX * squareLength + 2 * margins;imageSize.height = squaresY * squareLength + 2 * margins;board.generateImage(imageSize, boardImage, margins, borderBits);//! [generate_charucoBoard]if (showImage) {cv::imshow("board", boardImage);cv::waitKey(0);}if (pathOutImg != "")cv::imwrite(pathOutImg, boardImage);
}

在这里插入图片描述

6.2 ChArUco板检测

当你检测到一个ChArUco板时,实际上是在检测该板上的每个棋盘角点。
ChArUco板上的每个角点都有一个唯一的标识符(id)分配。这些id从0到板上的总角点数。ChArUco板检测的步骤可以分解为以下步骤:

  • 1.输入图像
Mat image, imageCopy;
inputVideo.retrieve(image);

原始图像中要检测标记。该图像是在ChArUco角点上执行亚像素精炼所必需的。

  • 2.读取相机标定参数(仅用于带有相机标定的检测)
if(parser.has("c")) {bool readOk = readCameraParameters(parser.get<std::string>("c"), camMatrix, distCoeffs);if(!readOk) {throw std::runtime_error("Invalid camera file\n");}
}// readCameraParameters的参数为:
// 第一个参数是相机内参矩阵和畸变系数的路径。
// 第二和第三个参数分别是cameraMatrix和distCoeffs。
// return
// 此函数将这些参数作为输入,并返回一个布尔值,表示相机标定参数是否有效。进行未标定的charuco角点检测时,此步骤不是必需的。
  • 3.检测标记和从标记插值查看角点
    ChArUco角的检测基于先前检测到的标记。因此,首先检测标记,然后从标记中插值出ChArUco角。检测ChArUco角的方法是cv::aruco::CharucoDetector::detectBoard()
// detect markers and charuco corners
charucoDetector.detectBoard(image, charucoCorners, charucoIds, markerCorners, markerIds);// detectBoard的参数是:
// image - 输入图像。
// charucoCorners - 输出检测到的角点的图像位置列表。
// charucoIds - 输出charucoCorners中每个检测到的角点的ID。
// markerCorners - 检测到的标记角点的输入/输出向量。
// markerIds - 检测到的标记的标识符的输入/输出向量。

如果 markerCorners 和 markerIds 为空,函数将检测 aruco 标记和 ID。

如果提供了标定参数,ChArUco 角点将通过首先从 ArUco 标记估计一个粗略的姿态,然后将 ChArUco 角点重新投影回图像来进行插值。

另一方面,如果不提供标定参数,ChArUco 角点将通过计算 ChArUco 平面与 ChArUco 图像投影之间的对应单应性来进行插值。

使用单应性的主要问题是插值对图像失真更加敏感。实际上,单应性仅使用每个 ChArUco 角点最近的标记进行处理,以减少失真的影响。

在检测 ChArUco 板的标记时,尤其是在使用单应性时,建议禁用标记的角点细化。这是因为,由于棋盘格方块的相近,亚像素处理可能会导致角点位置的重要偏差,这些偏差会传递到 ChArUco 角点插值中,从而产生不良结果。

PS:为了避免偏差,棋盘格与Aruco标记之间的间距应大于一个标记模块的70%
example:假设棋盘格边长100,aruco标记60,则边距设置为 60 x 0.7 = 42,边距设为 100 - 60 = 40

此外,只有当两个相邻标记都被找到时,这些角点才会被返回。如果任何一个相邻标记未被检测到,通常意味着该区域存在遮挡或图像质量不佳。在任何情况下,最好不考虑那个角点,因为我们希望确保插值的ChArUco角点非常准确。
在插值ChArUco角点之后,会执行亚像素精化。
一旦我们插值了ChArUco角点,我们可能希望绘制它们以检查其检测是否正确。这可以轻松地使用cv::aruco::drawDetectedCornersCharuco()函数完成:

aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));// imageCopy是将要绘制角点的图像(通常是检测到角点的相同图像)。  
// outputImage将是带有绘制角点的inputImage的克隆。  
// charucoCorners和charucoIds是从cv::aruco::CharucoDetector::detectBoard()函数检测到的Charuco角点。  
// 最后一个参数是我们想要绘制角点的(可选)颜色,类型为cv::Scalar。

源码

在这里插入图片描述
在这里插入图片描述

完整的工作示例包含在 samples/cpp/tutorial_code/objectDetection/ 中的 detect_board_charuco.cpp 文件内。

现在,samples detect_board_charuco.cpp 通过 cv::CommandLineParser 从命令行获取输入。对于该文件,示例参数是:

-w=5 -h=7 -sl=0.04 -ml=0.02 -d=10 -v=/path_to_opencv/opencv/doc/tutorials/objdetect/charuco_detection/images/choriginal.jpg
  • 源代码:detect_board_charuco.cpp
#include <opencv2/highgui.hpp>
//! [charucohdr]
#include <opencv2/objdetect/charuco_detector.hpp>
//! [charucohdr]
#include <vector>
#include <iostream>
#include "aruco_samples_utility.hpp"using namespace std;
using namespace cv;namespace {
const char* about = "Pose estimation using a ChArUco board";
const char* keys  ="{w        |       | Number of squares in X direction }""{h        |       | Number of squares in Y direction }""{sl       |       | Square side length (in meters) }""{ml       |       | Marker side length (in meters) }""{d        |       | dictionary: DICT_4X4_50=0, DICT_4X4_100=1, DICT_4X4_250=2,""DICT_4X4_1000=3, DICT_5X5_50=4, DICT_5X5_100=5, DICT_5X5_250=6, DICT_5X5_1000=7, ""DICT_6X6_50=8, DICT_6X6_100=9, DICT_6X6_250=10, DICT_6X6_1000=11, DICT_7X7_50=12,""DICT_7X7_100=13, DICT_7X7_250=14, DICT_7X7_1000=15, DICT_ARUCO_ORIGINAL = 16}""{cd       |       | Input file with custom dictionary }""{c        |       | Output file with calibrated camera parameters }""{v        |       | Input from video or image file, if ommited, input comes from camera }""{ci       | 0     | Camera id if input doesnt come from video (-v) }""{dp       |       | File of marker detector parameters }""{rs       |       | Apply refind strategy }";
}int main(int argc, char *argv[]) {CommandLineParser parser(argc, argv, keys);parser.about(about);if(argc < 6) {parser.printMessage();return 0;}//! [charuco_detect_board_full_sample]int squaresX = parser.get<int>("w");int squaresY = parser.get<int>("h");float squareLength = parser.get<float>("sl");float markerLength = parser.get<float>("ml");bool refine = parser.has("rs");int camId = parser.get<int>("ci");string video;if(parser.has("v")) {video = parser.get<string>("v");}Mat camMatrix, distCoeffs;readCameraParamsFromCommandLine(parser, camMatrix, distCoeffs);aruco::DetectorParameters detectorParams = readDetectorParamsFromCommandLine(parser);aruco::Dictionary dictionary = readDictionatyFromCommandLine(parser);if(!parser.check()) {parser.printErrors();return 0;}VideoCapture inputVideo;int waitTime = 0;if(!video.empty()) {inputVideo.open(video);} else {inputVideo.open(camId);waitTime = 10;}float axisLength = 0.5f * ((float)min(squaresX, squaresY) * (squareLength));// create charuco board objectaruco::CharucoBoard charucoBoard(Size(squaresX, squaresY), squareLength, markerLength, dictionary);// create charuco detectoraruco::CharucoParameters charucoParams;charucoParams.tryRefineMarkers = refine; // if tryRefineMarkers, refineDetectedMarkers() will be used in detectBoard()charucoParams.cameraMatrix = camMatrix; // cameraMatrix can be used in detectBoard()charucoParams.distCoeffs = distCoeffs; // distCoeffs can be used in detectBoard()aruco::CharucoDetector charucoDetector(charucoBoard, charucoParams, detectorParams);double totalTime = 0;int totalIterations = 0;while(inputVideo.grab()) {//! [inputImg]Mat image, imageCopy;inputVideo.retrieve(image);//! [inputImg]double tick = (double)getTickCount();vector<int> markerIds, charucoIds;vector<vector<Point2f> > markerCorners;vector<Point2f> charucoCorners;Vec3d rvec, tvec;//! [interpolateCornersCharuco]// detect markers and charuco cornerscharucoDetector.detectBoard(image, charucoCorners, charucoIds, markerCorners, markerIds);//! [interpolateCornersCharuco]//! [poseCharuco]// estimate charuco board posebool validPose = false;if(camMatrix.total() != 0 && distCoeffs.total() != 0 && charucoIds.size() >= 4) {Mat objPoints, imgPoints;charucoBoard.matchImagePoints(charucoCorners, charucoIds, objPoints, imgPoints);validPose = solvePnP(objPoints, imgPoints, camMatrix, distCoeffs, rvec, tvec);}//! [poseCharuco]double currentTime = ((double)getTickCount() - tick) / getTickFrequency();totalTime += currentTime;totalIterations++;if(totalIterations % 30 == 0) {cout << "Detection Time = " << currentTime * 1000 << " ms "<< "(Mean = " << 1000 * totalTime / double(totalIterations) << " ms)" << endl;}// draw resultsimage.copyTo(imageCopy);if(markerIds.size() > 0) {aruco::drawDetectedMarkers(imageCopy, markerCorners);}if(charucoIds.size() > 0) {//! [drawDetectedCornersCharuco]aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));//! [drawDetectedCornersCharuco]}if(validPose)cv::drawFrameAxes(imageCopy, camMatrix, distCoeffs, rvec, tvec, axisLength);imshow("out", imageCopy);if(waitKey(waitTime) == 27) break;}//! [charuco_detect_board_full_sample]return 0;
}
  • 简化后
void detectCharucoPose()
{//! [charuco_detect_board_full_sample]int squaresX       = 5;     // X方向上棋盘格方块的数量int squaresY       = 7;     // Y方向上棋盘格方块的数量float squareLength = 0.04;  // 棋盘格方块的长度,单位:mfloat markerLength = 0.02;  // aruco标记的长度,单位:mbool refine        = false; // try to use refine board, default falsecv::Mat camMatrix  = (cv::Mat_<double>(3, 3) << 643.154541015625,0,639.794189453125,0,643.154541015625,357.18597412109375,0,0,1);cv::Mat distCoeffs = (cv::Mat_<double>(5, 1) << 0.0, 0.0, 0.0, 0.0, 0.0);cv::aruco::DetectorParameters detectorParams;cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);float axisLength = 0.5f * ((float)cv::min(squaresX, squaresY) * (squareLength));// create charuco board objectcv::aruco::CharucoBoard charucoBoard(cv::Size(squaresX, squaresY), squareLength, markerLength, dictionary);// create charuco detectorcv::aruco::CharucoParameters charucoParams;charucoParams.tryRefineMarkers =refine; // if tryRefineMarkers, refineDetectedMarkers() will be used in detectBoard()charucoParams.cameraMatrix = camMatrix;  // cameraMatrix can be used in detectBoard()charucoParams.distCoeffs   = distCoeffs; // distCoeffs can be used in detectBoard()cv::aruco::CharucoDetector charucoDetector(charucoBoard, charucoParams, detectorParams);double totalTime    = 0;int totalIterations = 0;//! [inputImg]cv::Mat image = cv::imread("choriginal.jpg");cv::Mat imageCopy;std::vector<int> markerIds, charucoIds;std::vector<std::vector<cv::Point2f>> markerCorners;std::vector<cv::Point2f> charucoCorners;cv::Vec3d rvec, tvec;//! [interpolateCornersCharuco]// detect markers and charuco cornerscharucoDetector.detectBoard(image, charucoCorners, charucoIds, markerCorners, markerIds);//! [interpolateCornersCharuco]//! [poseCharuco]// estimate charuco board posebool validPose = false;if (camMatrix.total() != 0 && distCoeffs.total() != 0 && charucoIds.size() >= 4) {cv::Mat objPoints, imgPoints;charucoBoard.matchImagePoints(charucoCorners, charucoIds, objPoints, imgPoints);validPose = solvePnP(objPoints, imgPoints, camMatrix, distCoeffs, rvec, tvec);}//! [poseCharuco]// draw resultsimage.copyTo(imageCopy);if (markerIds.size() > 0) {cv::aruco::drawDetectedMarkers(imageCopy, markerCorners);}if (charucoIds.size() > 0) {//! [drawDetectedCornersCharuco]cv::aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));//! [drawDetectedCornersCharuco]}if (validPose)cv::drawFrameAxes(imageCopy, camMatrix, distCoeffs, rvec, tvec, axisLength);cv::imshow("out", imageCopy);cv::waitKey(0);
}

输出和示例代码一致
在这里插入图片描述

6.3 ChArUco 姿态估计

ChArUco 棋盘的最终目标是为了高精度校准或姿态估计而非常准确地找到角点。

aruco 模块提供了一个函数,可以轻松地执行 ChArUco 姿态估计。与 cv::aruco::GridBoard 相似,cv::aruco::CharucoBoard 的坐标系统放置在棋盘平面上,Z 轴指向内部,并以棋盘的左下角为中心

在OpenCV 4.6.0之后,板的坐标系发生了一个不兼容的变化,现在坐标系被放置在板的平面上,Z轴指向平面(之前轴是指向平面外)。按顺时针顺序排列的objPoints对应于Z轴指向平面。按逆时针顺序排列的objPoints对应于Z轴指向平面外。
https://github.com/opencv/opencv_contrib/pull/3174

要对 Charuco 板执行姿态估计,应该使用 cv::aruco::CharucoBoard::matchImagePoints()cv::solvePnP():

// estimate charuco board pose
bool validPose = false;
if(camMatrix.total() != 0 && distCoeffs.total() != 0 && charucoIds.size() >= 4) {Mat objPoints, imgPoints;charucoBoard.matchImagePoints(charucoCorners, charucoIds, objPoints, imgPoints);validPose = solvePnP(objPoints, imgPoints, camMatrix, distCoeffs, rvec, tvec);
}
// charucoCorners和charucoIds参数是来自cv::aruco::CharucoDetector::detectBoard()函数检测到的charuco角点。
// cameraMatrix和distCoeffs是进行姿态估计所需的相机标定参数。最后,rvec和tvec参数是Charuco板的输出姿态。
// cv::solvePnP()在姿态正确估计时返回true,否则返回false。失败的主要原因是用于姿态估计的角点不足,或者它们在同一条线上。

轴可以使用 cv::drawFrameAxes() 方法绘制,以检查姿态是否正确估计。结果为:(X:红色,Y:绿色,Z:蓝色)
在这里插入图片描述
samples/cpp/tutorial_code/objectDetection/ 目录中包含了完整的 detect_board_charuco.cpp 示例。
现在的 samples detect_board_charuco.cpp 通过命令行使用 cv::CommandLineParser 接收输入。对于此文件,示例参数如下:

-w=5 -h=7 -sl=0.04 -ml=0.02 -d=10
-v=/path_to_opencv/opencv/doc/tutorials/objdetect/charuco_detection/images/choriginal.jpg
-c=/path_to_opencv/opencv/samples/cpp/tutorial_code/objectDetection/tutorial_camera_charuco.yml

https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%202/%E4%BD%BF%E7%94%A8ArUco%E5%92%8CChArUco%E8%BF%9B%E8%A1%8C%E7%9B%B8%E6%9C%BA%E6%A0%87%E5%AE%9A.md

相关文章:

【ChArUco Marker】标定板检测

目录 1.ChArUco介绍2.源码分析&#xff08;opencv 4.5.4&#xff09;3.ChAruco板创建&#xff08;opencv 4.5.4&#xff09;4.Charuco板检测&#xff08;opencv 4.5.4&#xff09;ChArUco检测源码&#xff08;不使用标定参数&#xff09; 5.ChArUco姿态估计&#xff08;opencv …...

Kafka | RabbitMQ | RocketMQ | ActiveMQ 的区别和入门案例

springboot&#xff0c;vue&#xff0c;springcloudalibaba课程视频&#xff0c;有需要可以看看 <!-- springboot&#xff0c;springboot整合redis&#xff0c;整合rocketmq视频&#xff1a; --> https://www.bilibili.com/video/BV1nkmRYSErk/?vd_source14d27ec13a473…...

TensorBoard

1、TensorFlow的TensorBoard TensorBoard是TensorFlow的一个组件&#xff0c;它提供了一个交互式的界面&#xff0c;用于可视化TensorFlow程序的训练过程和模型结构。 使用TensorBoard&#xff0c;你可以&#xff1a; 可视化训练过程中的各种指标&#xff0c;如损失函数、准…...

C# 中的异步编程:提升应用程序响应性和性能

C#中的异步编程&#xff08;Asynchronous Programming&#xff09;。异步编程是现代应用程序开发中非常重要的一个方面&#xff0c;它允许程序在等待长时间运行的操作&#xff08;如I/O操作、网络请求等&#xff09;时继续执行其他任务&#xff0c;从而提高应用程序的响应性和性…...

前端框架 React 与 Vue3对比 —— 技术选型

在进行前端框架React与Vue3的技术选型对比时&#xff0c;我们可以从以下几个方面进行综合考虑&#xff1a; 1. 性能比较 • Vue3 通过 Vite 打包工具实现了快速的开发和构建&#xff0c;同时使用了响应式系统和 Proxy 技术来提高数据响应速度。在大部分测试用例中&#xff0c;…...

虚拟机与Xshell5和Xftp4连接与虚拟机克隆

虚拟机与Xshell5和Xftp4连接与虚拟机克隆 虚拟机与Xshell5和Xftp4连接 虚拟机与Xshell5连接 下载Xshell5后启动出现如下界面&#xff0c;点击新建 新建会话输入虚拟机命名&#xff0c;如master&#xff0c;主机输入虚拟机IP&#xff0c;xxx.xxx.xxx.xxx然后确认&#xff0c;…...

华为USG系列防火墙 RESTCONF NAT配置 Python实现

目录 前言 文档下载 开启RESTCONF接口 Python 实现SNAT增删改查 查看nat映射列表 查看私网地址池 查看源地址池&#xff08;公网&#xff09; 查看nat映射规则 创建nat映射规则 创建私网地址池 创建源地址池 创建nat映射规则 修改NAT映射规则 删除NAT映射规则 …...

qemu安装arm64架构银河麒麟

qemu虚拟化软件&#xff0c;可以在一个平台上模拟另一个硬件平台&#xff0c;可以支持多种处理器架构。 一、安装 安装教程&#xff1a;https://blog.csdn.net/qq_36035382/article/details/125308044 下载链接&#xff1a;https://qemu.weilnetz.de/w64/2024/ 我下载的是 …...

深入解析 Spring 框架:核心特性与应用价值

1.什么是spring? Spring 是一个开源的 Java 应用框架&#xff0c;由 Pivotal Software 提供支持。它为开发基于 Java 的企业级应用提供了一整套基础设施支持。Spring 框架的核心功能是依赖注入&#xff08;Dependency Injection, DI&#xff09;&#xff0c;它帮助开发者实现…...

protobuf 报文编解码工具

QT实现的 protobuf 反序列化 & 序列化工具&#xff0c;版本号V1.2 下载链接&#xff1a;protobuf报文编解码工具资源-CSDN文库 源码github&#xff1a;ProtobufTool 使用说明: 1. 点击“加载proto文件”按钮&#xff0c;从本地选择 .proto文件 2. 选择消息名称&#xff…...

Milvus向量数据库06-RAG检索增强

Milvus向量数据库06-RAG检索增强 文章目录 Milvus向量数据库06-RAG检索增强1-学习目标2-参考网址3-执行过程记录1-到底什么是RAGRAG 的基本流程&#xff1a;为什么 RAG 优于传统的基于检索的方法&#xff1a;示例流程&#xff1a; 2-RAG和Elasticsearch对比3-RAG和向量数据库之…...

Unity3D下采集camera场景并推送RTMP服务实现毫秒级延迟直播

技术背景 好多开发者&#xff0c;希望我们能够分享下如何实现Unity下的camera场景采集并推送rtmp服务&#xff0c;然后低延迟播放出来。简单来说&#xff0c;在Unity 中实现采集 Camera 场景并推送RTMP的话&#xff0c;先是获取 Camera 场景数据&#xff0c;通过创建 RenderTe…...

标记数据集生成模型助力无数据情况下的大模型指令微调

在构建大模型应用时&#xff0c;通常有两种方式来改进效果&#xff0c;一种是构建外部知识库&#xff0c;利用RAG来完成。但RAG并不是万能的&#xff0c;对于特定领域的LLM应用&#xff0c;以及无需示例&#xff0c;就能完成特定任务等场合就需要进行微调。然而&#xff0c;微调…...

第六届地博会世界酒中国菜助力广州龙美地标美食公司推动地标发展

第六届知交会暨地博会&#xff1a;世界酒中国菜助力广州龙美地标美食公司推动地标产品创新发展 2024年12月9日至11日&#xff0c;第六届粤港澳大湾区知识产权交易博览会暨国际地理标志产品交易博览会在中新广州知识城盛大启幕。本届盛会吸引了全球众多知识产权领域的专业人士和…...

vue响应式原理

对于响应式原理&#xff0c;我们先了解vue是一个MVVM结构的框架&#xff1b;也就是数据层、视图层、数据-视图层&#xff1b;响应式的原理就是实现当数据更新时&#xff0c;视图层也要相应的更新&#xff0c;基于响应式原理我们可以使数据驱动视图的实现变得简单而高效 一、响…...

SwiftUI 列表(或 Form)子项中的 Picker 引起导航无法跳转的原因及解决

概述 在 SwiftUI 的界面布局中&#xff0c;列表&#xff08;List&#xff09;和 Form 是我们秃头码农们司空见惯的选择。不过大家是否知道&#xff1a;如果将 Picker 之类的视图嵌入到列表或 Form 的子项中会导致导航操作无法被触发。 从上图可以看到&#xff1a;当在 List 的…...

使用Allure作为测试报告生成器(Java+Selenium)

背景 JAVA项目中原先用Jenkinsseleniumselenium grid来日常测试UI并记录。 问题 当某一个testSuite失败时&#xff0c;当需要确认UI regression issue还是selenium test case自身的问题&#xff0c;需要去jenkins中查log&#xff0c;一般得到的是“Can not find element xxx…...

【论文阅读】处理器芯片敏捷设计方法:问题与挑战

作者&#xff1a;包云岗老师 包云岗老师是计算机体系结构方向的大牛&#xff0c;推动了体系结构方面的开源事业! 欢迎对本栏目感兴趣的人学习"一生一芯"~ 学习体会&#xff1a; 已有的软硬件生态系统和开发成本制约了对新结构的探索。但目前仍在几种路线上做尝试~ 1…...

系统内核自动处理 TCP 连接(自动发送 RST 数据包来重置连接)

使用原始套接字发送了一个 SYN 数据包后&#xff0c;对方发送了 SYN,ACK 数据包&#xff0c;但系统仍然会自动发送 RST 数据包。这通常是因为操作系统内核在处理 TCP 连接时的行为。 原因分析 内核处理 TCP 连接&#xff1a; 即使你使用了原始套接字来发送和接收数据包&#x…...

VLDB 2024 | 时空数据(Spatial-temporal)论文总结

VLDB 2024于2024年8月26号-8月30号在中国广州举行。 本文总结了VLDB 2024有关时空数据&#xff08;time series data&#xff09;的相关论文&#xff0c;主要包含如有疏漏&#xff0c;欢迎大家补充。 &#x1f31f;【紧跟前沿】“时空探索之旅”与你一起探索时空奥秘&#xf…...

以ATTCK为例构建网络安全知识图

ATT&CK&#xff08;Adversarial Tactics, Techniques, and Common Knowledge &#xff09;是一个攻击行为知识库和模型&#xff0c;主要应用于评估攻防能力覆盖、APT情报分析、威胁狩猎及攻击模拟等领域。本文简单介绍ATT&CK相关的背景概念&#xff0c;并探讨通过ATT&a…...

Qt初识_对象树

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 Qt初识_对象树 收录于专栏【Qt开发】 本专栏旨在分享学习Qt的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 什么是对象树 为什么要引…...

规范秩相关信息搜集Day2

系列博客目录 文章目录 系列博客目录1.A Survey on Tensor Techniques and Applications in Machine Learning2.有没有研究低秩矩阵有利于分类的计算机方面的论文呢3.Image classification based on low-rank matrix recovery and Naive Bayes collaborative representatio 基于…...

【unity小技巧】分享vscode如何进行unity开发,且如何开启unity断点调试模式,并进行unity断点调试(2024年最新的方法,实测有效)

文章目录 前言一、前置条件1、已安装Visual Studio Code&#xff0c;并且unity首选项>外部工具>外部脚本编辑器选择为Visual Studio Code [版本号]&#xff0c;2、在Visual Studio Code扩展中搜索Unity&#xff0c;并安装3、同时注意这个插件下面的描述&#xff0c;需要根…...

交换瓶子(图论 贪心)

1224. 交换瓶子 - AcWing题库 把每一个瓶子看成一个点&#xff0c;从每个瓶子向他应该在的那个位置的瓶子连一条边 通过这个方式&#xff0c;我们就可以连出n条边 观察可以发现这些图有特点&#xff1a; n个点 连成n条边 因为每个点会指向它应该在的位置的那个点&#xff…...

汽车升级到底应不应该设置“可取消“功能

最近&#xff0c;汽车OTA&#xff08;Over-the-Air&#xff09;升级频频成为车主讨论的热点。有些车主反映&#xff0c;一些升级增加了实用功能&#xff0c;而另一些却让体验变得复杂甚至带来不便。于是&#xff0c;大家不禁发问&#xff1a;汽车升级功能究竟应不应该允许“可取…...

Mac电脑钓鱼到拿下核心权限

目录 一. 前言 二. PKG后门制作阶段 2.1 环境准备 2.2 制作过程 2.3 成功上线 三 . 浏览器密码抓取 四. 权限维持 1. 手动权限维持 2. MSF自动化维持 五. 参考文章 一. 前言 攻防对抗强度和难度日益演进,传统的渗透测试思路成本逐渐提高,钓鱼已经成为当下攻击者最常…...

Docker多架构镜像构建踩坑记

背景 公司为了做信创项目的亮点&#xff0c;需要将现有的一套在X86上运行的应用系统迁移到ARM服务器上运行&#xff0c;整个项目通过后端Java&#xff0c;前端VUEJS开发通过CICD做成Docker镜像在K8S里面运行。但是当前的CICD产品不支持ARM的镜像构建&#xff0c;于是只能手工构…...

docker 架构详解

Docker架构是基于客户端-服务器&#xff08;C/S&#xff09;模式的&#xff0c;包含多个关键组件&#xff0c;以确保容器化应用的高效构建、管理和运行。以下是对Docker架构的详细解析&#xff1a; Docker 架构概述 Docker 架构采用客户端-服务器&#xff08;C/S&#xff09;…...

05-标准库开发-STM32-IIC协议

七、STM32中IIC协议 概述 Inter-Integrated Circuit (IIC)&#xff0c;也常称为I2C&#xff08;I squared C&#xff09;&#xff0c;是一种同步、串行、半双工通信总线协议。它主要用于连接低速外围设备到处理器或微控制器上&#xff0c;如MPU6050姿态传感器、OLED显示屏、存…...

vue 封装全局过滤器

1.找到utils下创建fifilter.js 一些常用的过滤方法 export const filters {//url解码urlCode: value > {if (!value) return let v decodeURIComponent(value)let bigIndex v.lastIndexOf(/)let endIndex v.lastIndexOf(.)let url v.substring(bigIndex 1, endIndex)…...

【PlantUML系列】流程图(四)

目录 目录 一、基础用法 1.1 开始和结束 1.2 操作步骤 1.3 条件判断 1.4 并行处理 1.5 循环 1.6 分区 1.7 泳道 一、基础用法 1.1 开始和结束 开始一般使用start关键字&#xff1b;结束一般使用stop/end关键字。基础用法包括&#xff1a; start ... stopstart ...…...

MATLAB中的合并分类数组

目录 创建分类数组 串联分类数组 创建具有不同类别的分类数组 串联具有不同类别的数组 分类数组的并集 此示例演示了如何合并两个分类数组。 创建分类数组 创建分类数组 A&#xff0c;其中包含教室 A 中的 25 个学生的首选午餐饮料。 rng(default) A randi(3,[25,1]); …...

流编辑器sed(stream editor)

一.sed简介 sed是一种流编辑器&#xff0c;处理时&#xff0c;把当前处理的行存储在临时缓冲区中&#xff0c;称为模式空间&#xff0c;接着用sed命令处 理缓冲区中的内容&#xff0c;处理完成后&#xff0c;把缓冲区的内容送往屏幕。接着处理下一行&#xff0c;这样不断重复&…...

R语言的数据结构--矩阵

【图书推荐】《R语言医学数据分析实践》-CSDN博客 《R语言医学数据分析实践 李丹 宋立桓 蔡伟祺 清华大学出版社9787302673484》【摘要 书评 试读】- 京东图书 (jd.com) R语言医学数据分析实践-R语言的数据结构-CSDN博客 矩阵是一个二维数组&#xff0c;矩阵中的元素都具有相…...

使用 Python 爬取某网站简历模板(bs4/lxml+协程)

使用 Python 爬取站长素材简历模板 简介 在本教程中&#xff0c;我们将学习如何使用 Python 来爬取站长素材网站上的简历模板。我们将使用requests和BeautifulSoup库来发送 HTTP 请求和解析 HTML 页面。本教程将分为两个部分&#xff1a;第一部分是使用BeautifulSoup的方法&am…...

19 go语言(golang) - 通过反射手动实现json序列化

一、json 在 Go 语言中&#xff0c;JSON 序列化和反序列化通常通过标准库 encoding/json 来实现。这个包提供了简单易用的接口来将 Go 数据结构转换为 JSON 格式字符串&#xff08;序列化&#xff09;&#xff0c;以及从 JSON 字符串解析出 Go 数据结构&#xff08;反序列化&a…...

Scala:隐式转换

隐式转换的定义 //隐式转换&#xff1a;编译器自动滴&#xff0c;偷偷滴&#xff0c;把数据A->B object test04 {def main(args: Array[String]): Unit {val i:Int1//把Int类型&#xff0c;转化成Double类型//Int -> Double//隐式转换失败val b:Double1//隐式转换失败v…...

UVM之寄存器模型生成

1.采用python脚本生成寄存器模型 首先用excel表格做好寄存器描述 然后编写脚本生成.ralf文件 &#xff08;1&#xff09;首先通过openpyxl读取EXCEL表格&#xff0c; workbook openpyxl.load_workbook(reg.xlsx) # 返回一个workbook数据类型的值 &#xff08;2&#xff…...

PL/SQL批量生成数据

在PL/SQL中生成大量模拟数据&#xff0c;特别是当你需要生成大量记录&#xff08;如1亿条&#xff09;时&#xff0c;有几种常见的方式可以提高生成数据的效率和性能。以下是一些常用的方法和最佳实践&#xff1a; 1. 使用PL/SQL块批量生成数据 PL/SQL块可以通过循环生成大量…...

Xcode模拟器运行报错:The request was denied by service delegate

Xcode模拟器运行报错&#xff1a;The request was denied by service delegate 造成的原因: &#xff08;1&#xff09;新的苹果M系列芯片的Mac电脑 &#xff08;2&#xff09;此电脑首次安装启动Xcode的应用程序 &#xff08;3&#xff09;此电脑未安装Rosetta 2 解决方法: …...

2024小迪安全基础入门第十课

目录 一、传输格式&数据-类型&编码&算法 1. 传输格式&#xff1a; 2. 传输数据&#xff1a; 3. 影响与渗透测试&#xff1a; #传输格式 #传输数据 二、密码存储&混淆-不可逆&非对称性 1. 密码存储&#xff1a; 2. 密码存储的影响&#xff1a; 3.…...

Redisson分布式限流器

Redisson分布式限流器 一、使用1.1、方法1.2、示例 二、原理2.1、设置限流器2.2、获取令牌 三、总结 最近有需求在做分布式限流&#xff0c;调研的限流框架大概有&#xff1a; 1、spring cloud gateway集成redis限流,但属于网关层限流 2、阿里Sentinel,功能强大、带监控平台 …...

xvisor调试记录

Xvisor是一种开源hypervisor,旨在提供完整、轻量、移植且灵活的虚拟化解决方案,属于type-1类型的虚拟机,可以直接在裸机上启动。 启动xvisor步骤: 1、搭建riscv编译环境 首先从github上下载riscv-gnu-toolchain很费劲,建议直接从国内的源下载 git clone https://gitee…...

Android问题记录 - Inconsistent JVM-target compatibility detected for tasks

文章目录 前言开发环境问题描述问题分析解决方案补充内容最后 前言 前段时间升级Android Studio后修复了一堆问题&#xff0c;详情请看&#xff1a;Android问题记录 - 适配Android Studio Ladybug/Java 21/AGP 8.0&#xff08;持续更新&#xff09;。我以为问题已经全部解决了…...

【Python系列】使用 `psycopg2` 连接 PostgreSQL 数据库

???欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老…...

家庭路由器跑PCND的优点

在当今数字化的时代&#xff0c;PCDN&#xff08;Peer-to-Peer Content Delivery Network&#xff09;技术逐渐走入人们的视野&#xff0c;有人考虑在家庭路由器上跑PCDN&#xff0c;下面是优点&#xff1a; 1.资源利用最大化 家庭网络在很多时候存在闲置的带宽和计算资源。通…...

ASP.NET Core API + MySql

环境 数据库&#xff1a; mysql8.0 后端&#xff1a; vs2022 ASP.NET Core API .net 8 前端&#xff1a; Hbuilderx bootstrap 5.3.0 jquery v3.7.1 bootstrap-table 1.23.5 创建项目 添加资源包 AutoMapper Microsoft.EntityFrameworkCore.Tools 8.0.0 Pomelo.EntityFramew…...

torch.optim.lr_scheduler.ReduceLROnPlateau

torch.optim.lr_scheduler.ReduceLROnPlateau 是 PyTorch 中的一种学习率调度器&#xff0c;主要用于在模型训练过程中根据某些指标&#xff08;如验证损失&#xff09;动态调整学习率。它是一种基于性能指标动态调整学习率的策略&#xff0c;而不是预定义的固定时间调整。 主要…...

Dubbo

官方文档&#xff1a; Java SDK 手册 | Apache Dubbo 一 RPC及Dubbo 1 什么是RPC dubbo是⼀款⾼性能的rpc框架。什么是rpc呢&#xff1f; rpc是⼀种协议&#xff1a;是⼀种远程过程调⽤&#xff08;remote procudure call&#xff09;协议 rpc协议是在应⽤层之上的协议&…...