☰
YOLOv8训练自定义数据集全流程:从标注到调参避坑指南
2026/10/11 1:03:07 网站建设 项目流程

简介:一份围绕YOLOv8自定义数据集训练全流程的实战型知识文档,面向目标检测初学者和需要快速上手YOLOv8的算法工程师。资源包内含1个docx文档,压缩包大小为2.18MB,以分步骤笔记形式呈现,便于边读边操作。文档系统梳理了训练环境配置、数据集图片与标注文件准备、训练集/验证集切分、XML转YOLO_txt格式,以及模型训练命令和关键参数(task、mode、model、data、epochs、batch等)的含义;同时给出常见报错如Arial.ttf字体下载失败的解决办法,并单独讲解断点续训的resume参数用法,补充了多数教程未覆盖的细节。目前已有448人浏览学习,对希望快速用YOLOv8落地自定义目标检测任务的读者非常实用。

1. YOLOv8 训练自定义数据集:先理清链路再动手,避开 90% 的无效训练

我见过不少项目卡在同一个地方:图片标完了,目录也建了,执行 YOLOv8 训练自定义数据集时,要么 loss 直接起飞,要么训练过程一路绿灯、最后 mAP 却是 0。表面上看,把 data.yaml 指给 ultralytics 就能开跑,但一条完整链路里藏着标注格式、txt 归一化坐标、训练集验证集划分、显存与 batch 匹配、超参和断点续训这些环节。这篇稿子写给两类人:刚把标注做完、正打算把 YOLOv8 训练自定义数据集提上日程的初学者,以及正用 GTX 1660Ti 这类老卡摸爬滚打的落地工程师。目标是让你照着做一遍,能拿到一个可验证、可继续迭代的 best.pt,而不是只看一堆漂亮的曲线。

2. 准备自定义数据集:标注格式、转换脚本与目录结构

标注是 YOLOv8 训练自定义数据集的第一道门槛。不少人用任意标注工具画完框,导出的格式千奇百怪,有的带类别名字符串,有的带绝对像素坐标,直接塞给训练脚本必翻车。Ultralytics YOLOv8 默认读 YOLO txt 格式:每一行表示一个目标,第一列是类别 id,后面四列是归一化后的中心点 x、中心点 y、宽和高。这个格式看起来简单,但顺序、归一化、类别 id 从 0 还是从 1 开始,全是容易踩的坑。理解它,比多标两百张图更能帮你避开后续的诡异表现。

2.1 标注工具选型与三种常见格式的差别

标注工具我一般用 LabelImg 或 X-AnyLabeling 这类开源工具,选它们的理由不是功能多,而是导出格式可控、社区案例多。画检测框只需要矩形框,关键点和旋转框在自定义检测数据集里暂时用不上,反而会增加导出字段的干扰。

拿到标注结果后,你手上大概率是三种格式之一:VOC 的 XML、COCO 的 JSON、或者正好是 YOLO 的 txt。三者的差别用一张表能看得很清楚:

格式内容特征转换难度常见翻车点
VOC XML每个图一个 XML,框坐标是绝对像素值中类别名是中文、目录层级多
COCO JSON所有标注集中在 JSON,bbox 为 [x, y, w, h]中category_id 不连续或从 1 开始
YOLO txt每行 cls cx cy w h,全部归一化到 0~1低忘记归一化、类别 id 从 1 开始

VOC 和 COCO 都是给人看或给通用工具用的格式,YOLO 的 txt 则是给训练循环直接读的。转换的核心就两件事:把绝对坐标换成相对坐标,把类别字符串换成连续整数 id。只要这两点不出错,格式转换基本就稳了。

2.2 把 COCO 标注转成 YOLO txt:转换脚本与参数说明

假设你手上有一份 COCO 格式的标注文件 annotations.json,图片放在 images 目录,目标是生成与图片同名的 txt 文件。我一般会写这样一个脚本,放在项目根目录下执行:

import json from pathlib import Path from PIL import Image # 把 COCO 格式的 annotations 转换成 YOLO txt def coco_to_yolo(coco_json, img_dir, out_dir, class_names): out_dir = Path(out_dir) out_dir.mkdir(parents=True, exist_ok=True) with open(coco_json, "r", encoding="utf-8") as f: coco = json.load(f) # 建立 image_id 到文件名的映射,方便按图取标注 img_map = {im["id"]: im["file_name"] for im in coco["images"]} # 按 image_id 把标注归组 anns_by_img = {} for ann in coco["annotations"]: anns_by_img.setdefault(ann["image_id"], []).append(ann) for img_id, anns in anns_by_img.items(): img_path = Path(img_dir) / img_map[img_id] if not img_path.exists(): continue # 用 PIL 读图拿到真实宽高,归一化必须基于实际尺寸 with Image.open(img_path) as im: W, H = im.size txt_name = Path(img_map[img_id]).stem + ".txt" with open(out_dir / txt_name, "w", encoding="utf-8") as f: for ann in anns: # 跳过 crowd 这类无效标注 if ann.get("iscrowd", 0): continue cat_id = ann["category_id"] # 关键一步:class_names 的顺序必须与后续训练配置完全一致 cls = class_names.index(coco["categories"][cat_id - 1]["name"]) x, y, w, h = ann["bbox"] cx = (x + w / 2) / W cy = (y + h / 2) / H nw = w / W nh = h / H # 过滤面积为 0 或明显越界的框 if nw <= 0 or nh <= 0 or cx < 0 or cy < 0: continue f.write(f"{cls} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n")

这个脚本的逻辑不复杂:先建立 image_id 到文件名的映射,再把所有标注按图片归组。循环里用 PIL 读真实宽高,是为了归一化时不被图片缩放误导。参数说明里最需要注意的是class_names的顺序,它必须和训练时 data.yaml 里的 names 列表完全一致。COCO 的 category_id 和 class_names 的下标不一定对应,所以代码里通过名称去查下标,而不是直接用 cat_id。另一个容易忽略的是 bbox 的坐标原点,COCO 的 x、y 是左上角,中心点要自行加上 w/2 和 h/2。

如果你手上只有 VOC XML,思路完全一样:解析<bndbox>里的 xmin、ymin、xmax、ymax,计算中心点和宽高,再用 XML 里的<name>去查类别 id。转换完随机抽几个 txt 打开看一眼,确认坐标值都在 0 到 1 之间、类别 id 都在 0 到 nc-1 之间,再进入下一步。

2.3 目录结构、训练集验证集划分与 data.yaml 的写法

目录结构是 YOLOv8 训练自定义数据集最容易省掉、但最不该省的一步。官方训练默认按 images 和 labels 两个大目录组织,图片和标签靠同名关联:

dataset/ ├── images/ │ ├── train/ │ │ ├── 000001.jpg │ │ └── 000002.jpg │ └── val/ │ ├── 000101.jpg │ └── 000102.jpg └── labels/ ├── train/ │ ├── 000001.txt │ └── 000002.txt └── val/ ├── 000101.txt └── 000102.txt

目录结构定了,data.yaml 就有明确的指向。我习惯把 path 写成绝对路径,避免在不同终端里因为当前目录不同把路径解析带偏:

# data.yaml 示例,对应上面的 dataset 目录 path: /home/user/dataset # 数据集根目录,建议写绝对路径 train: images/train # 训练图片相对于根目录的路径 val: images/val # 验证图片相对于根目录的路径 nc: 2 # 类别总数 names: ["person", "helmet"] # 类别名列表,顺序必须与 txt 里的 id 一致

YOLOv8 会自动根据图片路径找到同级的 labels 目录,所以 train 和 val 里只需要写 images 子目录。训练集和验证集的划分,常见做法是 8:2 或 9:1,划分时尽量按场景而不是按文件名随机抽。比如安全帽检测数据里,白天和夜间的图要同时进训练集和验证集,否则验证集会因为场景分布单一给出一份虚高的 mAP。测试集只做最终评估,不需要写进 data.yaml,训练过程也不会读它。

注意:YOLO 的类别 id 必须从 0 开始连续编号,txt 里不能出现 -1 或 0.5 这类值。这个错误在训练时不会报错,只会让 mAP 永远停在 0。

3. 训练前调好这些参数:选模型、配环境、写一条能跑通的最小命令

训练参数和训练环境决定了你是能顺利跑完一轮实验,还是反复在“启动即报错”里打转。YOLOv8 训练自定义数据集之前,大多数人纠结的不是算法,而是三个问题:模型选哪个、batch 设多大、老显卡能不能扛住。这一章直接回答这三个问题,再给一条可以直接抄的训练命令。

3.1 YOLOv8n/s/m/l/x 怎么选:数据量和显卡决定下界

模型不是越大越好,YOLOv8 从 n 到 x 的参数量大致是递增的,占用显存、推理延迟和精度也随之上升。选型判断我只看两个硬指标:标注量级和显卡显存。

模型数据量参考显存参考我的典型用途
yolov8n几百到几千张6GB 可跑 batch 16首次跑通、小数据集、边缘设备
yolov8s数千张以上6GB 可跑 batch 8-16目标尺寸中等、有一定数据量
yolov8m万张以上建议 12GB 以上小目标密集、精度要求高
yolov8l/x数万张建议 24GB 以上追求极致精度,显存不敏感

如果你是在 GTX 1660Ti 上跑 YOLOv8 训练自定义数据集,我的建议很直接:先从 n 开始,数据链路调通、确认 mAP 曲线合理后,再尝试 s。m 在这类 6GB 卡上不是不能跑,但 batch 会被压到个位数,训练稳定性下降,收益远小于换一张云 GPU。别让模型容量成了第一个变量,先用小模型把数据和参数校正好,这是性价比最高的路线。

3.2 必调参数:epochs、batch、imgsz、workers 与 patience

YOLOv8 训练参数很多,但真正影响结果的其实就那么几个。我列了一张表,列的是我每次开始新数据集之前都会核对一遍的参数:

参数作用常见取值改大改小的影响
epochs总训练轮数100~300太小欠拟合,太大过拟合且浪费时间
batch每轮迭代的样本数8~32显存敏感,太小收敛慢且震荡
imgsz输入图片尺寸416~640越小越省显存,但小目标召回率下降
workers数据加载线程数4~8过大会造成 CPU 瓶颈或内存压力
patience早停耐心轮数20~50验证集不再提升时提前停,省时间
amp混合精度训练True/False省显存,但某些环境下数值不稳
cache是否缓存图片True/False提速明显,首次加载慢、吃内存

参数之间的联动关系比单个值更重要。imgsz 从 640 降到 416,显存占用可以少 30% 以上,但原本就小的目标在降采样后几乎消失,所以小目标数据集不要轻易降 imgsz。batch 减半意味着每个 epoch 的迭代次数翻倍,收敛速度下降但稳定性上升。workers 在 Windows 上不要太高,否则数据加载比训练还慢。

学习率参数 lr0 和 lrf,我默认不动,先用默认值 0.01 跑一轮。如果出现 loss 震荡或 nan,再单独调。过度改学习率会让老手也翻车,因为它和 batch、模型大小是耦合的。

3.3 最小训练命令与断点续训

训练环境的准备就一句话:创建虚拟环境,安装 ultralytics 包,确认 torch 能调用 GPU。这一步不需要额外配置。之后跑训练,官方把入口收敛成了 yolo 命令,最小可用命令是这样的:

yolo detect train \ model=yolov8n.pt \ data=dataset/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ project=runs/train \ name=helmet_v1

这条命令里涉及几个需要说明的参数。model 指定预训练权重,首次运行会从官方地址自动下载,如果你的训练环境访问不了外网,需要提前把权重文件放到本地指定路径。data 指向 data.yaml,里面已经写好了类别和目录结构,所以命令里不再重复。batch 这里设为 16,在 6GB 显存上跑 n 模型通常能吃得下,OOM 就降到 8。project 和 name 用来区分实验,同一个 name 重复运行会自动加后缀,避免覆盖上一次的结果。

训练中断是最容易慌的场景。官方每次 epoch 结束都会保存 last.pt 和 best.pt,断点续训的命令也很短:

yolo detect train resume model=runs/train/helmet_v1/weights/last.pt

resume 会从 last.pt 的状态继续,包括优化器、学习率调度器和当前 epoch。需要注意,续训前不要修改 data.yaml 的类别数量或数据集路径,否则优化器状态和数据流不匹配,训练曲线会出现奇怪的跳变。如果只是想拿一个中间结果,不需要接着训,直接用 best.pt 跑验证就行。

注意:续训优先用 last.pt,不要用 best.pt。best.pt 是历史最高验证指标,而 last.pt 才是最近的权重状态,从 best 接着训等于丢弃掉 last 之后的优化轨迹。

4. 训练过程常见问题排查:5 个高频坑,按现象找原因

YOLOv8 训练自定义数据集时,报错不可怕,可怕的是训练全程没有任何报错,但结果不能用。我把实际项目里反复出现的问题整理成 5 类,每类按现象、原因、解决三步拆开。这些坑在 GTX 1660Ti 上跑 YOLOv8 训练时特别容易触发,也和换机器之后的“野路子”经验直接相关。

4.1 模型训练报 nan:先关 AMP,再查学习率

现象:训练日志里 loss 突然显示 nan,进程没有退出,曲线从某个 epoch 开始断崖。不少第一次遇到的同事会反复重启实验,但重启多少次都复现。

原因归类到三种:混合精度训练下数值溢出、学习率过高、数据里有异常值。AMP 在某些驱动的显卡上对特定算子不稳定,这是训练环境层面的问题;学习率默认 0.01 在 batch 调大后可能过高,导致梯度爆炸;数据里的空 txt 或损坏图片也会让损失计算出现无效值。

解决顺序也按上面来:先把 amp=False 加进训练命令,关掉混合精度。如果 loss 恢复正常,那就是环境兼容问题,保持关闭即可,代价只是显存占用略升。如果仍然 nan,再检查 lr0,我习惯降到 0.001 试跑 10 个 epoch。最后检查数据,写一个命令把所有 labels 里的 txt 文件里行数为 0 的图片找出来,单独清理或重标。

4.2 显存 OOM:不要硬扛,先减 batch 再降 imgsz

现象:训练启动时报 CUDA out of memory,进程被杀,之前加载的数据全部白费。这在 GTX 1660Ti 这类 6GB 显卡上几乎是必经之路。

原因很直白:batch、imgsz 和模型大小的乘积超过了显存容量。很多人第一反应是换更大的显卡,但实际改两个参数就能解决。我先用 nvidia-smi 看显存占用,确认没有别的进程占着显存;然后把 batch 从 16 降到 8,这是成本最低的改动;如果还 OOM,再把 imgsz 从 640 降到 512 或 416。注意 imgsz 对显存的影响是平方级的,降 20% 尺寸能省接近 40% 的显存。

如果降尺寸影响小目标精度,可以开启 cache=False,避免图片缓存额外占用显存;另外把 workers 调到 2,减少数据加载时的内存峰值。换云 GPU 是最后的选项,不是第一选项。

4.3 训练没报错但 mAP 为 0:类别 id 和 names 对不上

现象:训练损失正常下降,验证曲线也走,但 val 指标里的 mAP50 一直是 0。这是最迷惑人的一种状态,因为没有任何报错提示。

原因九成是类别 id 对不上:txt 里的类别号越界,或者 data.yaml 的 names 顺序和标注时用的顺序不一致。比如标注工具导出时类别从 1 开始,而 YOLO 要求从 0 开始,于是所有目标被当成背景,模型学不到任何正样本。

解决方法是抽查标签。随便打开一个 txt,确认第一列的值都在 0 到 nc-1 之间。再跑一次验证命令,单独打印混淆矩阵,看预测结果里是否有类别被识别出来。如果混淆矩阵全空,基本可以断定标签没被正确读入,优先回头检查转换脚本里的类别映射。

4.4 训练到一半中断:last.pt 就是后悔药

现象:训练跑到第 80 个 epoch,终端被关、机器断电、或者你手动 Ctrl+C 停掉,以为前功尽弃。

事实上官方每个 epoch 都会保存权重,中断点前后有两个文件:last.pt 和 best.pt。续训时直接从上文的 resume 命令接上,会用 last.pt 恢复优化器状态,不需要从头再跑。这里要强调一个容易被忽略的细节:如果中断后你改了 data.yaml 或换了模型文件,resume 大概率失败,因为优化器里的参数形状和新模型对不上。正确做法是保持配置不变,先续训完成,再做任何实验调整。

另外,定期把 last.pt 和 best.pt 复制到项目目录之外,是个值得养成的好习惯。训练机磁盘满了导致写盘失败,权重文件会留在缓存里,血泪经验,别问我是怎么知道的。

4.5 训练正常但验证集不涨:过拟合的三个信号与对策

现象:train loss 一直降,val loss 在某个 epoch 后开始拐头向上,mAP 曲线停滞甚至回落。这是过拟合的典型轨迹,也是小数据集上最常出现的状态。

原因集中在三个地方:epochs 开太多、模型太大、数据增强强度不合适。最后一个经常被误解,很多人以为增强越强越不容易过拟合,但 mosaic 概率开到 1.0 时,小目标会被反复裁剪掉,模型反而学不到稳定特征。

解决先看 patience,把它从默认值调低到 20,让早停更敏感;再把模型从 s 换回 n;如果还不理想,检查一下 mosaic 和 hsv 增强参数,不需要全部关掉,降到 0.5 左右通常能改善。验证集不涨不完全是坏事,它说明数据量已经不够支撑更大的模型容量,这时候补数据比调参更值得投入。

5. 训练结果验证与迭代:把 best.pt 用起来,再把坑留给下次

训练结束后,先别急着拿跑出来的权重做推理。YOLOv8 会在 runs/train/helmet_v1 目录下生成 results.png、混淆矩阵、F1 曲线和 results.csv。其中 results.png 就是训练过程里 yOLOv8 自动画好的损失函数曲线图,包含 train/val 的 box_loss、cls_loss 和 dfl_loss,六个子图足够判断模型是否收敛。如果你想在自己的报告里重新画一份,不需要另找工具,直接读 results.csv 里的列,用 matplotlib 按列名绘制即可。

best.pt 和 last.pt 的选择是个容易被忽略的细节。评价指标以验证集为准,best.pt 保存的是验证集 mAP 最高的那个 epoch,但它的 epoch 数不一定比 last.pt 大。实际部署和后续推理用 best.pt,last.pt 只留作继续训练或回退分析的中间产物,这个习惯能避免不少“换了权重效果变差”的困惑。

验证完精度,下一步看速度。导出 ONNX 是常见的落地路径,一条命令就能完成:

yolo export model=runs/train/helmet_v1/weights/best.pt format=onnx

导出后可以再做端侧优化,比如部署到 RK3588 这类边缘设备时,还需要把 ONNX 转成 RKNN,转换工具对算子的支持比推理框架更敏感,所以导出时保持默认算子,不要加过多的自定义 op,能让后续转换少踩几个坑。

我现在的习惯是每次训练结束,先看 results.png 的 val loss 曲线有没有拐头,再确认混淆矩阵里每个类别的对角线明显高于其他格,最后才用 best.pt 做一次真实场景的样例推理。这三步走完才敢说这轮训练是合格的。希望这套流程能帮你少走弯路,把训练自定数据集的每一步都变成可预期的事。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询