一.数据准备:
1.数据集格式:
目录结构示例:
datasets/
├── images/
│ ├── train/ # 训练图片
│ └── val/ # 验证图片
└── labels/
├── train/ # 训练标签
└── val/ # 验证标签
每张图片对应一个 .txt
标注文件,内容为:
<class_id> <x_center> <y_center> <width> <height> # 归一化坐标(0~1)
2.将 VOC 格式的数据集转换为 YOLO 格式的数据集:
把 VOC 格式(XML标注)的数据集,转换成 YOLO 格式(TXT文本标注)的数据集,方便 YOLO 模型直接训练。
关键转换步骤:
(1) 坐标转换(核心)
VOC 的 XML 中存储的是绝对坐标:
<xmin>100</xmin>
<ymin>50</ymin>
<xmax>300</xmax>
<ymax>200</ymax>
👉 转换为 YOLO 的相对坐标(0~1):
0 0.25 0.125 0.5 0.375
(格式:类别ID x_中心 y_中心 宽度 高度
)
(2) 自动生成类别映射
- 扫描所有 XML 文件,自动提取类别名称(如
cat
,dog
)。 - 为每个类别分配数字 ID(如
cat→0
,dog→1
)。
(3) 文件结构重组
VOC格式目录结构: YOLO格式目录结构:
D:/dataset/ D:/dataset/
├── Annotations/ ├── images/
│ ├── 001.xml │ ├── 001.jpg
│ └── 002.xml │ └── 002.jpg
└── JPEGImages/ └── labels/
├── 001.jpg ├── 001.txt
└── 002.jpg └── 002.txt
为什么需要这个转换?
- YOLO 不吃 XML:YOLO 系列模型训练时需要的是简单的 TXT 标注文件。
- 统一标准:避免手动标注格式混乱。
- 效率提升:自动化转换比手动处理快100倍。
完整代码:
如何使用?
- 确保你的 VOC 数据集有
Annotations/
和JPEGImages/
目录。 - 修改代码中的路径:
voc_data_path = '你的/VOC/数据集路径' yolo_data_path = '输出的/YOLO/路径'
- 运行脚本,得到可直接用于 YOLO 训练的数据集。
import os
import shutil
import xml.etree.ElementTree as ET
# VOC格式数据集路径
voc_data_path = 'D:/YOLO/ultralytics-8.3.39/fall'
voc_annotations_path = os.path.join(voc_data_path, 'Annotations')
voc_images_path = os.path.join(voc_data_path, 'JPEGImages')
# YOLO格式数据集保存路径
yolo_data_path = 'D:/YOLO/ultralytics-8.3.39/data'
yolo_images_path = os.path.join(yolo_data_path, 'images')
yolo_labels_path = os.path.join(yolo_data_path, 'labels')
# 创建YOLO格式数据集目录
os.makedirs(yolo_images_path, exist_ok=True)
os.makedirs(yolo_labels_path, exist_ok=True)
# 自动生成类别映射
class_mapping = {}
class_id = 0
for voc_annotation in os.listdir(voc_annotations_path):
if voc_annotation.endswith('.xml'):
tree = ET.parse(os.path.join(voc_annotations_path, voc_annotation))
root = tree.getroot()
for obj in root.findall('object'):
cls = obj.find('name').text
if cls not in class_mapping:
class_mapping[cls] = class_id
class_id += 1
print("自动生成的类别映射:", class_mapping)
def convert_voc_to_yolo(voc_annotation_file, yolo_label_file):
tree = ET.parse(voc_annotation_file)
root = tree.getroot()
size = root.find('size')
width = float(size.find('width').text)
height = float(size.find('height').text)
with open(yolo_label_file, 'w') as f:
for obj in root.findall('object'):
cls = obj.find('name').text
if cls not in class_mapping:
continue
cls_id = class_mapping[cls]
xmlbox = obj.find('bndbox')
xmin = float(xmlbox.find('xmin').text)
ymin = float(xmlbox.find('ymin').text)
xmax = float(xmlbox.find('xmax').text)
ymax = float(xmlbox.find('ymax').text)
x_center = (xmin + xmax) / 2.0 / width
y_center = (ymin + ymax) / 2.0 / height
w = (xmax - xmin) / width
h = (ymax - ymin) / height
f.write(f"{cls_id} {x_center} {y_center} {w} {h}\n")
# 遍历VOC数据集的Annotations目录,进行转换
for voc_annotation in os.listdir(voc_annotations_path):
if voc_annotation.endswith('.xml'):
voc_annotation_file = os.path.join(voc_annotations_path, voc_annotation)
image_id = os.path.splitext(voc_annotation)[0]
voc_image_file = os.path.join(voc_images_path, f"{image_id}.jpg")
yolo_label_file = os.path.join(yolo_labels_path, f"{image_id}.txt")
yolo_image_file = os.path.join(yolo_images_path, f"{image_id}.jpg")
convert_voc_to_yolo(voc_annotation_file, yolo_label_file)
if os.path.exists(voc_image_file):
shutil.copy(voc_image_file, yolo_image_file)
print("转换完成!")
二.模型处理:
1.训练模型:
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
from ultralytics import YOLO
model = YOLO("yolo11n.pt") # Load a pretrained model
results = model.train(data="D:/YOLO/ultralytics-8.3.39/data.yaml",
epochs=100, imgsz=640, batch=16,device=0,workers=0, iou=0.4)
2.使用模型:
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
from ultralytics import YOLO
# Load a model
model = YOLO("D:/YOLO/ultralytics-8.3.39/runs/detect/train5/weights/best.pt")
# Perform object detection on an image
results = model("图片地址")
results[0].show()
3.pt转onnx:
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'
from ultralytics import YOLO
# 加载 YOLOv11 模型
model = YOLO('D:/YOLO/ultralytics-8.3.39/runs/detect/train3/weights/best.pt')
# 导出为 ONNX 格式
model.export(format='onnx', imgsz=640)
三.数据分析:
对比不同训练过程的结果(如不同模型或训练策略),通过图表展示以下指标:
- 精度指标:Precision、Recall、mAP@50、mAP@50-95
- 损失指标:训练集和验证集的Box/Class/DFL损失
项目目录:
项目目录/
├── plot_results.py # 当前脚本
├── env/ # 存放训练结果的目录
│ ├── results.csv # 第一次训练结果
│ └── results-final.csv # 第二次训练结果
然后修改配置文件路径:
results_files = [
'./env/results.csv', # 第一个训练结果文件
'./env/results-final.csv' # 第二个训练结果文件
]
custom_labels = [
'yolov11', # 第一个结果的图例标签
'yolov11-ema' # 第二个结果的图例标签
]
- 替换路径:将
./env/
改为你的实际结果文件路径(如runs/train/exp1/results.csv
) - 自定义标签:修改
custom_labels
中的名称,用于区分不同曲线(如"yolov11-baseline")
修改对比指标:
# 示例:添加F1分数对比
metrics = [
'metrics/precision(B)', 'metrics/recall(B)', 'metrics/mAP50(B)', 'metrics/F1'
]
labels = [
'Precision', 'Recall', 'mAP@50', 'F1 Score'
]
调整图表布局:
plot_comparison(metrics, labels, custom_labels, layout=(3, 2)) # 改为3行2列
保存图表:
plt.savefig('training_comparison.png', dpi=300, bbox_inches='tight')
下面是完整代码:
# -*- coding: utf-8 -*-
import pandas as pd
import matplotlib.pyplot as plt
# 训练结果列表
results_files = [
'./env/results.csv',
'./env/results-final.csv'
]
# 与results_files顺序对应
custom_labels = [
'yolov11',
'yolov11-ema',
]
#
def plot_comparison(metrics, labels, custom_labels, layout=(2, 2)):
fig, axes = plt.subplots(layout[0], layout[1], figsize=(15, 10)) # 创建网格布局
axes = axes.flatten() # 将子图对象展平,方便迭代
for i, (metric_key, metric_label) in enumerate(zip(metrics, labels)):
for file_path, custom_label in zip(results_files, custom_labels):
df = pd.read_csv(file_path)
# 清理列名中的多余空格
df.columns = df.columns.str.strip()
# 检查 'epoch' 列是否存在
if 'epoch' not in df.columns:
print(f"'epoch' column not found in {file_path}. Available columns: {df.columns}")
continue
# 检查目标指标列是否存在
if metric_key not in df.columns:
print(f"'{metric_key}' column not found in {file_path}. Available columns: {df.columns}")
continue
# 在对应的子图上绘制线条
axes[i].plot(df['epoch'], df[metric_key], label=f'{custom_label}')
axes[i].set_title(f' {metric_label}')
axes[i].set_xlabel('Epochs')
axes[i].set_ylabel(metric_label)
axes[i].legend()
plt.tight_layout() # 自动调整子图布局,防止重叠
plt.show()
if __name__ == '__main__':
# 精度指标
metrics = [
'metrics/precision(B)', 'metrics/recall(B)', 'metrics/mAP50(B)', 'metrics/mAP50-95(B)'
]
labels = [
'Precision', 'Recall', 'mAP@50', 'mAP@50-95'
]
# 调用通用函数绘制精度对比图
plot_comparison(metrics, labels, custom_labels, layout=(2, 2))
# 损失指标
loss_metrics = [
'train/box_loss', 'train/cls_loss', 'train/dfl_loss', 'val/box_loss', 'val/cls_loss', 'val/dfl_loss'
]
loss_labels = [
'Train Box Loss', 'Train Class Loss', 'Train DFL Loss', 'Val Box Loss', 'Val Class Loss', 'Val DFL Loss'
]
# 调用通用函数绘制损失对比图
plot_comparison(loss_metrics, loss_labels, custom_labels, layout=(2, 3))