目录
一.数据集获取
1.使用开源数据集
2.自定义数据图片
二.数据集标注
1.标注工具介绍
2.labelme安装
3.数据标注
1.选择要标注的数据集文件夹
2.设置自动保存:
3.创建多边形标注
4.格式转换
1.安装labelme2yolo
2.格式转换
3.效果查看
4.其他格式转换
5.划分数据集
三.模型训练
1.模型训练
2.参数解读
1.训练参数
2.结果参数
3.模型验证
四.总结
随着YOLO(You Only Look Once)系列的不断进化,YOLOv8以其显著的精度提升和优化的架构设计,再次吸引了众多研究者和开发者的眼球。在本系列的第四篇文章中,我们将深入探讨如何在自定义数据集上训练YOLOv8模型,让你从零开始掌握目标检测的实战技能。
本系列其他文章
【YOLOv8系列】(一)YOLOv8介绍:实时目标检测的最新突破-CSDN博客
【YOLOv8系列】(二)YOLOv8环境配置,手把手嘴对嘴保姆教学-CSDN博客
【YOLOv8系列】(三)YOLOv8应用实践:从识别到分类再到分割的全方位视觉解决方案-CSDN博客
一.数据集获取
在机器学习和深度学习中,数据集是训练模型的基础。尤其在计算机视觉领域,数据集的重要性更为突出。以下是获取数据集的几个途径:
1.使用开源数据集
有许多公开的目标检测数据集可供使用。以下是一些常见的数据集:
- COCO (Common Objects in Context):一个大规模的图像数据集,包含80种类别的物体。可以从 COCO官网 下载。
- Pascal VOC:一个经典的目标检测数据集,包含20种类别的物体。可以从Pascal VOC官网 下载。
- Open Images:一个包含约900万张图像和600种类别的物体数据集。可以从 Open Images官网下载。
- ModelScope :魔搭社区
2.自定义数据图片
通过爬虫程序批量下载网络上的图片数据集,或者使用相机或其他来源收集您需要的图片
二.数据集标注
数据标注是通过人工把需要识别和分辨的数据贴上标签。深度神经网络学习这些标注数据的特征,最终实现自主识别的功能。
1.标注工具介绍
使用标注工具对图片中的目标进行标注。常用的标注工具包括:
- LabelImg:一个开源的图像标注工具,支持VOC和YOLO格式。可以从 LabelImg官网 下载。
- LabelMe:一个在线的图像标注工具,支持多种标注格式。可以从 LabelMe官网 使用。
- Roboflow:一个在线平台,提供数据集管理和标注工具。可以从 Roboflow官网 使用。
2.labelme安装
我个人比较推荐使用labelme,因为可以进行多边形标注。
pip安装
pip install labelme
终端输入labelme打开软件
labelme
界面如下所示
3.数据标注
1.选择要标注的数据集文件夹
2.设置自动保存:
文件—>自动保存
3.创建多边形标注
标注的数据会自动保存
4.格式转换
将标注文件转换为YOLO格式。YOLO格式的标注文件内容如下:
<class_id> <x_center> <y_center> <width> <height>
但我们标注好的json数据格式是这样的,需要将json格式转换成需要的txt
1.安装labelme2yolo
pip install labelme2yolo
2.格式转换
labelme2yolo --json_dir /path/to/labelme_json_dir/ # 将所有 LabelMe JSON 文件放在 labelme_json_dir 下
--json_dirLabelMe JSON 文件文件夹路径。
--val_size(可选)验证数据集大小,例如 0.2 表示 20% 用于验证。
--test_size(可选)测试数据集大小,例如 0.1 表示 Test 的 10%。
--json_name(可选)转换单个LabelMe JSON文件。
--output_format(可选)标签的输出格式。
--label_list(可选)预先分配的类别标签。
3.效果查看
运行后会出现一个名为YOLODataset的文件夹
项目结构如下所示:
YOLODataset/
├── images/
│ ├── train/
│ ├── val/
├── labels/
│ ├── train/
│ ├── val/
├── dataset.yaml
yaml文件如下
转换后的txt文件如下
可以看到,除了格式转换,labelme2yolo模块已经帮我们划分好了数据集以及验证集并生成好了yaml文件。一劳永逸,灰常好用!!!!!
4.其他格式转换
除了json文件,最常见的就是voc格式的xml文件,例如安全帽检测数据集。以下脚本可以将xml转成txt文件。
import xml.etree.ElementTree as ET
import os
def convert(size, box):
x_center = (box[0] + box[1]) / 2.0
y_center = (box[2] + box[3]) / 2.0
#分别计算纵坐标和横坐标的中心点
x = x_center / size[0]
y = y_center / size[1]
w = (box[1] - box[0]) / size[0]
h = (box[3] - box[2]) / size[1]
# print(x, y, w, h)
return (x, y, w, h)
def convert_annotation(xml_files_path, save_txt_files_path, classes):
xml_files = os.listdir(xml_files_path)
print(xml_files)
for xml_name in xml_files:
print(xml_name)
xml_file = os.path.join(xml_files_path, xml_name)
out_txt_path = os.path.join(save_txt_files_path, xml_name.split('.')[0] + '.txt')
out_txt_f = open(out_txt_path, 'w')
tree = ET.parse(xml_file)
root = tree.getroot()
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
for obj in root.iter('object'):
difficult = obj.find('difficult').text
cls = obj.find('name').text
if cls not in classes or int(difficult) == 1:
continue
cls_id = classes.index(cls)
xmlbox = obj.find('bndbox')
b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
float(xmlbox.find('ymax').text))
# b=(xmin, xmax, ymin, ymax)
print(w, h, b)
bb = convert((w, h), b)
out_txt_f.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
if __name__ == "__main__":
# 1、指定yolo类别
classes1 = ["class1","class2","class3"]
# 2、voc格式的xml标签文件路径
xml_files1 = r'/path/to/your/xml/folder'
# 3、转化为yolo格式的txt标签文件存储路径
save_txt_files1 = r'/path/to/your/txt/save/folder'
convert_annotation(xml_files1, save_txt_files1, classes1)
with open(save_txt_files1 + '/classes.txt', 'w') as file:
for class_name in classes1:
file.write(class_name + '\n')
5.划分数据集
如果你是通过labelme按照以上步骤标注自己数据集,这一步可以跳过。但如果是下载的公开已标注好的数据集,那么需要通过脚本进行数据集的划分。
import os
import random
import shutil
import time
import yaml
class YOLOTrainDataSetGenerator:
def __init__(self, origin_dataset_dir, train_dataset_dir, train_ratio=0.7, val_ratio=0.15, test_ratio=0.15,
clear_train_dir=False):
# 设置随机数种子
random.seed(1233)
self.origin_dataset_dir = origin_dataset_dir
self.train_dataset_dir = train_dataset_dir
self.train_ratio = train_ratio
self.val_ratio = val_ratio
self.test_ratio = test_ratio
self.clear_train_dir = clear_train_dir
assert self.train_ratio > 0.5, 'train_ratio must larger than 0.5'
assert self.val_ratio > 0.01, 'train_ratio must larger than 0.01'
assert self.test_ratio > 0.01, 'test_ratio must larger than 0.01'
total_ratio = round(self.train_ratio + self.val_ratio + self.test_ratio)
assert total_ratio == 1.0, 'train_ratio + val_ratio + test_ratio must equal 1.0'
def generate(self):
time_start = time.time()
# 原始数据集的图像目录,标签目录,和类别文件路径
origin_image_dir = os.path.join(self.origin_dataset_dir, 'images')
origin_label_dir = os.path.join(self.origin_dataset_dir, 'labels')
origin_classes_file = os.path.join(self.origin_dataset_dir, 'classes.txt')
if not os.path.exists(origin_classes_file):
return
else:
origin_classes = {}
with open(origin_classes_file, mode='r') as f:
for cls_id, cls_name in enumerate(f.readlines()):
cls_name = cls_name.strip()
if cls_name != '':
origin_classes[cls_id] = cls_name
# 获取所有原始图像文件名(包括后缀名)
origin_image_filenames = os.listdir(origin_image_dir)
# 随机打乱文件名列表
random.shuffle(origin_image_filenames)
# 计算训练集、验证集和测试集的数量
total_count = len(origin_image_filenames)
train_count = int(total_count * self.train_ratio)
val_count = int(total_count * self.val_ratio)
test_count = total_count - train_count - val_count
# 定义训练集文件夹路径
if self.clear_train_dir and os.path.exists(self.train_dataset_dir):
shutil.rmtree(self.train_dataset_dir, ignore_errors=True)
train_dir = os.path.join(self.train_dataset_dir, 'train')
val_dir = os.path.join(self.train_dataset_dir, 'val')
test_dir = os.path.join(self.train_dataset_dir, 'test')
train_image_dir = os.path.join(train_dir, 'images')
train_label_dir = os.path.join(train_dir, 'labels')
val_image_dir = os.path.join(val_dir, 'images')
val_label_dir = os.path.join(val_dir, 'labels')
test_image_dir = os.path.join(test_dir, 'images')
test_label_dir = os.path.join(test_dir, 'labels')
# 创建训练集输出文件夹
os.makedirs(train_image_dir, exist_ok=True)
os.makedirs(train_label_dir, exist_ok=True)
os.makedirs(val_image_dir, exist_ok=True)
os.makedirs(val_label_dir, exist_ok=True)
os.makedirs(test_image_dir, exist_ok=True)
os.makedirs(test_label_dir, exist_ok=True)
# 将图像和标签文件按设定的ratio划分到训练集,验证集,测试集中
for i, filename in enumerate(origin_image_filenames):
if i < train_count:
output_image_dir = train_image_dir
output_label_dir = train_label_dir
elif i < train_count + val_count:
output_image_dir = val_image_dir
output_label_dir = val_label_dir
else:
output_image_dir = test_image_dir
output_label_dir = test_label_dir
src_img_name_no_ext = os.path.splitext(filename)[0]
src_image_path = os.path.join(origin_image_dir, filename)
src_label_path = os.path.join(origin_label_dir, src_img_name_no_ext + '.txt')
if os.path.exists(src_label_path):
# 复制图像文件
dst_image_path = os.path.join(output_image_dir, filename)
shutil.copy(src_image_path, dst_image_path)
# 复制标签文件
src_label_path = os.path.join(origin_label_dir, src_img_name_no_ext + '.txt')
dst_label_path = os.path.join(output_label_dir, src_img_name_no_ext + '.txt')
shutil.copy(src_label_path, dst_label_path)
else:
pass
train_dir = os.path.normpath(train_dir)
val_dir = os.path.normpath(val_dir)
test_dir = os.path.normpath(test_dir)
data_dict = {
'train': train_dir,
'val': val_dir,
'test': test_dir,
'nc': len(origin_classes),
'names': origin_classes
}
yaml_file_path = os.path.normpath(os.path.join(self.train_dataset_dir, 'data.yaml'))
with open(yaml_file_path, mode='w') as f:
yaml.safe_dump(data_dict, f, default_flow_style=False, allow_unicode=True)
if __name__ == '__main__':
g_origin_dataset_dir = '原始数据集'
g_train_dataset_dir = '生成的文件地址'
g_train_ratio = 0.7
g_val_ratio = 0.15
g_test_ratio = 0.15
yolo_generator = YOLOTrainDataSetGenerator(g_origin_dataset_dir, g_train_dataset_dir, g_train_ratio, g_val_ratio,
g_test_ratio, True)
yolo_generator.generate()
原始YOLO数据集:images包含被标注的图像,labels包含对应图像的标注文件,classes.txt包含标注的类别。
生成用于YOLOv8训练用的数据集,如下图所示:
三.模型训练
当以上步骤都完成后就可以开始漫长的模型训练过程了。
1.模型训练
#coding:utf-8
from ultralytics import YOLO
# 加载预训练模型
model = YOLO("./model/yolov8n.pt")
# Use the model
if __name__ == '__main__':
# Use the model
results = model.train(data='dataset.yaml', epochs=300, batch=4) # 训练模型
训练ing~~~
如果训练出现意外中断,例如说电脑爆炸,世界末日之类的,可以使用下面命令或python代码恢复训练
# Resume an interrupted training
yolo train resume model=path/to/last.pt
from ultralytics import YOLO
# Load a model
model = YOLO("path/to/last.pt") # load a partially trained model
# Resume training
results = model.train(resume=True)
2.参数解读
1.训练参数
- Epoch:训练过程中的迭代次数
- GPU_mem:GPU内存使用情况,通常是以MB或GB为单位的数字
- box_loss:模型预测的边界框与真实边界框之间的平均损失值
- cls_loss:模型预测的对象类别与真实类别之间的平均损失值
- dfl_loss:分布式焦距损失,用于提高边界框回归的精度
- Instances:检测任务中每个图像包含的目标实例
- Size:输入模型的图像的大小,通常是以像素为单位的宽度和高度。
- Class:检测的目标类别
- Images:测试集中包含该类别的图像数量
- Box(P:该类别的预测精确度(precision),即正确预测的物体数量占所有预测的物体数量的比例
- R:该类别的召回率(recall),即正确预测的物体数量占所有真实物体数量的比例
- mAP50:在 50% IoU(Intersection over Union)阈值下的平均精度(mean Average Precision)
- mAP50-95):在 IoU 阈值从 0.50 到 0.95(步长为 0.05)的范围内,逐步计算的平均精度的平均值
2.结果参数
Yolov8在训练完成之后,会在runs/detect/train目录下把训练的过程一些参数与结果示意图保存下来,这里面包含是目标检测性能指标。
- weights:包含best.pt以及last.pt,分布代表模型损失最低的结果以及最后一次训练完成后的结果
- args.yaml:训练时的保存的超参数
- 混淆矩阵(confusion_matrix):
其中:
True Positive (TP):预测为正类且实际为正类的样本数。
False Positive (FP):预测为正类但实际为负类的样本数(误报)。
False Negative (FN):预测为负类但实际为正类的样本数(漏报)。
True Negative (TN):预测为负类且实际为负类的样本数。
- 混淆矩阵归一化(confusion_matrix_normalized):将每个单元格的值除以该类别实际样本数,从而得到表示分类准确率的百分比
- F1曲线(F1_curve): 指在不同阈值下计算的 F1 分数与阈值之间的关系曲线。
- labels :代表每个检测到的目标的类别和边界框信息。
从左往右,从上到下按顺序排列:
1.训练集的数据量,显示每个类别包含的样本数量
2.框的尺寸和数量,展示了训练集中边界框的大小分布以及相应数量
3.中心点相对于整幅图的位置,描述了边界框中心点在图像中的位置分布情况
4.图中目标相对于整幅图的高宽比例,反映了训练集中目标高宽比例的分布状况
- labels_correlogram:展示了在训练过程中对标签之间相关性的建模情况。每个矩阵单元代表模型训练时使用的标签,而单元格的颜色深浅反映了对应标签之间的相关性。
- P_curve: 关于的是Precision和Confidence之间的关系。
- PR_curve :关于的是Precision和Recall之间的关系
- R_curve :召回率曲线。在目标检测中,召回率是衡量模型检测到所有正样本中有多少被正确检测出来的指标。
- results.csv :模型训练时每次迭代结果,记录了一些我们训练过程中的参数信息,包括损失和学习率等。
- results :
损失函数在目标检测任务中扮演关键角色,它用于衡量模型的预测值与真实值之间的差异,直接影响模型性能。以下是一些与目标检测相关的损失函数和性能评价指标的解释:
1. 定位损失(box_loss):
定义: 衡量预测框与标注框之间的误差,通常使用 GIoU(Generalized Intersection over Union)来度量,其值越小表示定位越准确。
目的: 通过最小化定位损失,使模型能够准确地定位目标。
2. 置信度损失(obj_loss):
定义: 计算网络对目标的置信度,通常使用二元交叉熵损失函数,其值越小表示模型判断目标的能力越准确。
目的: 通过最小化置信度损失,使模型能够准确判断目标是否存在。
3. 分类损失(cls_loss):
定义: 计算锚框对应的分类是否正确,通常使用交叉熵损失函数,其值越小表示分类越准确。
目的: 通过最小化分类损失,使模型能够准确分类目标。
4. Precision(精度):
定义: 正确预测为正类别的样本数量占所有预测为正类别的样本数量的比例。
目的: 衡量模型在所有预测为正例的样本中有多少是正确的。
5. Recall(召回率):
定义: 正确预测为正类别的样本数量占所有真实正类别的样本数量的比例。
目的: 衡量模型能够找出真实正例的能力。
6. mAP(平均精度):
定义: 使用 Precision-Recall 曲线计算的面积,mAP@[.5:.95] 表示在不同 IoU 阈值下的平均 mAP。
目的: 综合考虑了模型在不同精度和召回率条件下的性能,是目标检测任务中常用的评价指标。
在训练过程中,通常需要关注精度和召回率的波动情况,以及 mAP50 和 mAP50-95) 评估训练结果。这些指标可以提供关于模型性能和泛化能力的有用信息。
3.模型验证
当模型训练完成,我们使用以下命令行预测
yolo task=detect mode=predict model=best.pt source=YOLODataset/images/val
结果如下所示:
置信度都在90左右,效果还是蛮不错的!!!
四.总结
本文详细介绍了如何利用YOLOv8模型进行目标检测任务的训练过程,包括数据集获取、数据集标注、模型训练、以及最终的部署和应用。通过本文的指导,读者可以快速掌握训练YOLOv8模型的关键步骤,并在实际项目中应用和优化模型。
如果以上内容对您有帮助,可以三连打赏订阅本专栏哦, 谢谢~