YOLOv11模型训练
一、数据集
创建的个人数据集GL dataset
标注:用Labelme标注图片,得到图片标注json文件
数据格式:使用labelme2yolo.py将数据集转换为YOLO数据格式
#labelme2yolo.py
import os
import sys
import argparse
import shutil
import math
from collections import OrderedDictimport json
import cv2
import PIL.Imagefrom sklearn.model_selection import train_test_split
from labelme import utilsclass Labelme2YOLO(object):def __init__(self, json_dir, to_seg=False):self._json_dir = json_dirself._label_id_map = self._get_label_id_map(self._json_dir)self._to_seg = to_segi = 'YOLODataset'i += '_seg/' if to_seg else '/'self._save_path_pfx = os.path.join(self._json_dir, i)def _make_train_val_dir(self):self._label_dir_path = os.path.join(self._save_path_pfx, 'labels/')self._image_dir_path = os.path.join(self._save_path_pfx, 'images/')for yolo_path in (os.path.join(self._label_dir_path + 'train/'),os.path.join(self._label_dir_path + 'val/'),os.path.join(self._image_dir_path + 'train/'), os.path.join(self._image_dir_path + 'val/')):if os.path.exists(yolo_path):shutil.rmtree(yolo_path)os.makedirs(yolo_path) def _get_label_id_map(self, json_dir):label_set = set()for file_name in os.listdir(json_dir):if file_name.endswith('json'):json_path = os.path.join(json_dir, file_name)data = json.load(open(json_path, encoding='utf-8'))for shape in data['shapes']:label_set.add(shape['label'])return OrderedDict([(label, label_id) \for label_id, label in enumerate(label_set)])def _train_test_split(self, folders, json_names, val_size):if len(folders) > 0 and 'train' in folders and 'val' in folders:train_folder = os.path.join(self._json_dir, 'train/')train_json_names = [train_sample_name + '.json' \for train_sample_name in os.listdir(train_folder) \if os.path.isdir(os.path.join(train_folder, train_sample_name))]val_folder = os.path.join(self._json_dir, 'val/')val_json_names = [val_sample_name + '.json' \for val_sample_name in os.listdir(val_folder) \if os.path.isdir(os.path.join(val_folder, val_sample_name))]return train_json_names, val_json_namestrain_idxs, val_idxs = train_test_split(range(len(json_names)), test_size=val_size)train_json_names = [json_names[train_idx] for train_idx in train_idxs]val_json_names = [json_names[val_idx] for val_idx in val_idxs]return train_json_names, val_json_namesdef convert(self, val_size):json_names = [file_name for file_name in os.listdir(self._json_dir) \if os.path.isfile(os.path.join(self._json_dir, file_name)) and \file_name.endswith('.json')]folders = [file_name for file_name in os.listdir(self._json_dir) \if os.path.isdir(os.path.join(self._json_dir, file_name))]train_json_names, val_json_names = self._train_test_split(folders, json_names, val_size)self._make_train_val_dir()# convert labelme object to yolo format object, and save them to files# also get image from labelme json file and save them under images folderfor target_dir, json_names in zip(('train/', 'val/'), (train_json_names, val_json_names)):for json_name in json_names:json_path = os.path.join(self._json_dir, json_name)json_data = json.load(open(json_path, encoding='utf-8'))print('Converting %s for %s ...' % (json_name, target_dir.replace('/', '')))img_path = self._save_yolo_image(json_data, json_name, self._image_dir_path, target_dir)yolo_obj_list = self._get_yolo_object_list(json_data, img_path)self._save_yolo_label(json_name, self._label_dir_path, target_dir, yolo_obj_list)print('Generating dataset.yaml file ...')self._save_dataset_yaml()def convert_one(self, json_name):json_path = os.path.join(self._json_dir, json_name)json_data = json.load(open(json_path, encoding='utf-8'))print('Converting %s ...' % json_name)img_path = self._save_yolo_image(json_data, json_name, self._json_dir, '')yolo_obj_list = self._get_yolo_object_list(json_data, img_path)self._save_yolo_label(json_name, self._json_dir, '', yolo_obj_list)def _get_yolo_object_list(self, json_data, img_path):yolo_obj_list = []img_h, img_w, _ = cv2.imread(img_path).shapefor shape in json_data['shapes']:# labelme circle shape is different from others# it only has 2 points, 1st is circle center, 2nd is drag end pointif shape['shape_type'] == 'circle':yolo_obj = self._get_circle_shape_yolo_object(shape, img_h, img_w)else:yolo_obj = self._get_other_shape_yolo_object(shape, img_h, img_w)yolo_obj_list.append(yolo_obj)return yolo_obj_listdef _get_circle_shape_yolo_object(self, shape, img_h, img_w):label_id = self._label_id_map[shape['label']]obj_center_x, obj_center_y = shape['points'][0]radius = math.sqrt((obj_center_x - shape['points'][1][0]) ** 2 +(obj_center_y - shape['points'][1][1]) ** 2)if self._to_seg:retval = [label_id]n_part = radius / 10n_part = int(n_part) if n_part > 4 else 4n_part2 = n_part << 1pt_quad = [None for i in range(0, 4)]pt_quad[0] = [[obj_center_x + math.cos(i * math.pi / n_part2) * radius,obj_center_y - math.sin(i * math.pi / n_part2) * radius]for i in range(1, n_part)]pt_quad[1] = [[obj_center_x * 2 - x1, y1] for x1, y1 in pt_quad[0]]pt_quad[1].reverse()pt_quad[3] = [[x1, obj_center_y * 2 - y1] for x1, y1 in pt_quad[0]]pt_quad[3].reverse()pt_quad[2] = [[obj_center_x * 2 - x1, y1] for x1, y1 in pt_quad[3]]pt_quad[2].reverse()pt_quad[0].append([obj_center_x, obj_center_y - radius])pt_quad[1].append([obj_center_x - radius, obj_center_y])pt_quad[2].append([obj_center_x, obj_center_y + radius])pt_quad[3].append([obj_center_x + radius, obj_center_y])for i in pt_quad:for j in i:j[0] = round(float(j[0]) / img_w, 6)j[1] = round(float(j[1]) / img_h, 6)retval.extend(j)return retvalobj_w = 2 * radiusobj_h = 2 * radiusyolo_center_x= round(float(obj_center_x / img_w), 6)yolo_center_y = round(float(obj_center_y / img_h), 6)yolo_w = round(float(obj_w / img_w), 6)yolo_h = round(float(obj_h / img_h), 6)return label_id, yolo_center_x, yolo_center_y, yolo_w, yolo_hdef _get_other_shape_yolo_object(self, shape, img_h, img_w):label_id = self._label_id_map[shape['label']]if self._to_seg:retval = [label_id]for i in shape['points']:i[0] = round(float(i[0]) / img_w, 6)i[1] = round(float(i[1]) / img_h, 6)retval.extend(i)return retvaldef __get_object_desc(obj_port_list):__get_dist = lambda int_list: max(int_list) - min(int_list)x_lists = [port[0] for port in obj_port_list] y_lists = [port[1] for port in obj_port_list]return min(x_lists), __get_dist(x_lists), min(y_lists), __get_dist(y_lists)obj_x_min, obj_w, obj_y_min, obj_h = __get_object_desc(shape['points'])yolo_center_x= round(float((obj_x_min + obj_w / 2.0) / img_w), 6)yolo_center_y = round(float((obj_y_min + obj_h / 2.0) / img_h), 6)yolo_w = round(float(obj_w / img_w), 6)yolo_h = round(float(obj_h / img_h), 6)return label_id, yolo_center_x, yolo_center_y, yolo_w, yolo_hdef _save_yolo_label(self, json_name, label_dir_path, target_dir, yolo_obj_list):txt_path = os.path.join(label_dir_path, target_dir, json_name.replace('.json', '.txt'))with open(txt_path, 'w+') as f:for yolo_obj_idx, yolo_obj in enumerate(yolo_obj_list):yolo_obj_line = ""for i in yolo_obj:yolo_obj_line += f'{i} 'yolo_obj_line = yolo_obj_line[:-1]if yolo_obj_idx != len(yolo_obj_list) - 1:yolo_obj_line += '\n'f.write(yolo_obj_line)def _save_yolo_image(self, json_data, json_name, image_dir_path, target_dir):img_name = json_name.replace('.json', '.png')img_path = os.path.join(image_dir_path, target_dir,img_name)if not os.path.exists(img_path):img = utils.img_b64_to_arr(json_data['imageData'])PIL.Image.fromarray(img).save(img_path)return img_pathdef _save_dataset_yaml(self):yaml_path = os.path.join(self._save_path_pfx, 'dataset.yaml')with open(yaml_path, 'w+') as yaml_file:yaml_file.write('train: %s\n' % \os.path.join(self._image_dir_path, 'train/'))yaml_file.write('val: %s\n\n' % \os.path.join(self._image_dir_path, 'val/'))yaml_file.write('nc: %i\n\n' % len(self._label_id_map))names_str = ''for label, _ in self._label_id_map.items():names_str += "'%s', " % labelnames_str = names_str.rstrip(', ')yaml_file.write('names: [%s]' % names_str)if __name__ == '__main__':parser = argparse.ArgumentParser()parser.add_argument('--json_dir',type=str,help='Please input the path of the labelme json files.')parser.add_argument('--val_size',type=float, nargs='?', default=0.1,help='Please input the validation dataset size, for example 0.1 ')parser.add_argument('--json_name',type=str, nargs='?', default=None,help='If you put json name, it would convert only one json file to YOLO.')parser.add_argument('--seg', action='store_true',help='Convert to YOLOv5 v7.0 segmentation dataset')args = parser.parse_args(sys.argv[1:])convertor = Labelme2YOLO(args.json_dir, to_seg=args.seg)if args.json_name is None:convertor.convert(val_size=args.val_size)else:convertor.convert_one(args.json_name)
yolo数据集目录:
dataset/
├── images/
│ ├── train/
│ │ ├── xxx.jpg
│ │ └── ...
│ ├── val/
│ │ ├── yyy.jpg
│ │ └── ...
│ └── test/ # 可选(推理/测试用)
│ ├── zzz.jpg
│ └── ...
├── labels/
│ ├── train/
│ │ ├── xxx.txt
│ │ └── ...
│ ├── val/
│ │ ├── yyy.txt
│ │ └── ...
│ └── test/ # 可选
│ ├── zzz.txt
│ └── ...
└── data.yaml # 数据配置文件
数据集图片按照 训练集(train):验证集(val):测试集(test) = 8:1:1 的比例
二、模型配置
目标检测模型(传统深度学习模型): https://github.com/ultralytics/ultralytics
安装yolo模型:安装Ultralytics -Ultralytics YOLO 文档
选择yolo11m.pt
#我的代码和环境
Ultralytics 8.3.131
Python-3.10.16
torch-1.12.1+cu113 #CUDA:11.3
CUDA:2 (NVIDIA GeForce GTX TITAN X, 12213MiB)
三、执行训练
参数:配置 -Ultralytics YOLO 文档
#训练命令
yolo detect train data=/gl_yolo/dataset.yaml model=/ultralytics/gl_yolo/yolo11m.pt epochs=100 batch=16 imgsz=640 device=2 amp=True workers=8 cache=True
训练过程中遇到的问题
1、错误信息:
ImportError: Cannot load backend 'tkagg' which requires the 'tk' interactive framework, as 'headless' is currently running
解决方案:
MPLBACKEND=Agg
MPLBACKEND=Agg
用于防止 Tk backend 报错(无图形界面的服务器环境必须加)
2、错误信息:
ImportError: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.29' not found (required by /anaconda3/envs/yolo/lib/python3.10/site-packages/PIL/../../.././libLerc.so.4)
解决方案:
LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH
3、错误信息:
RuntimeError: Numpy is not availablenumpy 2.2.5 pypi_0 pypi
numpy-base 2.0.1 py310hb5e798b_1
解决方案:
pip uninstall numpy
conda uninstall numpy
conda install numpy==1.23.5
最终的训练命令:
LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH \
MPLBACKEND=Agg \
nohup yolo detect train \data=/ultralytics/gl_yolo/dataset.yaml \model=/ultralytics/gl_yolo/yolo11m.pt \epochs=100 \batch=16 \imgsz=640 \device=2 \amp=True \workers=8 \
cache=True > train.log 2>&1 &
nohup 命令允许将一个命令放到后台运行,并且即使关闭终端或注销,进程也不会停止
结果:
四、生成的训练结果
Ultralytics YOLO 会在训练结束(或中途):
每完成 一个 epoch,会记录一次训练结果到 runs/detect/train/results.csv
每次验证后,也会生成 多个曲线图:
F1 curve.png
PR curve.png
R curve.png
P curve.png
confusion_matrix.png 等
训练完成后,会生成综合图:results.png,其中包含:
box loss, cls loss, DFL loss(回归损失)
P / R / mAP@50 / mAP@50-95 等
重新生成某些图像(如 confusion_matrix.png)命令:
LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH \
MPLBACKEND=Agg
yolo detect val \model=runs/detect/train/weights/best.pt \data=/ultralytics/gl_yolo/dataset.yaml \save_json=True \save_txt=True
结果:生成val文件夹,其中含有下列内容
Labels 文件夹
confusion matrix.png
p confusion matrix normalized.png
F1 curve.png
P curve.png
PR curve.png
predictions.json
R curve.png
val batch0 labels.jpg
val batch0 pred.jpg
val batch1 labels.jpg
val batch1 pred.jpg
val batch2 labels.jpg
val batch2 pred.jpg
文件名 | 说明 |
confusion_matrix.png | 标准混淆矩阵,显示每个类别的真实值与预测值之间的对应情况(横轴预测,纵轴真实)。用于分析分类错误。 |
confusion_matrix_normalized.png | 归一化混淆矩阵(按真实标签行归一化),更清晰反映每类预测准确率。适合样本不均衡的情况。 |
F1_curve.png | 显示每个类别的 F1 分数(综合考虑精度和召回率)随阈值变化的曲线。用于挑选最佳置信度阈值。 |
P_curve.png | 精度(Precision)随置信度阈值的变化曲线。越平稳越好。 |
R_curve.png | 召回率(Recall)随置信度阈值变化的曲线。 |
PR_curve.png | 精度-召回曲线(Precision vs Recall Curve),越靠近右上角说明模型越好。 |
predictions.json | 保存了模型在验证集上的预测结果(包括类别、置信度、边界框等),供 COCO 等评估工具使用。 |
文件名 | 说明 |
val_batch0_labels.jpg | 可视化验证集中第一个 batch 的真实标签框(ground truth)。 |
val_batch0_pred.jpg | 可视化第一个 batch 的预测框(含置信度和类别)。 |
val_batch1_labels.jpg | 第二个 batch 的真实标签框。 |
val_batch1_pred.jpg | 第二个 batch 的预测框。 |
val_batch2_labels.jpg | 第三个 batch 的真实标签框。 |
val_batch2_pred.jpg | 第三个 batch 的预测框。 |
YOLOv8 中的图表生成方式
当你运行 YOLOv8 的训练命令:
yolo train model=yolov8n.pt data=your_data.yaml epochs=100 imgsz=640
YOLOv8 会在训练过程中自动保存以下图表(在 runs/train/exp*/
文件夹下):
📈 YOLOv8 自动生成的图表包括:
图表类型 | 说明 |
---|---|
results.png | 包含以下图表的合成图: |
- 训练损失(box loss, cls loss 等) | |
- 精度(Precision) | |
- 召回率(Recall) | |
- mAP@0.5 | |
confusion_matrix.png | 混淆矩阵图 |
F1_curve.png | F1 分数随阈值变化的曲线 |
PR_curve.png | Precision-Recall 曲线图 |
你可以使用以下命令单独执行验证,重新生成这些图表:
yolo val model=runs/train/exp/weights/best.pt data=your_data.yaml
🗂 文件结构示意:
runs/
└── train/└── exp/├── results.png ← 所有图表的汇总├── confusion_matrix.png ← 混淆矩阵├── PR_curve.png ← PR 曲线├── F1_curve.png ← F1 曲线└── weights/├── best.pt└── last.pt
你可以直接打开这些 .png
文件查看图表。