赞
踩
目录
随着YOLO(You Only Look Once)系列的不断进化,YOLOv8以其显著的精度提升和优化的架构设计,再次吸引了众多研究者和开发者的眼球。在本系列的第四篇文章中,我们将深入探讨如何在自定义数据集上训练YOLOv8模型,让你从零开始掌握目标检测的实战技能。
本系列其他文章
【YOLOv8系列】(一)YOLOv8介绍:实时目标检测的最新突破-CSDN博客
【YOLOv8系列】(二)YOLOv8环境配置,手把手嘴对嘴保姆教学-CSDN博客
【YOLOv8系列】(三)YOLOv8应用实践:从识别到分类再到分割的全方位视觉解决方案-CSDN博客
在机器学习和深度学习中,数据集是训练模型的基础。尤其在计算机视觉领域,数据集的重要性更为突出。以下是获取数据集的几个途径:
有许多公开的目标检测数据集可供使用。以下是一些常见的数据集:
通过爬虫程序批量下载网络上的图片数据集,或者使用相机或其他来源收集您需要的图片
数据标注是通过人工把需要识别和分辨的数据贴上标签。深度神经网络学习这些标注数据的特征,最终实现自主识别的功能。
使用标注工具对图片中的目标进行标注。常用的标注工具包括:
我个人比较推荐使用labelme,因为可以进行多边形标注。
pip安装
pip install labelme
终端输入labelme打开软件
labelme
界面如下所示
文件—>自动保存
标注的数据会自动保存
将标注文件转换为YOLO格式。YOLO格式的标注文件内容如下:
<class_id> <x_center> <y_center> <width> <height>
但我们标注好的json数据格式是这样的,需要将json格式转换成需要的txt
pip install labelme2yolo
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(可选)预先分配的类别标签。
运行后会出现一个名为YOLODataset的文件夹
项目结构如下所示:
- YOLODataset/
- ├── images/
- │ ├── train/
- │ ├── val/
- ├── labels/
- │ ├── train/
- │ ├── val/
- ├── dataset.yaml
yaml文件如下
转换后的txt文件如下
可以看到,除了格式转换,labelme2yolo模块已经帮我们划分好了数据集以及验证集并生成好了yaml文件。一劳永逸,灰常好用!!!!!
除了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')
如果你是通过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训练用的数据集,如下图所示:
当以上步骤都完成后就可以开始漫长的模型训练过程了。
- #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)
Yolov8在训练完成之后,会在runs/detect/train目录下把训练的过程一些参数与结果示意图保存下来,这里面包含是目标检测性能指标。
其中:
True Positive (TP):预测为正类且实际为正类的样本数。
False Positive (FP):预测为正类但实际为负类的样本数(误报)。
False Negative (FN):预测为负类但实际为正类的样本数(漏报)。
True Negative (TN):预测为负类且实际为负类的样本数。
从左往右,从上到下按顺序排列:
1.训练集的数据量,显示每个类别包含的样本数量
2.框的尺寸和数量,展示了训练集中边界框的大小分布以及相应数量
3.中心点相对于整幅图的位置,描述了边界框中心点在图像中的位置分布情况
4.图中目标相对于整幅图的高宽比例,反映了训练集中目标高宽比例的分布状况
损失函数在目标检测任务中扮演关键角色,它用于衡量模型的预测值与真实值之间的差异,直接影响模型性能。以下是一些与目标检测相关的损失函数和性能评价指标的解释:
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) 评估训练结果。这些指标可以提供关于模型性能和泛化能力的有用信息。
当模型训练完成,我们使用以下命令行预测
yolo task=detect mode=predict model=best.pt source=YOLODataset/images/val
结果如下所示:
置信度都在90%左右,效果还是蛮不错的!!!
本文详细介绍了如何利用YOLOv8模型进行目标检测任务的训练过程,包括数据集获取、数据集标注、模型训练、以及最终的部署和应用。通过本文的指导,读者可以快速掌握训练YOLOv8模型的关键步骤,并在实际项目中应用和优化模型。
如果以上内容对您有帮助,可以三连打赏订阅本专栏哦, 谢谢~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。