【ArUco boards】标定板检测
之前定位用的Charuco标定板做的(https://blog.csdn.net/qq_45445740/article/details/143897238),因为实际工况中对标定板的尺寸有要求,大概是3cm*2cm这个尺寸,加上选用的是ChAruco标定板,导致每一个aruco码都做的很小大概是一两个毫米,且只能选择是4x4字典的图案,再大一点如6x6字典都放不下,会造成精度方面的下降。
基于以上问题,想试下ArucoBoard标定板,和Charuco标定板一样的尺寸下,图像可以做到6x6的字典,并且每个aruco标记会比原来ChAruco上的aruco更大,理论上应该会提高些精度。
OpenCV:4.10.0
目录
- 1.ArUco boards介绍
- 2.标定板检测
- Q1.为什么自己实际生成的DICT_6X6_250的aruco图案和文档中的不同?
- Q2.为什么自己按照示例实现的位姿估计和官方文档中的识别的位姿估计原点不一样?
- 3.网格板
- 4.优化标记检测
- 4.1 官方文档说明
- 示例
- 4.2 关于refineDetectedMarkers()函数
- 4.2.1 refineDetectedMarkers()函数说明
- 4.2.2 detectMarkers 与 detectMarkers() 的区别和关系
- 4.2.3 是否需要 refineDetectedMarkers
- 5.opencv4.10.0 代码
- 5.1 创建 arucoBoard
- 5.2 arucoBoard 位姿估计
https://docs.opencv.org/4.10.0/db/da9/tutorial_aruco_board_detection.html
1.ArUco boards介绍
ArUco板是一组标记,其作用类似于单个标记,因为它为相机提供了一个单一的姿势。
最受欢迎的板是所有标记都在同一平面上的板,因为它很容易打印:
然而,棋盘不限于这种排列方式,并且可以表示任何二维或三维布局。
一个板和一组独立标记的区别在于,板中标记之间的相对位置是先验已知的。这使得所有标记的角点都可以用来估计相机相对于整个板的姿态。
当你使用一组独立的标记时,你可以单独估计每个标记的姿态,因为你不知道环境中标记的相对位置。
使用板子的主要好处有:
- 1.姿态估计更加通用。只需要一些标记就可以进行姿态估计。因此,即使存在遮挡或部分视图,也可以计算姿态。
- 2.获得的姿态通常更准确,因为使用了更多的点对应关系(标记角点)。
2.标定板检测
标定检测与标准标记检测类似,唯一的区别在于姿态估计步骤。事实上,要使用标记板,应该在估计板姿态之前先进行标准的标记检测。
要对ArucoBoard标定板进行姿态估计,应该使用 solvePnP()
函数,官方示例: samples/cpp/tutorial_code/objectDetection/detect_board.cpp
- detect_board.cpp
#include <iostream>
#include <vector>
#include <opencv2/highgui.hpp>
#include <opencv2/objdetect/aruco_detector.hpp>
#include "aruco_samples_utility.hpp"using namespace std;
using namespace cv;namespace {
const char* about = "Pose estimation using a ArUco Planar Grid board";//! [aruco_detect_board_keys]
const char* keys ="{w | | Number of squares in X direction }""{h | | Number of squares in Y direction }""{l | | Marker side length (in pixels) }""{s | | Separation between two consecutive markers in the grid (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 }""{c | | Output file with calibrated camera parameters }""{v | | Input from video or image file, if omitted, 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 }""{r | | show rejected candidates too }";
}
//! [aruco_detect_board_keys]int main(int argc, char *argv[]) {CommandLineParser parser(argc, argv, keys);parser.about(about);if(argc < 7) {parser.printMessage();return 0;}//! [aruco_detect_board_full_sample]int markersX = parser.get<int>("w");int markersY = parser.get<int>("h");float markerLength = parser.get<float>("l");float markerSeparation = parser.get<float>("s");bool showRejected = parser.has("r");bool refindStrategy = parser.has("rs");int camId = parser.get<int>("ci");Mat camMatrix, distCoeffs;readCameraParamsFromCommandLine(parser, camMatrix, distCoeffs);aruco::Dictionary dictionary = readDictionatyFromCommandLine(parser);aruco::DetectorParameters detectorParams = readDetectorParamsFromCommandLine(parser);String video;if(parser.has("v")) {video = parser.get<String>("v");}if(!parser.check()) {parser.printErrors();return 0;}aruco::ArucoDetector detector(dictionary, detectorParams);VideoCapture inputVideo;int waitTime;if(!video.empty()) {inputVideo.open(video);waitTime = 0;} else {inputVideo.open(camId);waitTime = 10;}float axisLength = 0.5f * ((float)min(markersX, markersY) * (markerLength + markerSeparation) +markerSeparation);// Create GridBoard object//! [aruco_create_board]aruco::GridBoard board(Size(markersX, markersY), markerLength, markerSeparation, dictionary);//! [aruco_create_board]// Also you could create Board object//vector<vector<Point3f> > objPoints; // array of object points of all the marker corners in the board//vector<int> ids; // vector of the identifiers of the markers in the board//aruco::Board board(objPoints, dictionary, ids);double totalTime = 0;int totalIterations = 0;while(inputVideo.grab()) {Mat image, imageCopy;inputVideo.retrieve(image);double tick = (double)getTickCount();vector<int> ids;vector<vector<Point2f>> corners, rejected;Vec3d rvec, tvec;//! [aruco_detect_and_refine]// Detect markersdetector.detectMarkers(image, corners, ids, rejected);// Refind strategy to detect more markersif(refindStrategy)detector.refineDetectedMarkers(image, board, corners, ids, rejected, camMatrix,distCoeffs);//! [aruco_detect_and_refine]// Estimate board poseint markersOfBoardDetected = 0;if(!ids.empty()) {// Get object and image points for the solvePnP functioncv::Mat objPoints, imgPoints;board.matchImagePoints(corners, ids, objPoints, imgPoints);// Find posecv::solvePnP(objPoints, imgPoints, camMatrix, distCoeffs, rvec, tvec);markersOfBoardDetected = (int)objPoints.total() / 4;}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(!ids.empty())aruco::drawDetectedMarkers(imageCopy, corners, ids);if(showRejected && !rejected.empty())aruco::drawDetectedMarkers(imageCopy, rejected, noArray(), Scalar(100, 0, 255));if(markersOfBoardDetected > 0)cv::drawFrameAxes(imageCopy, camMatrix, distCoeffs, rvec, tvec, axisLength);imshow("out", imageCopy);char key = (char)waitKey(waitTime);if(key == 27) break;//! [aruco_detect_board_full_sample]}return 0;
}
参数说明:
objPoints
,imgPoints
:对象点和图像点,与**cv::aruco::GridBoard::matchImagePoints()匹配,后者又将来自cv::aruco::ArucoDetector::detectMarkers()**函数检测到的标记的markerCorners和markerIds结构作为输入。board
:定义棋盘布局及其ID的cv::aruco::Board对象。cameraMatrix
和distCoeffs
:用于姿态估计的必要相机校准参数。rvec
和tvec
:棋盘估计的姿态。如果非空,则将其视为初始猜测。- 该函数返回用于估计棋盘姿态的标记总数。
drawFrameAxes()
函数可用于检查获得的姿态。例如:
这是一张棋盘部分被遮挡的例子:
正如所观察到的,尽管一些标记未被检测到,但仍然可以从其余标记估计板位姿。
这些示例现在通过 cv::CommandLineParser 经由命令行获取输入,示例参数如下所示:
-w=5 -h=7 -l=100 -s=10
-v=/path_to_opencv/opencv/doc/tutorials/objdetect/aruco_board_detection/gboriginal.jpg
-c=/path_to_opencv/opencv/samples/cpp/tutorial_code/objectDetection/tutorial_camera_params.yml
-cd=/path_to_opencv/opencv/samples/cpp/tutorial_code/objectDetection/tutorial_dict.yml
Q1.为什么自己实际生成的DICT_6X6_250的aruco图案和文档中的不同?
我发现我生成的arucoboard和官方文档中的图案始终对应不上,原因是作者用的自定义的图案:
Q2.为什么自己按照示例实现的位姿估计和官方文档中的识别的位姿估计原点不一样?
- 官方的图:坐标原点是在id30的aruco码上面,id30、id31、id32、id33、id34是红色的X轴,id30、id25、id20、id15、id10、id5、id0是绿色的Y轴。
- 自己识别的:坐标原点是在id0的aruco码上面,id0、id1、id2、id3、id4是红色的X轴,id0、id5、id10、id15、id20、id25、id30是绿色的Y轴。
没明白。。。
3.网格板
创建 cv::aruco::Board
对象需要指定环境中每个标记的角点位置。 然而在许多情况下,板仅仅是同一平面上且呈网格布局的一组标记,因此可以很容易地打印和使用。
幸运的是,aruco模块提供了基本的功能来轻松创建和打印这些类型的标记。
cv::aruco::GridBoard
类是一个专门的类,它继承自cv::aruco::Board
类,表示一个所有标记位于同一平面且以网格布局排列的Board,如下图所示:
具体来说,网格板上的坐标系位于板平面内,以板的左下角为中心,Z轴指向外,如下图所示(X:红色,Y:绿色,Z:蓝色):
可以使用以下参数定义 cv::aruco::GridBoard
对象:
- X 方向的标记数量。
- Y 方向的标记数量。
- 标记边的长度。
- 标记间距的长度。
- 标记的字典。
- 所有标记的 ID (X*Y 个标记)。
可以使用 cv::aruco::GridBoard
构造函数从这些参数轻松创建此对象:
aruco::GridBoard board(Size(markersX, markersY), markerLength, markerSeparation, dictionary);// 参数说明:
// markersX和markersY:分别是X和Y方向上的标记数量。
//markerLength和markerSeparation:分别是标记长度和标记间距,它们可以以任何单位提供,但请记住,此板的估计姿势将以相同的单位测量(通常使用米)。
// dictionary:提供了标记的字典。
所以,这个板子将由5x7=35个标记组成。每个标记的ID默认按升序分配,从0开始,所以它们将是0, 1, 2, …, 34。
在创建网格板之后,我们可能需要打印并使用它。有两种方法可以做到这一点:
- 1.通过使用脚本
doc/patter_tools/gen_pattern.py
,请参阅创建校准图案。
- gen_pattern.py
#!/usr/bin/env python"""gen_pattern.py
Usage example:
python gen_pattern.py -o out.svg -r 11 -c 8 -T circles -s 20.0 -R 5.0 -u mm -w 216 -h 279
-o, --output - output file (default out.svg)
-r, --rows - pattern rows (default 11)
-c, --columns - pattern columns (default 8)
-T, --type - type of pattern: circles, acircles, checkerboard, radon_checkerboard, charuco_board. default circles.
-s, --square_size - size of squares in pattern (default 20.0)
-R, --radius_rate - circles_radius = square_size/radius_rate (default 5.0)
-u, --units - mm, inches, px, m (default mm)
-w, --page_width - page width in units (default 216)
-h, --page_height - page height in units (default 279)
-a, --page_size - page size (default A4), supersedes -h -w arguments
-m, --markers - list of cells with markers for the radon checkerboard
-p, --aruco_marker_size - aruco markers size for ChAruco pattern (default 10.0)
-f, --dict_file - file name of custom aruco dictionary for ChAruco pattern
-H, --help - show help
"""import argparse
import numpy as np
import json
import gzip
from svgfig import *class PatternMaker:def __init__(self, cols, rows, output, units, square_size, radius_rate, page_width, page_height, markers, aruco_marker_size, dict_file):self.cols = colsself.rows = rowsself.output = outputself.units = unitsself.square_size = square_sizeself.radius_rate = radius_rateself.width = page_widthself.height = page_heightself.markers = markersself.aruco_marker_size = aruco_marker_size #for charuco boards onlyself.dict_file = dict_fileself.g = SVG("g") # the svg group containerdef make_circles_pattern(self):spacing = self.square_sizer = spacing / self.radius_ratepattern_width = ((self.cols - 1.0) * spacing) + (2.0 * r)pattern_height = ((self.rows - 1.0) * spacing) + (2.0 * r)x_spacing = (self.width - pattern_width) / 2.0y_spacing = (self.height - pattern_height) / 2.0for x in range(0, self.cols):for y in range(0, self.rows):dot = SVG("circle", cx=(x * spacing) + x_spacing + r,cy=(y * spacing) + y_spacing + r, r=r, fill="black", stroke="none")self.g.append(dot)def make_acircles_pattern(self):spacing = self.square_sizer = spacing / self.radius_ratepattern_width = ((self.cols-1.0) * 2 * spacing) + spacing + (2.0 * r)pattern_height = ((self.rows-1.0) * spacing) + (2.0 * r)x_spacing = (self.width - pattern_width) / 2.0y_spacing = (self.height - pattern_height) / 2.0for x in range(0, self.cols):for y in range(0, self.rows):dot = SVG("circle", cx=(2 * x * spacing) + (y % 2)*spacing + x_spacing + r,cy=(y * spacing) + y_spacing + r, r=r, fill="black", stroke="none")self.g.append(dot)def make_checkerboard_pattern(self):spacing = self.square_sizexspacing = (self.width - self.cols * self.square_size) / 2.0yspacing = (self.height - self.rows * self.square_size) / 2.0for x in range(0, self.cols):for y in range(0, self.rows):if x % 2 == y % 2:square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing,height=spacing, fill="black", stroke="none")self.g.append(square)@staticmethoddef _make_round_rect(x, y, diam, corners=("right", "right", "right", "right")):rad = diam / 2cw_point = ((0, 0), (diam, 0), (diam, diam), (0, diam))mid_cw_point = ((0, rad), (rad, 0), (diam, rad), (rad, diam))res_str = "M{},{} ".format(x + mid_cw_point[0][0], y + mid_cw_point[0][1])n = len(cw_point)for i in range(n):if corners[i] == "right":res_str += "L{},{} L{},{} ".format(x + cw_point[i][0], y + cw_point[i][1],x + mid_cw_point[(i + 1) % n][0], y + mid_cw_point[(i + 1) % n][1])elif corners[i] == "round":res_str += "A{},{} 0,0,1 {},{} ".format(rad, rad, x + mid_cw_point[(i + 1) % n][0],y + mid_cw_point[(i + 1) % n][1])else:raise TypeError("unknown corner type")return res_strdef _get_type(self, x, y):corners = ["right", "right", "right", "right"]is_inside = Trueif x == 0:corners[0] = "round"corners[3] = "round"is_inside = Falseif y == 0:corners[0] = "round"corners[1] = "round"is_inside = Falseif x == self.cols - 1:corners[1] = "round"corners[2] = "round"is_inside = Falseif y == self.rows - 1:corners[2] = "round"corners[3] = "round"is_inside = Falsereturn corners, is_insidedef make_radon_checkerboard_pattern(self):spacing = self.square_sizexspacing = (self.width - self.cols * self.square_size) / 2.0yspacing = (self.height - self.rows * self.square_size) / 2.0for x in range(0, self.cols):for y in range(0, self.rows):if x % 2 == y % 2:corner_types, is_inside = self._get_type(x, y)if is_inside:square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing,height=spacing, fill="black", stroke="none")else:square = SVG("path", d=self._make_round_rect(x * spacing + xspacing, y * spacing + yspacing,spacing, corner_types), fill="black", stroke="none")self.g.append(square)if self.markers is not None:r = self.square_size * 0.17pattern_width = ((self.cols - 1.0) * spacing) + (2.0 * r)pattern_height = ((self.rows - 1.0) * spacing) + (2.0 * r)x_spacing = (self.width - pattern_width) / 2.0y_spacing = (self.height - pattern_height) / 2.0for x, y in self.markers:color = "black"if x % 2 == y % 2:color = "white"dot = SVG("circle", cx=(x * spacing) + x_spacing + r,cy=(y * spacing) + y_spacing + r, r=r, fill=color, stroke="none")self.g.append(dot)@staticmethoddef _create_marker_bits(markerSize_bits, byteList):marker = np.zeros((markerSize_bits+2, markerSize_bits+2))bits = marker[1:markerSize_bits+1, 1:markerSize_bits+1]for i in range(markerSize_bits):for j in range(markerSize_bits):bits[i][j] = int(byteList[i*markerSize_bits+j])return markerdef make_charuco_board(self):if (self.aruco_marker_size>self.square_size):print("Error: Aruco marker cannot be lager than chessboard square!")returnif (self.dict_file.split(".")[-1] == "gz"):with gzip.open(self.dict_file, 'r') as fin:json_bytes = fin.read()json_str = json_bytes.decode('utf-8')dictionary = json.loads(json_str)else:f = open(self.dict_file)dictionary = json.load(f)if (dictionary["nmarkers"] < int(self.cols*self.rows/2)):print("Error: Aruco dictionary contains less markers than it needs for chosen board. Please choose another dictionary or use smaller board than required for chosen board")returnmarkerSize_bits = dictionary["markersize"]side = self.aruco_marker_size / (markerSize_bits+2)spacing = self.square_sizexspacing = (self.width - self.cols * self.square_size) / 2.0yspacing = (self.height - self.rows * self.square_size) / 2.0ch_ar_border = (self.square_size - self.aruco_marker_size)/2if ch_ar_border < side*0.7:print("Marker border {} is less than 70% of ArUco pin size {}. Please increase --square_size or decrease --marker_size for stable board detection".format(ch_ar_border, int(side)))marker_id = 0for y in range(0, self.rows):for x in range(0, self.cols):if x % 2 == y % 2:square = SVG("rect", x=x * spacing + xspacing, y=y * spacing + yspacing, width=spacing,height=spacing, fill="black", stroke="none")self.g.append(square)else:img_mark = self._create_marker_bits(markerSize_bits, dictionary["marker_"+str(marker_id)])marker_id +=1x_pos = x * spacing + xspacingy_pos = y * spacing + yspacingsquare = SVG("rect", x=x_pos+ch_ar_border, y=y_pos+ch_ar_border, width=self.aruco_marker_size,height=self.aruco_marker_size, fill="black", stroke="none")self.g.append(square)for x_ in range(len(img_mark[0])):for y_ in range(len(img_mark)):if (img_mark[y_][x_] != 0):square = SVG("rect", x=x_pos+ch_ar_border+(x_)*side, y=y_pos+ch_ar_border+(y_)*side, width=side,height=side, fill="white", stroke="white", stroke_width = spacing*0.01)self.g.append(square)def save(self):c = canvas(self.g, width="%d%s" % (self.width, self.units), height="%d%s" % (self.height, self.units),viewBox="0 0 %d %d" % (self.width, self.height))c.save(self.output)def main():# parse command line optionsparser = argparse.ArgumentParser(description="generate camera-calibration pattern", add_help=False)parser.add_argument("-H", "--help", help="show help", action="store_true", dest="show_help")parser.add_argument("-o", "--output", help="output file", default="out.svg", action="store", dest="output")parser.add_argument("-c", "--columns", help="pattern columns", default="8", action="store", dest="columns",type=int)parser.add_argument("-r", "--rows", help="pattern rows", default="11", action="store", dest="rows", type=int)parser.add_argument("-T", "--type", help="type of pattern", default="circles", action="store", dest="p_type",choices=["circles", "acircles", "checkerboard", "radon_checkerboard", "charuco_board"])parser.add_argument("-u", "--units", help="length unit", default="mm", action="store", dest="units",choices=["mm", "inches", "px", "m"])parser.add_argument("-s", "--square_size", help="size of squares in pattern", default="20.0", action="store",dest="square_size", type=float)parser.add_argument("-R", "--radius_rate", help="circles_radius = square_size/radius_rate", default="5.0",action="store", dest="radius_rate", type=float)parser.add_argument("-w", "--page_width", help="page width in units", default=argparse.SUPPRESS, action="store",dest="page_width", type=float)parser.add_argument("-h", "--page_height", help="page height in units", default=argparse.SUPPRESS, action="store",dest="page_height", type=float)parser.add_argument("-a", "--page_size", help="page size, superseded if -h and -w are set", default="A4",action="store", dest="page_size", choices=["A0", "A1", "A2", "A3", "A4", "A5"])parser.add_argument("-m", "--markers", help="list of cells with markers for the radon checkerboard. Marker ""coordinates as list of numbers: -m 1 2 3 4 means markers in cells ""[1, 2] and [3, 4]",default=argparse.SUPPRESS, action="store", dest="markers", nargs="+", type=int)parser.add_argument("-p", "--marker_size", help="aruco markers size for ChAruco pattern (default 10.0)", default="10.0",action="store", dest="aruco_marker_size", type=float)parser.add_argument("-f", "--dict_file", help="file name of custom aruco dictionary for ChAruco pattern", default="DICT_ARUCO_ORIGINAL.json",action="store", dest="dict_file", type=str)args = parser.parse_args()show_help = args.show_helpif show_help:parser.print_help()returnoutput = args.outputcolumns = args.columnsrows = args.rowsp_type = args.p_typeunits = args.unitssquare_size = args.square_sizeradius_rate = args.radius_ratearuco_marker_size = args.aruco_marker_sizedict_file = args.dict_fileif 'page_width' and 'page_height' in args:page_width = args.page_widthpage_height = args.page_heightelse:page_size = args.page_size# page size dict (ISO standard, mm) for easy lookup. format - size: [width, height]page_sizes = {"A0": [840, 1188], "A1": [594, 840], "A2": [420, 594], "A3": [297, 420], "A4": [210, 297],"A5": [148, 210]}page_width = page_sizes[page_size][0]page_height = page_sizes[page_size][1]markers = Noneif p_type == "radon_checkerboard" and "markers" in args:if len(args.markers) % 2 == 1:raise ValueError("The length of the markers array={} must be even".format(len(args.markers)))markers = set()for x, y in zip(args.markers[::2], args.markers[1::2]):if x in range(0, columns) and y in range(0, rows):markers.add((x, y))else:raise ValueError("The marker {},{} is outside the checkerboard".format(x, y))if p_type == "charuco_board" and aruco_marker_size >= square_size:raise ValueError("ArUco markers size must be smaller than square size")pm = PatternMaker(columns, rows, output, units, square_size, radius_rate, page_width, page_height, markers, aruco_marker_size, dict_file)# dict for easy lookup of pattern typemp = {"circles": pm.make_circles_pattern, "acircles": pm.make_acircles_pattern,"checkerboard": pm.make_checkerboard_pattern, "radon_checkerboard": pm.make_radon_checkerboard_pattern,"charuco_board": pm.make_charuco_board}mp[p_type]()# this should save pattern to outputpm.save()if __name__ == "__main__":main()
- 2.通过使用函数
cv::aruco::GridBoard::generateImage()
。
cv::aruco::GridBoard
类的generateImage()
函数可以通过以下代码调用:
Mat boardImage;
board.generateImage(imageSize, boardImage, margins, borderBits);// 参数说明:
// imageSize:表示输出图像的尺寸,以像素为单位。在本例中为 600x500 像素。如果这与棋盘尺寸不成比例,它将居中显示在图像上。
// boardImage:包含棋盘的输出图像。
// margins:表示(可选的)边距,以像素为单位,因此没有任何标记接触图像边界。在本例中,边距为 10。
// borderBits:标记边框的大小,类似于 generateImageMarker() 函数。默认值为 1。
samples/cpp/tutorial_code/objectDetection/create_board.cpp
中包含了一个完整的棋盘创建工作示例。
输出图像将类似于这样:
这些示例现在通过cv::CommandLineParser经由命令行获取输入,对于上面的示例,输入参数如下所示:
"_output_path_/aboard.png" -w=5 -h=7 -l=100 -s=10 -d=10
4.优化标记检测
4.1 官方文档说明
ArUco板也可用于提高标记的检测率。如果我们检测到属于该板的一部分标记,我们可以使用这些标记和板的布局信息,尝试找到之前未检测到的标记。
这可以通过cv::aruco::refineDetectedMarkers()
函数来实现,该函数应在调用cv::aruco::ArucoDetector::detectMarkers()
之后调用。
此函数的主要参数是检测到标记的原始图像、棋盘对象、检测到的标记角点、检测到的标记 ID 和拒绝的标记角点。
被拒绝的角点可以从cv::aruco::ArucoDetector::detectMarkers()函数中获得,也被称为marker候选点。这些候选点是在原始图像中发现的方形形状,但未能通过识别步骤(即它们的内部编码存在太多错误),因此它们未被识别为marker。
然而,由于图像中存在高噪声、分辨率极低或其他影响二进制码提取的相关问题,这些候选者有时实际上是未被正确识别的实际标记。cv::aruco::ArucoDetector::refineDetectedMarkers()
函数会查找这些候选者与板上缺失标记之间的对应关系。此搜索基于两个参数:
- 候选者与缺失标记投影之间的距离。为了获得这些投影,至少需要检测到棋盘上的一个标记。如果提供了相机参数(相机矩阵和畸变系数),则使用这些参数获得投影。如果没有提供,则从局部单应性获得投影,并且只允许使用平面棋盘(即,所有标记角点的 Z 坐标应该相同)。 refineDetectedMarkers() 中的 minRepDistance 参数确定候选角点与投影标记角点之间的最小欧几里德距离(默认值为 10)。
- 二进制编码。如果候选者超过最小距离条件,则再次分析其内部位,以确定它是否实际上是投影标记。但是,在这种情况下,条件不是那么严格,并且允许的错误位数可以更高。这在 errorCorrectionRate 参数中指示(默认值为 3.0)。如果提供负值,则根本不分析内部位,而仅评估角点距离。
示例
这是一个使用 cv::aruco::ArucoDetector::refineDetectedMarkers()
函数的例子:
// Detect markers
detector.detectMarkers(image, corners, ids, rejected);// Refind strategy to detect more markers
if(refindStrategy)detector.refineDetectedMarkers(image, board, corners, ids, rejected, camMatrix, distCoeffs);
还需注意的是,在某些情况下,如果最初检测到的标记数量太少(例如,只有 1 或 2 个标记),则缺失标记的投影质量可能较差,从而产生错误的对应关系。
4.2 关于refineDetectedMarkers()函数
4.2.1 refineDetectedMarkers()函数说明
/** @brief Refine not detected markers based on the already detected and the board layout** @param image input image* @param board layout of markers in the board.* @param detectedCorners vector of already detected marker corners.* @param detectedIds vector of already detected marker identifiers.* @param rejectedCorners vector of rejected candidates during the marker detection process.* @param cameraMatrix optional input 3x3 floating-point camera matrix* \f$A = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{1}\f$* @param distCoeffs optional vector of distortion coefficients* \f$(k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6],[s_1, s_2, s_3, s_4]])\f$ of 4, 5, 8 or 12 elements* @param recoveredIdxs Optional array to returns the indexes of the recovered candidates in the* original rejectedCorners array.** This function tries to find markers that were not detected in the basic detecMarkers function.* First, based on the current detected marker and the board layout, the function interpolates* the position of the missing markers. Then it tries to find correspondence between the reprojected* markers and the rejected candidates based on the minRepDistance and errorCorrectionRate parameters.* If camera parameters and distortion coefficients are provided, missing markers are reprojected* using projectPoint function. If not, missing marker projections are interpolated using global* homography, and all the marker corners in the board must have the same Z coordinate.*/CV_WRAP void refineDetectedMarkers(InputArray image, const Board &board,InputOutputArrayOfArrays detectedCorners,InputOutputArray detectedIds, InputOutputArrayOfArrays rejectedCorners,InputArray cameraMatrix = noArray(), InputArray distCoeffs = noArray(),OutputArray recoveredIdxs = noArray()) const;
- 函数功能:
refineDetectedMarkers
是 OpenCV 中cv::aruco::ArucoDetector
类中的一个成员函数,主要用于改进和修正已检测的标记(markers)。它的作用是基于当前已检测的标记及其布局,进一步细化未被检测到的标记位置。通常,这个函数是在标记检测阶段后调用的,尤其是在某些标记未被正常检测到的情况下。
该函数尝试通过已有的检测结果来推测缺失标记的位置,并通过计算与已拒绝候选标记的匹配关系来进一步确认缺失标记。它还支持根据相机的内参和畸变系数进行误差校正。
- 输入和输出参数:
4.2.2 detectMarkers 与 detectMarkers() 的区别和关系
-
detectMarkers
函数:
detectMarkers 是 OpenCV 中 ArucoDetector 类的一个方法,主要用于检测图像中的标记,并返回标记的角点坐标和标识符。它基于图像中的亮点、边缘等特征来找到标记,并且可以执行基本的检测和识别任务。然而,在一些情况下,某些标记可能由于噪声、遮挡或其他原因未能被检测到,或是误判为不存在。 -
refineDetectedMarkers
函数:refineDetectedMarkers 则是在 detectMarkers 之后用来进一步修正和完善标记检测结果的一个函数。它并不重新执行标记的检测,而是通过利用已检测到的标记和标记板的布局信息,推测和修正那些未能正确检测到的标记。这个函数特别适用于在初次检测时漏掉的标记,或者需要提高标记检测精度的情况。
4.2.3 是否需要 refineDetectedMarkers
- 是否需要调用 refineDetectedMarkers 取决于实际的应用场景:
如果所有标记都已成功检测到,且检测结果较为精确,则通常不需要调用 refineDetectedMarkers。如果部分标记未被检测到,或者标记的角点有误差,可以通过调用 refineDetectedMarkers 来修正这些问题,特别是在检测不完整或复杂环境下的标记时。
简而言之,refineDetectedMarkers 是在标记检测后进行的额外步骤,旨在通过已知的标记布局和相机参数(如果提供)来进一步细化检测结果。
- 在调用 detectMarkers 后,始终默认加上 refineDetectedMarkers,即使没有检测到任何标记。这样做的好处是,即使在某些情况下没有检测到标记,refineDetectedMarkers 也会根据已经得到的结果(如果有的话)尽可能修正角点位置,或者提供更精确的检测效果。
5.opencv4.10.0 代码
5.1 创建 arucoBoard
#include <opencv2/highgui.hpp>
#include <opencv2/objdetect/aruco_detector.hpp>
#include <iostream>
// ref: samples/cpp/tutorial_code/objectDetection/create_board.cpp
TEST(TestDetect_Board, create_aruco_board)
{int markersX = 5; // X方向aruco标记的数量int markersY = 7; // Y方向aruco标记的数量int markerLength = 100; // aruco标记边长(以像素为单位)int markerSeparation = 10; // 网格中两个连续aruco标记之间的间距(以像素为单位)int margins = markerSeparation;// 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 = 16int dictionaryId = 10;int borderBits = 1; // 标记边框中的位数,默认1bool showImage = true;cv::Size imageSize;imageSize.width = markersX * (markerLength + markerSeparation) - markerSeparation + 2 * margins;imageSize.height = markersY * (markerLength + markerSeparation) - markerSeparation + 2 * margins;cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(dictionaryId);cv::aruco::GridBoard board(cv::Size(markersX, markersY), float(markerLength), float(markerSeparation), dictionary);// show created board//! [aruco_generate_board_image]cv::Mat boardImage;board.generateImage(imageSize, boardImage, margins, borderBits);//! [aruco_generate_board_image]if (showImage) {cv::imshow("Aruco boards", boardImage);cv::waitKey(0);}cv::imwrite("arucoBoard.png", boardImage);
}
5.2 arucoBoard 位姿估计
官方代码里面markerLength和markerSeparation的代码注释写的单位是像素,实际验证这里的单位是米。
#include <opencv2/highgui.hpp>
#include <opencv2/objdetect/aruco_detector.hpp>
#include <iostream>
#include <algorithm>int markersX = 5; // X方向aruco标记的数量
int markersY = 7; // Y方向aruco标记的数量
float markerLength = 0.0032; // aruco标记边长(以m素为单位)
float markerSeparation = 0.0008; // 网格中两个连续aruco标记之间的间距(以m为单位)
bool showRejected = true;
bool refindStrategy = true; // 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 = 16int dictionaryId = 10;cv::Mat camMatrix = (cv::Mat_<double>(3, 3) << 5166.6, 0.0, 1524.46, 0.0, 5165.3, 1118.25, 0.0, 0.0, 1.0);
cv::Mat distCoeffs = (cv::Mat_<double>(5, 1) << 0.0261802, -0.455624, 0.00267969, -0.00287971, -0.877674);cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(dictionaryId);
cv::aruco::DetectorParameters detectorParams;cv::aruco::ArucoDetector detector(dictionary, detectorParams);float axisLength = 0.5f * ((float)std::min(markersX, markersY) * (markerLength + markerSeparation) + markerSeparation);// Create GridBoard object
//! [aruco_create_board]
cv::aruco::GridBoard board(cv::Size(markersX, markersY), markerLength, markerSeparation, dictionary);//! [aruco_create_board]
// Also you could create Board object
// vector<vector<Point3f> > objPoints; // array of object points of all the marker corners in the board
// vector<int> ids; // vector of the identifiers of the markers in the board
// aruco::Board board(objPoints, dictionary, ids);cv::Mat image = cv::imread("./data/localization/arucoboards_1.bmp", cv::IMREAD_COLOR);
cv::Mat imageCopy;std::vector<int> ids;
std::vector<std::vector<cv::Point2f>> corners, rejected;
cv::Vec3d rvec, tvec;//! [aruco_detect_and_refine]// Detect markers
detector.detectMarkers(image, corners, ids, rejected);// Refind strategy to detect more markers
if (refindStrategy) {detector.refineDetectedMarkers(image, board, corners, ids, rejected, camMatrix, distCoeffs);
}//! [aruco_detect_and_refine]// Estimate board pose
int markersOfBoardDetected = 0;
if (!ids.empty()) {// Get object and image points for the solvePnP functioncv::Mat objPoints, imgPoints;board.matchImagePoints(corners, ids, objPoints, imgPoints);// Find posecv::solvePnP(objPoints, imgPoints, camMatrix, distCoeffs, rvec, tvec);markersOfBoardDetected = (int)objPoints.total() / 4;
}// Draw results
image.copyTo(imageCopy);
if (!ids.empty())cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);if (showRejected && !rejected.empty())cv::aruco::drawDetectedMarkers(imageCopy, rejected, cv::noArray(), cv::Scalar(100, 0, 255));if (markersOfBoardDetected > 0)cv::drawFrameAxes(imageCopy, camMatrix, distCoeffs, rvec, tvec, axisLength);cv::imshow("out", imageCopy);
cv::waitKey(0);cv::imwrite("detect_arucoBoard.png", imageCopy);
相关文章:
【ArUco boards】标定板检测
之前定位用的Charuco标定板做的(https://blog.csdn.net/qq_45445740/article/details/143897238),因为实际工况中对标定板的尺寸有要求,大概是3cm*2cm这个尺寸,加上选用的是ChAruco标定板,导致每一个aruco码…...
2025 年 408 真题及答案
2025 年 408 真题 历年408真题及答案下载直通车 1、以下 C 代码的时间复杂度是多少?() int count 0; for (int i0; i*i<n; i)for (int j0; j<i; j)count;A O(log2n)B O(n)C O(nlogn)D O(n2) 2、对于括号匹配问题,符号栈…...
设计模式每日硬核训练 Day 18:备忘录模式(Memento Pattern)完整讲解与实战应用
🔄 回顾 Day 17:中介者模式小结 在 Day 17 中,我们学习了中介者模式(Mediator Pattern): 用一个中介者集中管理对象之间的通信。降低对象之间的耦合,适用于聊天系统、GUI 控件联动、塔台调度等…...
ByteArrayOutputStream 类详解
ByteArrayOutputStream 类详解 ByteArrayOutputStream 是 Java 中用于在内存中动态写入字节数据的输出流(ByteArrayOutputStream和ByteArrayInputStream是节点流),位于 java.io 包。它不需要关联物理文件或网络连接,所有数据都存储在内存的字节数组中。 1. 核心特性 内存缓冲…...
Linux中web服务器的部署及优化
前言:Nginx 和 Apache HTTP Server 是两款非常流行的 Web 服务器。 Nginx 简介:Nginx 是一款轻量级的高性能 Web 服务器、反向代理服务器以及电子邮件(IMAP/POP3)代理服务器。由俄罗斯人伊戈尔・赛索耶夫开发,其在处…...
使用Mathematica绘制Sierpinski地毯
在Mathematica中内置的绘制Sierpinski地毯的函数: SierpinskiCurve[n] gives the line segments representing the n-step Sierpiński curve. 注意,直接运行这个函数,返回的是Line对象,例如: 运行如下代码…...
Qt 信号槽机制底层原理学习
简介 Qt的信号和槽(Signals and Slots)是Qt开发团队创造的一种特殊回调机制,提供了非常简洁易用的事件触发-函数调用机制。 原理学习 虽然上层使用简单,但底层实现机制却复杂的不得了,这里简单的学习一下大概原理。…...
【Java学习笔记】包
包(package) 包的本质:实际上就是创建不同的文件夹或者目录来保存类文件 包的三大作用 区分相同名字的类 当类很多的时候可以更方便的管理类 控制访问范围 使用方法 关键字:import—>导入(引入) …...
进程的程序替换——exec系列函数的使用
目录 前言 一、替换函数 二、程序替换的本质 一些细节: 三、程序替换与环境变量间的关系 1.介绍其他参数的意义并总结 2.自定义环境变量 1)通过execcle传参全局环境变量 2)通过execcle传参自定义环境变量 3)将自定义环境变量通过p…...
【论文阅读】DETR+Deformable DETR
可变形注意力是目前transformer结构中经常使用的一种注意力机制,最近补了一下这类注意力的论文,提出可变形注意力的论文叫Deformable DETR,是在DETR的基础上进行的改进,所以顺带着把原本的DETR也看了一下。 一、DETR DETR本身是…...
ArchLinux卡死在GRUB命令行模式修复
ArchLinux卡死在GRUB命令行模式修复 文章目录 ArchLinux卡死在GRUB命令行模式修复前言一、 系统配置1.系统配置2.磁盘分区信息 二、重建GRUB引导1.插入带ArchLinux ISO的U盘,BIOS选择U盘启动并进入ArchLinux安装界面。2.挂载btrfs根目录分区3.挂载/boot分区4.进入ch…...
Docker 容器 - Dockerfile
Docker 容器 - Dockerfile 一、Dockerfile 基本结构二、Dockerfile 指令详解2.1 FROM2.2 MAINTAINER2.3 COPY2.4 ADD2.5 WORKDIR2.6 VOLUME2.7 EXPOSE2.8 ENV2.9 RUN2.10 CMD2.11 ENTRYPOINT 三、Dockerfile 创建镜像与模板3.1 Dockerfile 镜像3.2 镜像管理3.3 Dockerfile 模板…...
C++ 中二级指针的正确释放方法
C 中二级指针的正确释放 一、什么是二级指针? 简单说,二级指针就是指向指针的指针。 即: int** p;它可以指向一个 int*,而 int* 又指向一个 int 类型的变量。 常见应用场景 动态二维数组(例如 int** matrix&#x…...
解释器模式(Interpreter Pattern)
解释器模式(Interpreter Pattern) 是行为型设计模式之一,通常用于处理“语言”类问题,比如计算器、编程语言的解析等。它的核心思想是通过建立一个解释器,解析并解释由语法规则描述的语言,通常以**抽象语法…...
编译原理期末重点-个人总结——1 概论
概述 计算机语言的分类 低级语言:机器语言(唯一能被计算机执行的),汇编语言 高级语言:JAVA ,C 执行高级语言或汇编语言的步骤 高级语言程序或汇编语言程序> (通过解释 或 翻译)转…...
五一作业-day04
文章目录 1. **ps -ef是显示当前系统进程的命令,统计下当前系统一共有多少进程**2. **last命令用于显示所用用户最近1次登录情况,awk可以取出某一列,现在要取出last命令第1列并去重统计次数**3. **secure日志是用户的登录日志,过滤出secure日志中的Failed password的次数(用课堂…...
Java按字节长度截取字符串指南
在Java中,由于字符串可能包含多字节字符(如中文),直接按字节长度截取可能会导致乱码或截取不准确的问题。以下是几种按字节长度截取字符串的方法: 方法一:使用String的getBytes方法 java public static String substringByBytes(…...
[特殊字符]Git 操作实战:如何将本地项目提交到远程 Gitee 仓库
在日常开发中,我们经常需要将本地开发的项目同步到远程代码仓库中(如 GitHub、Gitee 等),以便团队协作或备份管理。本文将以 Gitee(码云) 为例,详细讲解如何将本地已有项目提交到远程仓库&#…...
【信息系统项目管理师-论文真题】2008上半年论文详解(包括解题思路和写作要点)
更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 试题1:企业级信息系统项目管理体系的建立1、写作要点2、解题思路项目管理流程和项目管理的工具试题2:项目的质量管理1、写作要点2、解题思路项目的早期阶段如何制定项目质量管理计划如何确保项目质量管理计划…...
C语言|函数的递归调用
函数的递归调用 (逐层分解,逐层合并) 自己调用自己,必须要知道什么时候停止调用,不然会造成电脑死机。 【知识点】 1 函数调用是通过栈实现的。 多个函数嵌套调用时,会按照先调用后返回的原则进行返回。 2 函数递归必须满足的两…...
QT 在圆的边界画出圆
QT 在圆的边界画出圆 QT 在圆的边界画出实心圆 在Qt中,要实现在圆的边界上绘制图形,你需要使用QPainter类来在QWidget或其子类的paintEvent中绘制。下面我将通过一个简单的例子来说明如何在Qt中绘制一个圆,并在其边界上绘制其他图形&#x…...
Guass数据库实验(数据字典设计、交叉表设计)
Assignment 2: Database Design 目录 Assignment 2: Database Design 数据库创建 新建用户bit,并创建数据库模式ass2 使用datastdui以该用户远程登陆 创建学科数据字典相关表 学科门类表 一级学科表 二级学科表 三级学科表 学科变更历史表 插入数据字典…...
算法题(139):牛可乐和魔法封印
审题: 本题需要我们将数组中包含在区间x~y之间的数据个数找到并输出 思路: 方法一:暴力解法 首先我们可以直接遍历一次数组,找到x的索引,然后再找到y的索引,并计算最终的元素个数,这里就要有O&a…...
LeetCode热题100--189.轮转数组--中等
1. 题目 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,…...
DeepSeek-Prover-V2:数学定理证明领域的新突破
前言 在人工智能飞速发展的当下,模型的迭代与创新层出不穷。 五一假期期间,DeepSeek 再次发力,推出了令人瞩目的新模型 ——DeepSeek-Prover-V2。 与大众期待的 R2 通用推理模型不同,这次 DeepSeek 将目光聚焦于数学定理证明领…...
调试——GDB、日志
调试——GDB、日志 1. gdb常用指令2. 如何生成core文件并调试?3. 如何调试正在运行的程序4. 调试多进程程序5. 调试多线程程序6. log日志 gcc编译器可以帮我们发现语法错误,但是对业务逻辑错误却无能为力。当我们想找出逻辑错误时,就需要调试…...
ARM子程序调用与返回
子程序(也叫过程、函数、方法)是一个能被调用和执行并返回到调用点那条指令的代码 段。 两个问题:如何将参数传递给子程序或从子程序中传递出来?怎么从子程序返回到调用点? 指令BSR Proc_A调用子程序Proc_A。 处理器将…...
WSL 安装 Debian 后,apt get 如何更改到国内镜像网址?
提问:Debian apt install 如何更改到国内镜像网址? 在 Debian 系统中,你可以通过修改 /etc/apt/sources.list 文件,将软件源更改为国内镜像网址,以加快软件包的下载速度。下面为你详细介绍操作步骤: 1. 备…...
SpringCloud GateWay网关
1、网关介绍 微服务网关(Microservices Gateway)是微服务架构中的核心组件,充当所有客户端请求的统一入口,负责请求的路由、过滤和聚合等操作。它是微服务与外部系统(如Web、移动端)之间的中间层࿰…...
可视化大屏开发全攻略:技术与实践指南
引言 在数字化浪潮席卷全球的当下,数据已成为企业乃至整个社会发展的核心驱动力。从繁华都市的交通管控中心,到大型企业的数据运营中枢,可视化大屏无处不在,以直观、震撼的方式展示着数据的魅力与价值。它就像是一扇通往数据世界…...
如何设计一个为QStackWidget的界面切换动画?
目录 前言 接口考虑 实现的思路 前言 笔者这段时间沉迷于给我的下位机I.MX6ULL做桌面,这里抽空更新一下QT的东西。这篇文章是跟随CCMoveWidget一样的文章,尝试分享自己如何书写这份代码的思考的过程 接口考虑 笔者不太想使用继承的方式重新写我们的…...
LeetCode 0790.多米诺和托米诺平铺:难想条件的简单动态规划
【LetMeFly】790.多米诺和托米诺平铺:难想条件的简单动态规划 力扣题目链接:https://leetcode.cn/problems/domino-and-tromino-tiling/ 有两种形状的瓷砖:一种是 2 x 1 的多米诺形,另一种是形如 "L" 的托米诺形。两种…...
模拟芯片设计中数字信号处理一些常用概念(一)
模拟芯片设计中经常用时域场景思考来解决问题,但实际上很多地方如果采用频域角度思考,解决问题更快更方便。 时域和频域的对照关系如下: a、如果时域信号是周期的,那么它的频谱就是离散的。 b、如果时域信号是非周期的,那么它的频谱就是连续的。 c、如果时域信号是离散的…...
c++进阶——AVL树主要功能的模拟实现(附带旋转操作讲解)
文章目录 AVL树的实现AVL树的概念及引入AVL树调整问题AVL树的实现AVL树的结构AVL树的插入插入的流程更新平衡因子的原则实现插入的基本框架(插入 调整平衡因子)旋转操作右单旋左单旋左右双旋右左双旋 合并旋转代码 测试部分平衡检测接口测试用例 对于其他接口的说明 AVL树的实…...
一个电商场景串联23种设计模式:创建型、结构型和行为型
理解了!你希望有一个具体的项目案例,能够涵盖所有23种设计模式,并且将它们分类为创建型、结构型和行为型。这个需求非常好,能够帮助你从实际的应用场景理解每种设计模式的用法。 为了实现这个目标,我将为你设计一个电…...
浅拷贝和深拷贝的区别
Person p1 new Person(10);Person p2 p1;p2.age 20;System.out.println(p1p2); // trueSystem.out.println(p1.age); // 20 这种做法只是复制了对象的地址,即两个变量现在是指向了同一个对象,任意一个变量,操作了对象的属性,都…...
Java开发者面试实录:微服务架构与Spring Cloud的应用
面试场景 面试官: 请介绍一下你的基本情况。 程序员: 大家好,我叫张小明,今年27岁,硕士学历,拥有5年的Java后端开发经验。主要负责基于Spring Boot开发企业级应用,以及微服务架构的设计和实现。 面试官: 好的&#…...
在Ubuntu系统中安装桌面环境
在 Ubuntu 系统中安装桌面环境可以通过包管理器 apt 或工具 tasksel 实现。以下是详细的安装方法和常见桌面环境的选择: --- ### **1. 准备系统更新** 在安装前,建议更新软件源和系统包: bash sudo apt update && sudo apt upgrade…...
多语言笔记系列:Polyglot Notebooks 中使用 xUnit 单元测试
Polyglot Notebooks 中使用 xUnit 单元测试 本文目录 Polyglot Notebooks 中使用 xUnit 单元测试[TOC](本文目录)Polgylot Notebooks 并没有直接支持单元测试框架。不能像VS里那样方便的进行单元测试。简单远行的话,可以使用下面的方案!1、引入必要的NuG…...
Cisco Packet Tracer 选项卡的使用
目录 设备Config选项卡的使用 Realtime and Simulation模式(数据包跟踪与分析) 设备Desktop选项卡的使用 设备Config选项卡的使用 Hostname NVRAM Startup Config----Load 加载 INTERFACE 点击on Save 如果,不把Running Config保存为Sta…...
杨校老师竞赛课之C++备战蓝桥杯初级组省赛
目录 1. 灯塔 题目描述 输入描述 输出描述 输入样例1 输出样例1 输入样例2 输出样例2 数据说明 2. 子区间 题目描述 输入描述 输出描述 输入样例 输出样例 数据说明 3. 染色 题目描述 输入描述 输出描述 输入样例1 输出样例1 输入样例2 输出样例2 数据…...
gcc/g++用法摘记
链接静态库 gcc main.o -L/path/to/libs -lmylib -o myprogram 【待续】...
kotlin 扩展函数
Kotlin 扩展函数的定义与使用 定义扩展函数 Kotlin 的扩展函数是一种强大的机制,允许开发者为已有的类添加额外的功能,而无需继承该类或对其进行任何修改。这种特性极大地提高了代码的灵活性和可读性。 扩展函数可以通过在函数名称前指定目标类型的接…...
机器人强化学习入门学习笔记
(1)物理引擎 物理引擎就是模拟真实世界物理规律的软件工具。它会根据你给定的物体、质量、形状、力等信息,计算这些物体在时间上的运动和相互作用。如果你设计了一个机器人,那物理引擎就是“虚拟现实世界”,让机器人在里面“活起来”,模拟它走路、抓东西、摔倒等动作。而…...
《RESTful API版本控制的哲学思辨:稳定性与创新性的终极平衡》
有效的版本控制,就如同精密仪器中的校准装置,确保API在不断升级的过程中,依然能与旧有系统无缝对接,维持整个生态的平稳运行。 不同的客户端对API的依赖程度和使用方式各不相同。有些客户端可能因为各种原因,无法及时…...
spring中spring-boot-configuration-processor的使用
spring-boot-configuration-processor 是 Spring Boot 提供的注解处理器,用于在编译阶段生成配置元数据文件(spring-configuration-metadata.json),从而优化开发体验。以下是其核心功能和使用指南: 一、核心功能 IDE 智…...
30天开发操作系统 第27天 -- LDT与库
前言 大家早上好,我们今天的第一个任务就是修复昨天晚上的那个bug。是个什么bug来着?就是用nsct命令运行的应用程序,无论是按ShiftF1还是点击窗口的“x”按钮都没有反应的那个bug啦。 我们得先来找到出问题的原因,然后才能采取对…...
std::move()详解
一、std::move()的作用和原理 本质: std::move()并不像字面意思“搬走”那些对象,而是: 将传入的对象“强制转化”为右值引用类型,从而开启“移动语义”。 在源码层面: 复制代码 template<typename T> std::…...
linux系统基本操作命令
文件和目录操作 ls:列出目录内容。 例如:ls -l 显示详细信息,ls -a 显示包括隐藏文件在内的所有文件。 cd:改变当前目录。 例如:cd /home/username 切换到指定目录。 pwd:显示当前目录的完整路径。 mk…...
python打卡day16
NumPy 数组基础 因为前天说了shap,这里涉及到数据形状尺寸问题,所以需要在这一节说清楚,后续的神经网络我们将要和他天天打交道。 知识点: numpy数组的创建:简单创建、随机创建、遍历、运算numpy数组的索引:…...