【opencv】示例-aruco_dict_utils.cpp 计算 ArUco 字典度量

4016747f97a8a4fca15b65323d64f7e6.png

该程序可用于计算 ArUco 字典度量。

要计算考虑翻转标记的指标,请使用 -'r' 标志。

该程序可用于创建和编写自定义 ArUco 词典。

12c62f192220aefee57f8be0b298a5cf.png

1ebaa80dcafb8732a4bbb5f6135590af.png

#include <opencv2/objdetect/aruco_detector.hpp> // 包含aruco marker检测相关功能的头文件
#include <iostream> // 包含输入输出流相关功能的头文件using namespace cv; // 使用命名空间cv,这样我们就可以直接使用OpenCV的函数和类而不需要加cv::前缀
using namespace std; // 使用命名空间std,标准的C++库函数比如std::cout可以直接写成cout// 下面是静态函数的定义,因为我们不需要实例化对象就可以直接用类名调用它们
//为了确保ArUco标记的独一无二,这个函数计算了一个标记的自身最小汉明距离(即标记的不同旋转形态之间的最小差异度量),这有助于保证即使在不同的旋转下,标记也能被准确地检测和识别。
static int _getSelfDistance(const Mat &marker) {// 计算单个marker自身的汉明距离(marker之间的差异程度)Mat bytes = aruco::Dictionary::getByteListFromBits(marker); // 将marker的位图转换为字节列表double minHamming = (double)marker.total() + 1; // 初始化最小汉明距离为marker的总数+1for(int r = 1; r < 4; r++) { // 对每个旋转的标记进行遍历(共有四种旋转,不包括未旋转)// 创建两个临时的字节行,用于存储转换后的字节信息cv::Mat tmp1(1, bytes.cols, CV_8UC1, Scalar::all(0));cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));uchar* rot0 = tmp1.ptr(); // 获取行tmp1的指针uchar* rot1 = tmp2.ptr(); // 获取行tmp2的指针for (int i = 0; i < bytes.cols; ++i) { // 将marker的字节旋转r*90度并拷贝到临时字节行中rot0[i] = bytes.ptr()[i];rot1[i] = bytes.ptr()[bytes.cols*r + i];}// 计算两个临时行的汉明距离double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);// 更新最小汉明距离if (currentHamming < minHamming) minHamming = currentHamming;}// 对marker进行水平翻转和垂直翻转后的处理逻辑与上面类似,不再重复。// 在marker的所有可能的变换后,返回最小的汉明距离// 检查水平翻转后的情况Mat b; // 定义一个矩阵用于存储翻转的结果flip(marker, b, 0); // 对marker进行水平翻转Mat flipBytes = aruco::Dictionary::getByteListFromBits(b); // 获取翻转后marker的字节列表// 对翻转后的marker进行相似度检查,逻辑与上面类似for(int r = 0; r < 4; r++) {// ... 代码逻辑与上面类似 ...cv::Mat tmp1(1, flipBytes.cols, CV_8UC1, Scalar::all(0));cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));uchar* rot0 = tmp1.ptr();uchar* rot1 = tmp2.ptr();for (int i = 0; i < bytes.cols; ++i) {rot0[i] = flipBytes.ptr()[i];rot1[i] = bytes.ptr()[bytes.cols*r + i];}double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);if(currentHamming < minHamming) minHamming = currentHamming;}// 检查垂直翻转后的情况flip(marker, b, 1); // 对marker进行垂直翻转flipBytes = aruco::Dictionary::getByteListFromBits(b); // 获取翻转后marker的字节列表// 对垂直翻转后的marker进行相似度检查,逻辑与上面类似for(int r = 0; r < 4; r++) {// ... 代码逻辑与上面类似 ...cv::Mat tmp1(1, flipBytes.cols, CV_8UC1, Scalar::all(0));cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));uchar* rot0 = tmp1.ptr();uchar* rot1 = tmp2.ptr();for (int i = 0; i < bytes.cols; ++i) {rot0[i] = flipBytes.ptr()[i];rot1[i] = bytes.ptr()[bytes.cols*r + i];}double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);if(currentHamming < minHamming) minHamming = currentHamming;}// 返回最小汉明距离的四舍五入结果return cvRound(minHamming);
}
//计算给定字典中,某个ArUco标记与特定ID的其他标记之间在全部或部分旋转的情况下的最小汉明距离。这个函数可以用于评估字典中标记的独一无二性,以及是否可以准确识别翻转后的标记。
static inline int getFlipDistanceToId(const aruco::Dictionary& dict, InputArray bits, int id, bool allRotations = true) {// 根据给定的ID计算字典中一个标记与其余标记的汉明距离,包括考虑标记的翻转Mat bytesList = dict.bytesList; // 获取字典中所有标记的字节列表CV_Assert(id >= 0 && id < bytesList.rows); // 检查输入的ID是否有效unsigned int nRotations = 4; // 默认情况下,考虑所有4个旋转if(!allRotations) nRotations = 1; // 如果不考虑旋转,只需要计算未旋转的情况Mat candidateBytes = aruco::Dictionary::getByteListFromBits(bits.getMat()); // 获取候选标记的字节列表double currentMinDistance = int(bits.total() * bits.total()); // 初始化当前的最小汉明距离for(unsigned int r = 0; r < nRotations; r++) { // 遍历所有的旋转(可能包括未旋转)// 创建两个临时字节行cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));uchar* rot0 = tmp1.ptr(); // 行tmp1的指针uchar* rot1 = tmp2.ptr(); // 行tmp2的指针for (int i = 0; i < candidateBytes.cols; ++i) { // 将字典中的标记旋转后,与候选标记的字节进行比较rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i];rot1[i] = candidateBytes.ptr()[i];}// 计算当前的汉明距离double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);// 更新最小汉明距离if(currentHamming < currentMinDistance) {currentMinDistance = currentHamming;}}// 对候选标记进行水平翻转和垂直翻转后的处理逻辑与上面类似,不再重复。// 返回分配给字典中特定ID的汉明距离Mat b; // 定义一个Mat对象b用于存储翻转后的图像flip(bits.getMat(), b, 0); // 将输入的图像bits沿着水平轴翻转并存入bcandidateBytes = aruco::Dictionary::getByteListFromBits(b); // 将翻转后的图像b转换为字节列表for(unsigned int r = 0; r < nRotations; r++) { // 循环遍历每种旋转状态cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0)); // 创建一个用于储存旋转的字节的临时Mat对象tmp1cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0)); // 创建一个用于储存原始字节的临时Mat对象tmp2uchar* rot0 = tmp1.ptr(); // 获取tmp1的指针uchar* rot1 = tmp2.ptr(); // 获取tmp2的指针for (int i = 0; i < candidateBytes.cols; ++i) { // 循环遍历字节的每一列rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i]; // 读取固定ID的翻转状态的字节rot1[i] = candidateBytes.ptr()[i]; // 从候选字节中读取对应列的字节}double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING); // 计算tmp1和tmp2之间的汉明距离if (currentHamming < currentMinDistance) { // 如果当前汉明距离小于当前记录的最小距离currentMinDistance = currentHamming; // 更新最小汉明距离}}flip(bits.getMat(), b, 1); // 将输入的图像bits沿着垂直轴翻转并存入bcandidateBytes = aruco::Dictionary::getByteListFromBits(b); // 将翻转后的图像b转换为字节列表for(unsigned int r = 0; r < nRotations; r++) { // 循环遍历每种旋转状态,逻辑与上述相同// 对翻转的图像执行与上面相同的操作,检查汉明距离,并更新最小值// ... (代码逻辑与上面相同,未显示) ...cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));uchar* rot0 = tmp1.ptr();uchar* rot1 = tmp2.ptr();for (int i = 0; i < candidateBytes.cols; ++i) {rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i];rot1[i] = candidateBytes.ptr()[i];}double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);if (currentHamming < currentMinDistance) {currentMinDistance = currentHamming;}}return cvRound(currentMinDistance);
}// 以下函数用于生成定制的非对称ArUco字典
static inline aruco::Dictionary generateCustomAsymmetricDictionary(int nMarkers, int markerSize,const aruco::Dictionary &baseDictionary,int randomSeed) { // 定义一个静态内联函数,用于生成定制的非对称的ArUco标记字典RNG rng((uint64)(randomSeed)); // 基于随机种子初始化一个随机数生成器aruco::Dictionary out; // 创建一个空的ArUco字典用于输出out.markerSize = markerSize; // 设置输出字典中标记的大小// 理论上最大的标记间汉明距离// 论文参考:S. Garrido-Jurado, et al., 2014. "Automatic generation and detection of highly reliable fiducial markers under occlusion".int C = (int)std::floor(float(markerSize * markerSize) / 4.f); // 计算理论最大标记间距int tau = 2 * (int)std::floor(float(C) * 4.f / 3.f); // 计算arity的临界值// 如果提供了基础字典,计算它的标记间距if(baseDictionary.bytesList.rows > 0) {CV_Assert(baseDictionary.markerSize == markerSize); // 确认基础字典的尺寸匹配out.bytesList = baseDictionary.bytesList.clone(); // 克隆基础字典的字节列表int minDistance = markerSize * markerSize + 1; // 初始化最小距离for(int i = 0; i < out.bytesList.rows; i++) { // 遍历基础字典所有标记Mat markerBytes = out.bytesList.rowRange(i, i + 1); // 获取当前标记的字节行Mat markerBits = aruco::Dictionary::getBitsFromByteList(markerBytes, markerSize); // 将当前标记的字节转换成位矩阵minDistance = min(minDistance, _getSelfDistance(markerBits)); // 更新最短汉明距离for(int j = i + 1; j < out.bytesList.rows; j++) { // 计算当前标记与其他标记的距离minDistance = min(minDistance, getFlipDistanceToId(out, markerBits, j)); // 更新最短汉明距离}}tau = minDistance; // 更新临界值tau}// 当前最佳的选项int bestTau = 0; // 初始化最佳tauMat bestMarker; // 存储当前最佳标记// 经过指定次数未产生结果的迭代后,接受最佳选项const int maxUnproductiveIterations = 5000; // 最大无产出迭代次数int unproductiveIterations = 0; // 无产出迭代计数while(out.bytesList.rows < nMarkers) { // 当生成的标记数量还未满足要求时Mat currentMarker(markerSize, markerSize, CV_8UC1, Scalar::all(0)); // 创建一个新的空标记rng.fill(currentMarker, RNG::UNIFORM, 0, 2); // 使用随机数填充当前标记int selfDistance = _getSelfDistance(currentMarker); // 计算当前标记的自汉明距离int minDistance = selfDistance; // 将其设置为最小距离参考值// 如果自汉明距离大于或等于当前最佳,计算与之前接受的标记之间的距离if(selfDistance >= bestTau) {for(int i = 0; i < out.bytesList.rows; i++) { // 遍历之前接受的所有标记int currentDistance = getFlipDistanceToId(out, currentMarker, i); // 计算距离minDistance = min(currentDistance, minDistance); // 更新最小距离// 如果最小距离小于或等于最佳tau,则跳出循环if(minDistance <= bestTau) {break;}}}// 如果距离足够大,接受当前标记// 如果距离足够大,则接受当前标记if(minDistance >= tau) { // 如果最小汉明距离大于等于预定的阈值tauunproductiveIterations = 0; // 重置无产出迭代计数器bestTau = 0; // 重置最好的tau值Mat bytes = aruco::Dictionary::getByteListFromBits(currentMarker); // 获取当前marker的字节列表out.bytesList.push_back(bytes); // 将当前marker添加到输出字典的字节列表中} else {// 如果距离不够大,则进入下面的流程unproductiveIterations++; // 无产出迭代计数器加一// 如果距离尚不够大,但比当前最佳选择要好if(minDistance > bestTau) { // 如果最小汉明距离大于当前最佳的tau值bestTau = minDistance; // 更新最好的tau值为当前的最小距离bestMarker = currentMarker; // 更新最好的marker为当前的marker}// 如果达到了无产出迭代的设定极限,接受当前最佳选择if(unproductiveIterations == maxUnproductiveIterations) { // 如果无产出迭代计数等于最大无产出迭代次数unproductiveIterations = 0; // 重置无产出迭代计数器tau = bestTau; // 设置tau为当前最好的tau值bestTau = 0; // 重置最好的tau值Mat bytes = aruco::Dictionary::getByteListFromBits(bestMarker); // 获取当前最佳marker的字节列表out.bytesList.push_back(bytes); // 将最佳marker添加到输出字典的字节列表中}}}// 更新生成字典的最大误差修正位数out.maxCorrectionBits = (tau - 1) / 2; // 计算并设置最大纠错位数return out; // 返回生成的自定义字典
}// 以下函数用于获取字典中的最小汉明距离
static inline int getMinDistForDict(const aruco::Dictionary& dict) {// 定义一个静态内联函数,用于计算ArUco字典中所有标记的最小汉明距离const int dict_size = dict.bytesList.rows; // 获取字典中标记的数量const int marker_size = dict.markerSize; // 获取字典中标记的尺寸int minDist = marker_size * marker_size; // 初始化最小距离为标记尺寸的平方// 双重循环遍历每一对标记for (int i = 0; i < dict_size; i++) {Mat row = dict.bytesList.row(i); // 获取第i个标记的字节行Mat marker = dict.getBitsFromByteList(row, marker_size); // 将字节行转换为二进制位矩阵for (int j = 0; j < dict_size; j++) {// 确保不与自身比较if (j != i) {// 获取第i个标记与第j个标记的汉明距离,并更新最小距离minDist = min(dict.getDistanceToId(marker, j), minDist);}}}return minDist; // 返回最小距离
}
// 以下函数用于获取字典在考虑翻转marker的情况下的最小汉明距离
static inline int getMinAsymDistForDict(const aruco::Dictionary& dict) {// 定义一个静态内联函数,用于计算考虑翻转的情况下ArUco字典中所有标记的最小汉明距离const int dict_size = dict.bytesList.rows; // 获取字典中标记的数量const int marker_size = dict.markerSize; // 获取字典中标记的尺寸int minDist = marker_size * marker_size; // 初始化最小距离为标记尺寸的平方// 双重循环遍历每一对标记for (int i = 0; i < dict_size; i++){Mat row = dict.bytesList.row(i); // 获取第i个标记的字节行Mat marker = dict.getBitsFromByteList(row, marker_size); // 将字节行转换为二进制位矩阵for (int j = 0; j < dict_size; j++){if (j != i) // 确保不与自身比较{// 获取考虑翻转的情况下第i个标记与第j个标记的汉明距离,并更新最小距离minDist = min(getFlipDistanceToId(dict, marker, j), minDist);}}}return minDist; // 返回考虑翻转的最小距离
}// 命令行参数定义字符串
const char* keys  ="{@outfile |<none> | Output file with custom dict }" // 输出文件参数,输出自定义字典到该文件"{r | false | Calculate the metric considering flipped markers }" // 计算考虑翻转标记的度量标准"{d | | Dictionary Name: ...}" // 字典名称参数"{nMarkers | | Number of markers in the dictionary }" // 字典中标记的数量参数"{markerSize | | Marker size }" // 标记大小参数"{cd | | Input file with custom dictionary }"; // 自定义字典的输入文件参数// 程序简介
const char* about ="This program can be used to calculate the ArUco dictionary metric.\n""To calculate the metric considering flipped markers use -'r' flag.\n""This program can be used to create and write the custom ArUco dictionary.\n";int main(int argc, char *argv[])
{CommandLineParser parser(argc, argv, keys); // 初始化命令行解析器parser.about(about); // 设置关于程序的信息if(argc < 2) {parser.printMessage(); // 如果参数数量不够,打印帮助信息return 0;}// 从解析器中取得命令行参数string outputFile = parser.get<String>(0); // 获取输出文件名int nMarkers = parser.get<int>("nMarkers"); // 获取标记数量int markerSize = parser.get<int>("markerSize"); // 获取标记大小bool checkFlippedMarkers = parser.get<bool>("r"); // 获取是否检查翻转标记的标志// 创建一个默认的ArUco字典aruco::Dictionary dictionary = aruco::getPredefinedDictionary(0);if (parser.has("d")) {// 如果提供了字典名称参数,获取相应的预定义字典string arucoDictName = parser.get<string>("d"); // 获取字典名称cv::aruco::PredefinedDictionaryType arucoDict; // 定义预定义字典类型变量// 根据提供的字典名称设置预定义字典类型变量if (arucoDictName == "DICT_4X4_50") { arucoDict = cv::aruco::DICT_4X4_50; } // 如果字典名称是"DICT_4X4_50"else if (arucoDictName == "DICT_4X4_100") { arucoDict = cv::aruco::DICT_4X4_100; } // 如果字典名称是"DICT_4X4_100"else if (arucoDictName == "DICT_4X4_250") { arucoDict = cv::aruco::DICT_4X4_250; } // 如果字典名称是"DICT_4X4_250"else if (arucoDictName == "DICT_4X4_1000") { arucoDict = cv::aruco::DICT_4X4_1000; } // 如果字典名称是"DICT_4X4_1000"else if (arucoDictName == "DICT_5X5_50") { arucoDict = cv::aruco::DICT_5X5_50; } // 如果字典名称是"DICT_5X5_50"else if (arucoDictName == "DICT_5X5_100") { arucoDict = cv::aruco::DICT_5X5_100; } // 如果字典名称是"DICT_5X5_100"else if (arucoDictName == "DICT_5X5_250") { arucoDict = cv::aruco::DICT_5X5_250; } // 如果字典名称是"DICT_5X5_250"else if (arucoDictName == "DICT_5X5_1000") { arucoDict = cv::aruco::DICT_5X5_1000; } // 如果字典名称是"DICT_5X5_1000"else if (arucoDictName == "DICT_6X6_50") { arucoDict = cv::aruco::DICT_6X6_50; } // 如果字典名称是"DICT_6X6_50"else if (arucoDictName == "DICT_6X6_100") { arucoDict = cv::aruco::DICT_6X6_100; } // 如果字典名称是"DICT_6X6_100"else if (arucoDictName == "DICT_6X6_250") { arucoDict = cv::aruco::DICT_6X6_250; } // 如果字典名称是"DICT_6X6_250"else if (arucoDictName == "DICT_6X6_1000") { arucoDict = cv::aruco::DICT_6X6_1000; } // 如果字典名称是"DICT_6X6_1000"else if (arucoDictName == "DICT_7X7_50") { arucoDict = cv::aruco::DICT_7X7_50; } // 如果字典名称是"DICT_7X7_50"else if (arucoDictName == "DICT_7X7_100") { arucoDict = cv::aruco::DICT_7X7_100; } // 如果字典名称是"DICT_7X7_100"else if (arucoDictName == "DICT_7X7_250") { arucoDict = cv::aruco::DICT_7X7_250; } // 如果字典名称是"DICT_7X7_250"else if (arucoDictName == "DICT_7X7_1000") { arucoDict = cv::aruco::DICT_7X7_1000; } // 如果字典名称是"DICT_7X7_1000"else if (arucoDictName == "DICT_ARUCO_ORIGINAL") { arucoDict = cv::aruco::DICT_ARUCO_ORIGINAL; } // 如果字典名称是"DICT_ARUCO_ORIGINAL"else if (arucoDictName == "DICT_APRILTAG_16h5") { arucoDict = cv::aruco::DICT_APRILTAG_16h5; } // 如果字典名称是"DICT_APRILTAG_16h5"else if (arucoDictName == "DICT_APRILTAG_25h9") { arucoDict = cv::aruco::DICT_APRILTAG_25h9; } // 如果字典名称是"DICT_APRILTAG_25h9"else if (arucoDictName == "DICT_APRILTAG_36h10") { arucoDict = cv::aruco::DICT_APRILTAG_36h10; } // 如果字典名称是"DICT_APRILTAG_36h10"else if (arucoDictName == "DICT_APRILTAG_36h11") { arucoDict = cv::aruco::DICT_APRILTAG_36h11; } // 如果字典名称是"DICT_APRILTAG_36h11"else {cout << "incorrect name of aruco dictionary \n"; // 如果没有匹配的字典名称,打印错误信息return 1; // 返回非零值表示错误}dictionary = aruco::getPredefinedDictionary(arucoDict); // 从预定义字典类型获取预定义字典}else if (parser.has("cd")) {// 如果提供了自定义字典文件参数,读取自定义字典FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);bool readOk = dictionary.readDictionary(fs.root());if(!readOk) {cerr << "Invalid dictionary file" << endl; // 如果读取失败,打印错误信息return 0;}}else if (outputFile.empty() || nMarkers == 0 || markerSize == 0) {cerr << "Dictionary not specified" << endl; // 如果必要的参数没有提供,打印错误信息return 0;}if (!outputFile.empty() && nMarkers > 0 && markerSize > 0){// 如果提供了所需要的参数,则生成自定义字典并写入文件FileStorage fs(outputFile, FileStorage::WRITE);if (checkFlippedMarkers)dictionary = generateCustomAsymmetricDictionary(nMarkers, markerSize, aruco::Dictionary(), 0); // 生成考虑翻转的自定义字典elsedictionary = aruco::extendDictionary(nMarkers, markerSize, aruco::Dictionary(), 0); // 扩展字典dictionary.writeDictionary(fs); // 写入字典到文件}// 计算并打印字典的最小汉明距离if (checkFlippedMarkers) {cout << "Hamming distance: " << getMinAsymDistForDict(dictionary) << endl; // 考虑翻转的最小汉明距离}else {cout << "Hamming distance: " << getMinDistForDict(dictionary) << endl; // 不考虑翻转的最小汉明距离}return 0; // 程序结束
}

a95473030047b144a27dd2c7dc85f85a.png

PS V:\learn\opencv\github\opencv\sources\test\x64\Debug> ./test.exe  @outfile=custom_dict.yml -nMarkers=50 -markerSize=6
Hamming distance: 12

c18005d654991924cef7f3497dcf74f5.png

@outfile=custom_dict.yml

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/796347.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在 Ubuntu 上安装和配置 Tomcat 服务器?

简介&#xff1a;最近有粉丝朋友在问如何在 Ubuntu 上安装和配置 Tomcat 服务器&#xff1f;今天特地写这篇文章进行解答&#xff0c;希望能够帮助到大家。 文章目录 Ubuntu上安装和配置Tomcat的详细步骤Tomcat在Linux环境下的安装与配置一、下载并上传Tomcat压缩包二、启动To…

云架构(三) 防腐层模式

Anti-corruption Layer pattern (https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer) 简单描述 实现一个门面或者适配层在新应用和历史遗留程序之间。在不同的系统之间实现一个门面或者适配层&#xff0c;不需要使用相同的语义。它在不…

961: 进制转换问

【学习版】 【C语言】 #include<iostream>struct SeqList {int top;int len;int* s; };void initStack(SeqList* stack, int len) {stack->s new int[len];stack->top -1;stack->len len; }void push(SeqList* stack, int x) {stack->s[stack->top] …

YOLOv5实战记录05 Pyside6可视化界面

个人打卡&#xff0c;慎看。 指路大佬&#xff1a;【手把手带你实战YOLOv5-入门篇】YOLOv5 Pyside6可视化界面_哔哩哔哩_bilibili 零、虚拟环境迁移路径后pip报错解决 yolov5-master文件夹我换位置后&#xff0c;无法pip install了。解决如下&#xff1a; activate.bat中修改…

.NET8 和 Vue.js 的前后端分离

在.NET 8中实现前后端分离主要涉及到两个部分&#xff1a;后端API的开发和前端应用的开发。后端API通常使用ASP.NET Core来构建&#xff0c;而前端应用则可以使用任何前端框架或技术栈&#xff0c;比如Vue.js、React或Angular等。下面是一个简化的步骤指南&#xff0c;帮助你在…

汇川PLC学习Day4:电机参数和气缸控制参数

汇川PLC学习Day4&#xff1a;伺服电机参数和气缸控制参数 一、伺服电机参数二、气缸参数1. 输入IO映射&#xff08;1&#xff09;输入IO映射&#xff08;2&#xff09; 输入IO触摸屏标签显示映射 2. 输出IO映射&#xff08;1&#xff09;输出IO映射&#xff08;2&#xff09; …

基于单片机电动车电压电流监测系统

**单片机设计介绍&#xff0c;基于单片机电动车电压电流监测系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的电动车电压电流监测系统是一个集成了电子技术、单片机编程以及电压电流测量技术的系统。其主要目的是…

Open3D (C++) 从.txt文件中读取数据到矩阵

目录 一、算法概述二、代码实现三、结果展示四、测试数据本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法概述 在进行实验的时候有时需要借助不同的工具来实现一些比较复杂的操作,比如使用matlab中自带的拉…

lua学习笔记4(运算符的学习)

print("*****************************lua运算符的学习*******************************") print("*****************************基本运算符*******************************") a1145 b8848 print("加法运算"..ab) print("减法运算".…

Spark-Scala语言实战(14)

在之前的文章中&#xff0c;我们学习了如何在spark中使用键值对中的fullOuterJoin&#xff0c;zip&#xff0c;combineByKey三种方法。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点…

考研高数(平面图形的面积,旋转体的体积)

1.平面图形的面积 纠正&#xff1a;参数方程求面积 2.旋转体的体积&#xff08;做题时&#xff0c;若以x为自变量不好计算&#xff0c;可以求反函数&#xff0c;y为自变量进行计算&#xff09;

【STL学习】(3)vector容器

前言 本章主要内容为两个部分&#xff1a; vector是什么&#xff1f;vector常用接口的使用。 一、vector的介绍 vector是表示可变大小数组的容器就像数组一样&#xff0c;vector也采用的连续空间来存储元素。也意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样…

【二分查找】Leetcode 在排序数组中查找元素的第一个和最后一个位置

题目解析 34. 在排序数组中查找元素的第一个和最后一个位置 我们使用暴力方法进行算法演化&#xff0c;寻找一个数字的区间&#xff0c;我们可以顺序查找&#xff0c;记录最终结果 首先数组是有序的&#xff0c;所以使用二分法很好上手&#xff0c;但是我们就仅仅使用上一道题…

C++的并发世界(七)——互斥锁

0.死锁的由来 假设有两个线程T1和T2&#xff0c;它们需要对两个互斥量mtx1和mtx2进行访问。而且需要按照以下顺序获取互斥量的所有权&#xff1a; -T1先获取mte1的所有权,再获取mt2的所有权。 -T2先获取 mtx2的所有权。再铁取 mtx1的所有权。 如果两个线程同时执行&#xff0c…

C++入门语法(命名空间缺省函数函数重载引用内联函数nullptr)

目录 前言 1. 什么是C 2. C关键字 3. 命名空间 3.1 命名空间的定义 3.2 命名空间的使用 4. C输入和输出 5. 缺省函数 5.1 概念 5.2 缺省参数分类 6. 函数重载 6.1 概念 6.2 为何C支持函数重载 7. 引用 7.1 概念 7.2 特性 7.3 常引用 7.4 引用与指针的区别 7…

Node.JS多线程PromisePool之promise-pool库实现

什么是Promise Pool Map-like, concurrent promise processing for Node.js. Promise-Pool是一个用于管理并发请求的JavaScript库&#xff0c;它可以限制同时进行的请求数量&#xff0c;以避免过多的请求导致服务器压力过大。使用Promise-Pool可以方便地实现对多个异步操作的并…

pandas用法-详解教程

pandas用法-详解教程 一、生成数据表二、数据表信息查看三、数据表清洗四、数据预处理五、数据提取六、数据筛选七、数据汇总八、数据统计九、数据输出 一、生成数据表 1、首先导入pandas库&#xff0c;一般都会用到numpy库&#xff0c;所以我们先导入备用&#xff1a; impor…

Vue3 项目实例(二)vite.config.ts的配置与axios安装

一、vite.config.ts的配置 1、对相对路径的处理&#xff08;&#xff09; import { defineConfig } from vite import vue from vitejs/plugin-vue // vite 提供node核心对象path import path from path // https://vitejs.dev/config/ export default defineConfig({plugins…

计算机网络 实验指导 实验12

路由信息协议&#xff08;RIP&#xff09;实验 1.实验拓扑图 名称接口IP地址网关Switch AF0/1192.168.1.1/24F0/2172.1.1.1/24Switch BF0/1192.168.1.2/24F0/2172.2.2.1/24PC1172.1.1.2/24172.1.1.1PC2172.1.1.3/24172.1.1.1PC3172.2.2.2/24172.2.2.1PC4172.2.2.3/24172.2.2.1…

Linux——进程管理

1.gcc与g区别(补充了解) 比如有两个文件:main.c,mainc.cpp(分别用C语言和C语言写的)如果要用gcc编译呢? gcc -o mainc main.c gcc -o mainc mainc.cpp -lstdc 指明用c的标准库; 区别一: gcc默认只链接C库,并不会链接C的库;g会默认链接c标准库. 区别二: gcc编译.c文件,则按照C语…