YOLOv5-训练自己的VOC格式数据集(VOC、自建数据集)

YOLOv5:训练自己的 VOC 格式数据集

1. 自定义数据集

1.1 环境安装

pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

注意

  1. 安装 lxml
  2. Pillow 版本要低于 10.0.0,解释链接: module ‘PIL.Image’ has no attribute ‘ANTIALIAS’ 问题处理

1.2 创建数据集

我们自己下载 PASCAL VOC 也行,按照 PASCAL VOC 自建一个也行,具体过程见 PASCAL VOC 2012数据集讲解与制作自己的数据集。

文章不长

1.3 PASCAL VOC 数据集结构

PASCAL VOC 数据集结构如下所示。

PASCAL VOC 2012 数据集
|
├── VOC2012
|   ├── JPEGImages    # 包含所有图像文件
|   |   ├── 2007_000027.jpg
|   |   ├── 2007_000032.jpg
|   |   ├── ...
|   |
|   ├── Annotations    # 包含所有标注文件(XML格式)
|   |   ├── 2007_000027.xml
|   |   ├── 2007_000032.xml
|   |   ├── ...
|   |
|   ├── ImageSets
|   |   ├── Main
|   |   |   ├── train.txt  # 训练集的图像文件列表
|   |   |   ├── val.txt    # 验证集的图像文件列表
|   |   |   ├── test.txt   # 测试集的图像文件列表
|   |
|   ├── SegmentationClass  # 语义分割的标注
|   |   ├── 2007_000032.png
|   |   ├── ...
|   |
|   ├── SegmentationObject  # 物体分割的标注
|   |   ├── 2007_000032.png
|   |   ├── ...
|   |
|   ├── ...               # 其他可能的子文件夹
|
├── VOCdevkit
|   ├── VOCcode          # 包含用于处理数据集的工具代码
|
├── README

我们可以看到,对于我们来说,我们只需要两个文件夹就可以了。

  1. JPEGImages: 存放所有的图片
  2. Annotations: 存放所有的标注信息

这里我们从 PASCAL VOC 中提取出几张图片,组成 VOC2012-Lite:

即此时我们的数据集结构为:

VOCdevkit
└─VOC2012-Lite├─Annotations│      2007_000027.xml│      2007_000032.xml│      2007_000033.xml│      2007_000039.xml│      2007_000042.xml│      2007_000061.xml│      ...│└─JPEGImages2007_000027.jpg2007_000032.jpg2007_000033.jpg2007_000039.jpg2007_000042.jpg2007_000061.jpg...

需要注意的是,YOLOv5 的要求标注文件后缀为 .txt,但 Annotations 中的文件后缀是 .xml,所以我们需要进行转换。

YOLO 标注文件说明

标注文件举例:

0 0.481719 0.634028 0.690625 0.713278
1 0.741094 0.524306 0.314750 0.933389
2 0.254162 0.247742 0.574520 0.687422

其中,每行代表一个物体的标注,每个标注包括五个值,分别是:

  1. <class_id>:物体的类别标识符。在这里,有三个不同的类别,分别用 0、1 和 2 表示。
  2. <center_x>:物体边界框的中心点 x 坐标,归一化到图像宽度。这些值的范围应在 0 到 1 之间。
  3. <center_y>:物体边界框的中心点 y 坐标,归一化到图像高度。同样,这些值的范围应在 0 到 1 之间。
  4. <width>:物体边界框的宽度,归一化到图像宽度。
  5. <height>:物体边界框的高度,归一化到图像高度。

以第一行为例:

  • <class_id> 是 0,表示这个物体属于类别 0。
  • <center_x> 是 0.481719,这意味着物体边界框的中心点 x 坐标位于图像宽度的 48.17% 处。
  • <center_y> 是 0.634028,中心点 y 坐标位于图像高度的 63.40% 处。
  • <width> 是 0.690625,边界框宽度占图像宽度的 69.06%。
  • <height> 是 0.713278,边界框高度占图像高度的 71.33%。

1.4 YOLO 想要的数据集结构

1.4.1 YOLOv3

一般而言,YOLOv3 想要的数据结构如下所示:

YOLOv3 数据集
|
├── images         # 包含所有图像文件
|   ├── image1.jpg
|   ├── image2.jpg
|   ├── ...
|
├── labels         # 包含所有标注文件(每个图像对应一个标注文件)
|   ├── image1.txt
|   ├── image2.txt
|   ├── ...
|
├── classes.names  # 类别文件,包含所有类别的名称
|
├── train.txt      # 训练集的图像文件列表
├── valid.txt      # 验证集的图像文件列表

1.4.2 YOLOv5

与 YOLOv3 不同,YOLOv5 所需要的数据集结构如下所示:

|-- test
|   |-- images
|   |   |-- 000000000036.jpg
|   |   `-- 000000000042.jpg
|   `-- labels
|       |-- 000000000036.txt
|       `-- 000000000042.txt
|-- train
|   |-- images
|   |   |-- 000000000009.jpg
|   |   `-- 000000000025.jpg
|   `-- labels
|       |-- 000000000009.txt
|       `-- 000000000025.txt
`-- val|-- images|   |-- 000000000030.jpg|   `-- 000000000034.jpg`-- labels|-- 000000000030.txt`-- 000000000034.txt

既然我们已经知道了 YOLOv5 所需要的数据集格式,那么就可以动手了!

1.5 将 PASCAL VOC 数据集转换为 YOLOv5 数据集格式

"""
本脚本有两个功能:1. 将 voc 数据集标注信息(.xml)转为 yolo 标注格式(.txt),并将图像文件复制到相应文件夹2. 根据 json 标签文件,生成对应 names 标签(my_data_label.names)3. 兼容 YOLOv3 和 YOLOv5
"""
import os
from tqdm import tqdm
from lxml import etree
import json
import shutil
import argparse
from tqdm import tqdm
from prettytable import PrettyTable
from sklearn.model_selection import train_test_splitdef args_table(args):# 创建一个表格table = PrettyTable(["Parameter", "Value"])table.align["Parameter"] = "l"  # 使用 "l" 表示左对齐table.align["Value"] = "l"  # 使用 "l" 表示左对齐# 将args对象的键值对添加到表格中for key, value in vars(args).items():# 处理列表的特殊格式if isinstance(value, list):value = ', '.join(map(str, value))table.add_row([key, value])# 返回表格的字符串表示return str(table)def generate_train_and_val_txt(args):target_train_file = args.train_txt_pathtarget_val_file = args.val_txt_path# 获取源文件夹中的所有文件files = os.listdir(args.voc_images_path)# 划分训练集和验证集train_images, val_images = train_test_split(files, test_size=args.val_size, random_state=args.seed)# 打开目标文件以写入模式with open(target_train_file, 'w', encoding='utf-8') as f:# 使用tqdm创建一个进度条,迭代源文件列表for file in tqdm(train_images, desc=f"\033[1;33mProcessing Files for train\033[0m"):file_name, _ = os.path.splitext(file)# 写入文件名f.write(f'{file_name}\n')with open(target_val_file, 'w', encoding='utf-8') as f:# 使用tqdm创建一个进度条,迭代源文件列表for file in tqdm(val_images, desc=f"\033[1;33mProcessing Files for val\033[0m"):file_name, _ = os.path.splitext(file)# 写入文件名f.write(f'{file_name}\n')print(f"\033[1;32m文件名已写入到 {target_train_file}{target_val_file} 文件中!\033[0m")def parse_args():# 创建解析器parser = argparse.ArgumentParser(description="将 .xml 转换为 .txt")# 添加参数parser.add_argument('--voc_root', type=str, default="VOCdevkit", help="PASCAL VOC路径(之后的所有路径都在voc_root下)")parser.add_argument('--voc_version', type=str, default="VOC2012-Lite", help="VOC 版本")parser.add_argument('--save_path', type=str, default="VOC2012-YOLO", help="转换后的保存目录路径")parser.add_argument('--train_list_name', type=str, default="train.txt", help="训练图片列表名称")parser.add_argument('--val_list_name', type=str, default="val.txt", help="验证图片列表名称")parser.add_argument('--val_size', type=float, default=0.1, help="验证集比例")parser.add_argument('--seed', type=int, default=42, help="随机数种子")parser.add_argument('--num_classes', type=int, default=20, help="数据集类别数(用于校验)")parser.add_argument('--classes', help="数据集具体类别数(用于生成 classes.json 文件)", default=['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat','chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'])return parser.parse_args()def configure_path(args):# 转换的训练集以及验证集对应txt文件args.train_txt = "train.txt"args.val_txt = "val.txt"# 转换后的文件保存目录args.save_file_root = os.path.join(args.voc_root, args.save_path)# 生成json文件# label标签对应json文件args.label_json_path = os.path.join(args.voc_root, "classes.json")# 创建一个将类别与数值关联的字典class_mapping = {class_name: index + 1 for index, class_name in enumerate(args.classes)}with open(args.label_json_path, 'w', encoding='utf-8') as json_file:json.dump(class_mapping, json_file, ensure_ascii=False, indent=4)print(f'\033[1;31m类别列表已保存到 {args.label_json_path}\033[0m')# 拼接出voc的images目录,xml目录,txt目录args.voc_images_path = os.path.join(args.voc_root, args.voc_version, "JPEGImages")args.voc_xml_path = os.path.join(args.voc_root, args.voc_version, "Annotations")args.train_txt_path = os.path.join(args.voc_root, args.voc_version, args.train_txt)args.val_txt_path = os.path.join(args.voc_root, args.voc_version, args.val_txt)# 生成对应的 train.txt 和 val.txtgenerate_train_and_val_txt(args)# 检查文件/文件夹都是否存在assert os.path.exists(args.voc_images_path), f"VOC images path not exist...({args.voc_images_path})"assert os.path.exists(args.voc_xml_path), f"VOC xml path not exist...({args.voc_xml_path})"assert os.path.exists(args.train_txt_path), f"VOC train txt file not exist...({args.train_txt_path})"assert os.path.exists(args.val_txt_path), f"VOC val txt file not exist...({args.val_txt_path})"assert os.path.exists(args.label_json_path), f"label_json_path does not exist...({args.label_json_path})"if os.path.exists(args.save_file_root) is False:os.makedirs(args.save_file_root)print(f"创建文件夹:{args.save_file_root}")def parse_xml_to_dict(xml):"""将xml文件解析成字典形式,参考tensorflow的recursive_parse_xml_to_dictArgs:xml: xml tree obtained by parsing XML file contents using lxml.etreeReturns:Python dictionary holding XML contents."""if len(xml) == 0:  # 遍历到底层,直接返回tag对应的信息return {xml.tag: xml.text}result = {}for child in xml:child_result = parse_xml_to_dict(child)  # 递归遍历标签信息if child.tag != 'object':result[child.tag] = child_result[child.tag]else:if child.tag not in result:  # 因为object可能有多个,所以需要放入列表里result[child.tag] = []result[child.tag].append(child_result[child.tag])return {xml.tag: result}def translate_info(file_names: list, save_root: str, class_dict: dict, train_val='train', args=None):"""将对应xml文件信息转为yolo中使用的txt文件信息:param file_names::param save_root::param class_dict::param train_val::return:"""save_txt_path = os.path.join(save_root, train_val, "labels")if os.path.exists(save_txt_path) is False:os.makedirs(save_txt_path)save_images_path = os.path.join(save_root, train_val, "images")if os.path.exists(save_images_path) is False:os.makedirs(save_images_path)for file in tqdm(file_names, desc="translate {} file...".format(train_val)):# 检查下图像文件是否存在img_path = os.path.join(args.voc_images_path, file + ".jpg")assert os.path.exists(img_path), "file:{} not exist...".format(img_path)# 检查xml文件是否存在xml_path = os.path.join(args.voc_xml_path, file + ".xml")assert os.path.exists(xml_path), "file:{} not exist...".format(xml_path)# read xmlwith open(xml_path) as fid:xml_str = fid.read()xml = etree.fromstring(xml_str)data = parse_xml_to_dict(xml)["annotation"]img_height = int(data["size"]["height"])img_width = int(data["size"]["width"])# write object info into txtassert "object" in data.keys(), "file: '{}' lack of object key.".format(xml_path)if len(data["object"]) == 0:# 如果xml文件中没有目标就直接忽略该样本print("Warning: in '{}' xml, there are no objects.".format(xml_path))continuewith open(os.path.join(save_txt_path, file + ".txt"), "w") as f:for index, obj in enumerate(data["object"]):# 获取每个object的box信息xmin = float(obj["bndbox"]["xmin"])xmax = float(obj["bndbox"]["xmax"])ymin = float(obj["bndbox"]["ymin"])ymax = float(obj["bndbox"]["ymax"])class_name = obj["name"]class_index = class_dict[class_name] - 1  # 目标id从0开始# 进一步检查数据,有的标注信息中可能有w或h为0的情况,这样的数据会导致计算回归loss为nanif xmax <= xmin or ymax <= ymin:print("Warning: in '{}' xml, there are some bbox w/h <=0".format(xml_path))continue# 将box信息转换到yolo格式xcenter = xmin + (xmax - xmin) / 2ycenter = ymin + (ymax - ymin) / 2w = xmax - xminh = ymax - ymin# 绝对坐标转相对坐标,保存6位小数xcenter = round(xcenter / img_width, 6)ycenter = round(ycenter / img_height, 6)w = round(w / img_width, 6)h = round(h / img_height, 6)info = [str(i) for i in [class_index, xcenter, ycenter, w, h]]if index == 0:f.write(" ".join(info))else:f.write("\n" + " ".join(info))# copy image into save_images_pathpath_copy_to = os.path.join(save_images_path, img_path.split(os.sep)[-1])if os.path.exists(path_copy_to) is False:shutil.copyfile(img_path, path_copy_to)def create_class_names(class_dict: dict, args):keys = class_dict.keys()with open(os.path.join(args.voc_root, "my_data_label.names"), "w") as w:for index, k in enumerate(keys):if index + 1 == len(keys):w.write(k)else:w.write(k + "\n")def main(args):# read class_indictjson_file = open(args.label_json_path, 'r')class_dict = json.load(json_file)# 读取train.txt中的所有行信息,删除空行with open(args.train_txt_path, "r") as r:train_file_names = [i for i in r.read().splitlines() if len(i.strip()) > 0]# voc信息转yolo,并将图像文件复制到相应文件夹translate_info(train_file_names, args.save_file_root, class_dict, "train", args=args)# 读取val.txt中的所有行信息,删除空行with open(args.val_txt_path, "r") as r:val_file_names = [i for i in r.read().splitlines() if len(i.strip()) > 0]# voc信息转yolo,并将图像文件复制到相应文件夹translate_info(val_file_names, args.save_file_root, class_dict, "val", args=args)# 创建my_data_label.names文件create_class_names(class_dict, args=args)if __name__ == "__main__":args = parse_args()configure_path(args)# 美化打印 argsprint(f"\033[1;34m{args_table(args)}\033[0m")# 执行 .xml 转 .txtmain(args)

我们在运行下面命令即可完成转换:

python voc2yolo.py --voc_root ./VOCdevkit --voc_version VOC2012-Lite

转换后的目录结构为:

VOCdevkit
│  classes.json
│  my_data_label.names
│  
├─VOC2012-Lite
│  │  train.txt
│  │  val.txt
│  │  
│  ├─Annotations
│  │      2007_000027.xml
│  │      2007_000032.xml
│  │      2007_000033.xml
│  │      2007_000039.xml
│  │      2007_000042.xml
│  │      2007_000061.xml
│  │      ...
│  │      
│  └─JPEGImages
│          2007_000027.jpg
│          2007_000032.jpg
│          2007_000033.jpg
│          2007_000039.jpg
│          2007_000042.jpg
│          2007_000061.jpg
│          ...
│
└─VOC2012-YOLO├─train│  ├─images│  │      2007_000032.jpg│  │      2007_000033.jpg│  │      2007_000039.jpg│  │      2007_000042.jpg│  │      2007_000061.jpg│  │      ...│  ││  └─labels│          2007_000032.txt│          2007_000033.txt│          2007_000039.txt│          2007_000042.txt│          2007_000061.txt│          ...│└─val├─images│      2007_000027.jpg│      ...│└─labels2007_000027.txt...

1.6 YOLOv5 配置文件变动

根据 .yaml 配置文件变动而变动的,这里我们复制 coco128.yamlcustom_dataset.yaml 为例:

# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license
# COCO128 dataset https://www.kaggle.com/ultralytics/coco128 (first 128 images from COCO train2017) by Ultralytics
# Example usage: python train.py --data coco128.yaml
# parent
# ├── yolov5
# └── datasets
#     └── coco128  ← downloads here (7 MB)# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: VOCdevkit/VOC2012-YOLO  # dataset root dir
train: train/images  # train images (relative to 'path') 128 images
val: val/images  # val images (relative to 'path') 128 images
test:  # test images (optional)# Classes
names:0: person1: bicycle2: car3: motorcycle4: airplane5: bus6: train7: truck8: boat9: traffic light10: fire hydrant...# Download script/URL (optional)
download: https://ultralytics.com/assets/coco128.zip

此时我们就可以使用这个数据集进行 YOLOv5 的模型训练了!

2. 模型选择

我们需要选择一个合适的模型来进行训练,在这里,我们选择 YOLOv5s,这是第二小和速度最快的可用模型。

3. 模型训练

通过指定数据集、批次大小、图像大小以及预训练权重 --weights yolov5s.pt在我们自建的数据集上训练 YOLOv5s 模型。

export CUDA_VISIBLE_DEVICES=4
python train.py --img 640 \--epochs 150 \--data custom_dataset.yaml \--weights weights/yolov5s.pt \--batch-size 32 \--single-cls \--project runs/train \--cos-lr

为了加快训练速度,可以添加 --cache ram--cache disk 选项(需要大量的内存/磁盘资源)。所有训练结果都会保存在 runs/train/ 目录下,每次训练都会创建一个递增的运行目录,例如 runs/train/exp2runs/train/exp3 等等。

2.5 可视化

训练结果会自动记录在 Tensorboard 和 CSV 日志记录器中,保存在 runs/train 目录下,每次新的训练都会创建一个新的实验目录,例如 runs/train/exp2runs/train/exp3 等。

该目录包含了训练和验证的统计数据、马赛克图像、标签、预测结果、以及经过增强的马赛克图像,还包括 Precision-Recall(PR)曲线和混淆矩阵等度量和图表。

结果文件 results.csv 在每个 Epoch 后更新,然后在训练完成后绘制为 results.png(如下所示)。我们也可以手动绘制任何 results.csv 文件:

from utils.plots import plot_resultsplot_results('path/to/results.csv')  # plot 'results.csv' as 'results.png'

知识来源

  1. Ultralytics YOLOv5 Docs
  2. 【CSDN】PASCAL VOC 2012 数据集讲解与制作自己的数据集
  3. 【Bilibili】PASCAL VOC 2012 数据集讲解与制作自己的数据集
  4. trans_voc2yolo.py

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

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

相关文章

Jetpack:012-Jetpack中的弹出菜单

文章目录 1. 概念介绍2. 使用方法2.1 DropdownMenu2.2 DropdownMenuItem 3. 示例代码3.1 代码和注释3.2 代码难点3.3 运行效果 4. 内容总结 我们在上一章回中介绍了Jetpack中标题栏相关的内容&#xff0c;本章回中主要 弹出菜单。闲话休提&#xff0c;让我们一起Talk Android …

AI系统ChatGPT源码+详细搭建部署教程+支持GPT4.0+支持ai绘画(Midjourney)/支持OpenAI GPT全模型+国内AI全模型

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统AI绘画系统&#xff0c;支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署…

【vSphere 8 自签名证书】企业 CA 签名证书替换 vSphere Machine SSL 证书Ⅱ—— 创建和添加证书模板

目录 博文摘要3. 使用 Microsoft 证书颁发机构创建 Machine SSL 和 Solution User 证书模板3.1 打开 Certificate Template Console3.2 复制模板3.3 修改 Compatibility 选项卡3.4 修改 General 选项卡3.5 修改 Extensions 选项卡3.6 修改 Subject Name 选项卡3.7 确认新模板 4…

软件工程与计算总结(十六)详细设计的设计模式

一.设计模式基础 某种意义上来说&#xff0c;设计模式就是设计经验的总结~ 设计模式不是简单的经验总结&#xff0c;更不是无中生有&#xff0c;它是经过实践反复检验、能解决关键技术难题、有广泛应用前景和能够显著提高软件质量的有效的经验总结。 每个模式都不是独立的&a…

Docker安装GitLab及使用图文教程

作者&#xff1a; 宋发元 GitLab安装及使用教程 官方教程 https://docs.gitlab.com/ee/install/docker.html Docker安装GitLab 宿主机创建容器持久化目录卷 mkdir -p /docker/gitlab/{config,data,logs}拉取GitLab镜像 docker pull gitlab/gitlab-ce:15.3.1-ce.0运行GitLa…

Linux性能优化--性能追踪:受CPU限制的应用程序(GIMP)

10.0 概述 本章包含了一个例子&#xff1a;如何用Linux性能工具在受CPU限制的应用程序中寻找并修复性能问题。 阅读本章后&#xff0c;你将能够&#xff1a; 在受CPU限制的应用程序中明确所有的CPU被哪些源代码行使用。用1trace和oprofile弄清楚应用程序调用各种内部与外部函…

Jmeter接口测试 —— jmeter对图片验证码的处理

jmeter对图片验证码的处理 在web端的登录接口经常会有图片验证码的输入&#xff0c;而且每次登录时图片验证码都是随机的&#xff1b;当通过jmeter做接口登录的时候要对图片验证码进行识别出图片中的字段&#xff0c;然后再登录接口中使用&#xff1b; 通过jmeter对图片验证码…

在启智平台上安装anconda

安装Anaconda3-5.0.1-Linux-x86_64.sh python版本是3.6 在下面的网站上找到要下载的anaconda版本&#xff0c;把对应的.sh文件下载下来 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 把sh文件压缩成.zip文件&#xff0c;拖到启智平台的调试页面 上传到平台上 un…

《从菜鸟到大师之路 正则表达式 篇》

《从菜鸟到大师之路 正则表达式 篇》 正则表达式是一个强大的文本匹配工具。但是&#xff0c;对于前端初学者来说&#xff0c;众多的符号和规则可能让人难以理解。其实&#xff0c;你不需要记住所有的正则表达式语法&#xff01;本文将分享一些简单而实用的技巧&#xff0c;帮…

【C++ 学习 ㉘】- 详解 C++11 的列表初始化

目录 一、C11 简介 二、列表初始化 2.1 - 统一初始化 2.2 - 列表初始化的使用细节 2.2.1 - 聚合类型的定义 2.2.2 - 注意事项 2.3 - initializer_list 2.3.1 - 基本使用 2.3.2 - 源码剖析 一、C11 简介 1998 年&#xff0c;C 标准委员会发布了第一版 C 标准&#xff0…

vm虚拟机克隆ubuntu

1. 使用vm虚拟机自带的克隆功能 2. 选择完整克隆&#xff0c;然后选择您克隆到哪里的目录 3. 点击编辑你克隆后的虚拟机&#xff0c;点网络适配器&#xff0c;然后点高级&#xff0c;点击生成mac地址&#xff08;由于唯一&#xff0c;所以需要重新生成&#xff09; 4. 开启虚拟…

【MySQL】分析SQL的几种方式

文章目录 一、查看SQL执行频率二、定位低效率执行SQL1. show processlist2. 慢查询日志 三、explain分析执行计划1. id2. select_type3. type4. key5. extra 四、show profile 一、查看SQL执行频率 show session status&#xff1a;显示 session 级的统计结果&#xff08;不写…

openGauss学习笔记-100 openGauss 数据库管理-管理数据库安全-客户端接入之用SSL进行安全的TCP/IP连接

文章目录 openGauss学习笔记-100 openGauss 数据库管理-管理数据库安全-客户端接入之用SSL进行安全的TCP/IP连接100.1 背景信息100.2 前提条件100.3 注意事项100.4 操作步骤100.5 相关参考 openGauss学习笔记-100 openGauss 数据库管理-管理数据库安全-客户端接入之用SSL进行安…

excel+requests管理测试用例接口自动化框架

背景&#xff1a; 某项目有多个接口&#xff0c;之前使用的unittest框架来管理测试用例&#xff0c;将每个接口的用例封装成一个py文件&#xff0c;接口有数据或者字段变动后&#xff0c;需要去每个py文件中找出变动的接口测试用例&#xff0c;维护起来不方便&#xff0c;为了…

双目视觉实战--单视图测量方法

目录 一.简介 二、2D变换 1. 等距变换&#xff08;欧式变换&#xff09; 2. 相似变换 3. 仿射变换 4. 射影变换&#xff08;透视变换&#xff09; 5. 结论 三、影消点与影消线 1. 平面上的线 2. 直线的交点 3. 2D无穷远点 4. 无穷远直线 5. 无穷远点的透视变换与仿…

Spring Cloud Gateway 使用 Redis 限流使用教程

从本文开始&#xff0c;笔者将总结 spring cloud 相关内容的教程 版本选择 为了适应 java8&#xff0c;笔者选择了下面的版本&#xff0c;后续会出 java17的以SpringBoot3.0.X为主的教程 SpringBoot 版本 2.6.5 SpringCloud 版本 2021.0.1 SpringCloudAlibaba 版本 2021.0.1.…

Prometheus的Pushgateway快速部署及使用

prometheus-pushgateway安装 一. Pushgateway简介 Pushgateway为Prometheus整体监控方案的功能组件之一&#xff0c;并做于一个独立的工具存在。它主要用于Prometheus无法直接拿到监控指标的场景&#xff0c;如监控源位于防火墙之后&#xff0c;Prometheus无法穿透防火墙&…

C++基础——内存分区模型

1 概述 C程序在执行是&#xff0c;将内存大致分为4个区域&#xff1a; 代码区&#xff1a;用于存放二进制代码&#xff0c;由操作系统进行管理全局区&#xff1a;存放全局变量和静态变量及常量栈区&#xff1a;由编译器自动分配释放&#xff0c;存放函数的参数、局部变量等堆…

回首往昔,初学编程那会写过的两段愚蠢代码

一、关于判断两个整数是否能整除的GW BASIC创意代码 记得上大学时第一个编程语言是BASIC&#xff0c;当时Visual Basic还没出世&#xff0c;QBASIC虽然已经在1991年随MS-DOS5.0推出了&#xff0c;但我们使用的还是 GW-BASIC&#xff0c; 使用的教材是谭浩强、田淑清编著的《BA…

【广州华锐互动】VR建筑安全培训体验为建筑行业人才培养提供有力支持

随着建筑行业的快速发展&#xff0c;建筑施工安全问题日益受到广泛关注。然而&#xff0c;传统的安全培训方式往往缺乏实践性和真实性&#xff0c;难以让员工真正掌握安全操作技能。近年来&#xff0c;虚拟现实(VR)技术的广泛应用为建筑施工安全培训提供了新的机遇。 虚拟现实技…