YOLOv8 自定义目标检测完整训练流程:数据准备、训练、验证、测试与导出
本文介绍如何使用 Ultralytics YOLOv8 完成一个自定义目标检测项目,内容包括环境配置、训练集/验证集/测试集划分、YOLO 标注格式、数据描述文件、模型训练、指标评估、推理以及模型导出。
本文以单类别目标检测为例。多类别数据集只需要增加类别名称,并保证标签中的类别编号对应正确。
1. 训练、验证和测试集分别有什么作用
一个完整的数据集通常分为三部分:
| 数据集 | 主要用途 | 是否参与权重更新 |
|---|---|---|
| 训练集(train) | 模型学习目标特征 | 是 |
| 验证集(val) | 每轮训练后评估效果、选择最佳权重、触发提前停止 | 否 |
| 测试集(test) | 模型和参数全部确定后,进行最终泛化能力评估 | 否 |
常用划分比例:
-
数据量充足:训练集 80%,验证集 10%,测试集 10%。
-
数据量一般:训练集 70%,验证集 20%,测试集 10%。
-
数据量很少时,测试结果波动会很大,应优先增加数据,也可以使用交叉验证获得更可靠的结论。
划分数据时需要注意:
-
先划分原始数据,再对训练集做数据增强,不能把同一张图片的增强版本分到不同集合。
-
各集合应尽量保持类别、场景、光照和目标尺寸分布一致。
-
测试集不能用于挑选模型、调整阈值或修改训练参数。
-
如果图片来自视频抽帧,应按照视频或连续时间片段分组划分。不能随机打散所有相邻帧,否则训练集和测试集画面高度相似,评估指标会虚高。
2. 配置 YOLOv8 环境
建议使用 Conda 创建独立环境:
conda create -n yolov8 python=3.10 -y
conda activate yolov8
根据显卡、驱动和 CUDA 情况,从 PyTorch 官方安装页面选择对应的 PyTorch 安装命令,然后安装 Ultralytics:
pip install ultralytics
检查环境:
yolo checks
也可以使用 Python 检查 GPU:
import torch
print("PyTorch:", torch.__version__)
print("CUDA 可用:", torch.cuda.is_available())
if torch.cuda.is_available():
print("GPU:", torch.cuda.get_device_name(0))
如果 torch.cuda.is_available() 返回 False,模型仍可使用 CPU 训练,但速度通常会明显降低。
3. 准备数据集目录
推荐采用以下目录结构:
project/
├── datasets/
│ └── my_dataset/
│ ├── images/
│ │ ├── train/
│ │ ├── val/
│ │ └── test/
│ └── labels/
│ ├── train/
│ ├── val/
│ └── test/
├── dataset.yaml
└── train.py
图片与标签必须同名。例如:
images/train/example_001.jpg
labels/train/example_001.txt
常见图片格式包括 .jpg、.jpeg、.png 和 .bmp。
3.1 自动划分静态图片数据集
如果原始图片相互独立,可以使用下面的脚本按照 8:1:1 划分数据。原始目录约定为:
raw_data/ ├── images/ └── labels/
创建 split_dataset.py:
import random
import shutil
from pathlib import Path
SOURCE_IMAGES = Path("raw_data/images")
SOURCE_LABELS = Path("raw_data/labels")
OUTPUT_ROOT = Path("datasets/my_dataset")
IMAGE_SUFFIXES = {".jpg", ".jpeg", ".png", ".bmp"}
RANDOM_SEED = 42
def copy_sample(image_path: Path, split: str) -> None:
image_output = OUTPUT_ROOT / "images" / split
label_output = OUTPUT_ROOT / "labels" / split
image_output.mkdir(parents=True, exist_ok=True)
label_output.mkdir(parents=True, exist_ok=True)
shutil.copy2(image_path, image_output / image_path.name)
# 负样本允许不存在标签文件;正样本应有同名标签。
label_path = SOURCE_LABELS / f"{image_path.stem}.txt"
if label_path.exists():
shutil.copy2(label_path, label_output / label_path.name)
def main() -> None:
images = sorted(
path
for path in SOURCE_IMAGES.iterdir()
if path.is_file() and path.suffix.lower() in IMAGE_SUFFIXES
)
if not images:
raise RuntimeError("没有找到可划分的图片")
random.Random(RANDOM_SEED).shuffle(images)
train_end = int(len(images) * 0.8)
val_end = train_end + int(len(images) * 0.1)
split_images = {
"train": images[:train_end],
"val": images[train_end:val_end],
"test": images[val_end:],
}
for split, paths in split_images.items():
for image_path in paths:
copy_sample(image_path, split)
print(f"{split}: {len(paths)} 张图片")
if __name__ == "__main__":
main()
运行:
python split_dataset.py
脚本固定使用随机种子 42,因此相同输入可以得到相同划分。正式运行前应确认输出目录中没有旧数据,避免旧文件混入新划分。
这段随机划分脚本只适用于相互独立的静态图片。视频连续帧、同一目标的连拍图片或同一场景的高度相似图片,应先按视频、拍摄批次或场景分组,再把整个组分配到某个集合。
3.2 自己批注数据集
这里我们可以打开终端,在(yolo8)环境下,安装labelimg
pip install labelimg
不过我一般用在线批注的make Sense:Make Sense

3.3 YOLO 检测标签格式
每个目标占标签文件中的一行:
class_id x_center y_center width height
例如:
0 0.512500 0.483333 0.325000 0.550000
字段含义:
-
class_id:类别编号,从0开始。 -
x_center、y_center:边界框中心点坐标。 -
width、height:边界框宽度和高度。 -
后四个数都必须除以图片宽高,归一化到
[0, 1]。
归一化公式:
x_center = (x_min + x_max) / 2 / image_width
y_center = (y_min + y_max) / 2 / image_height
width = (x_max - x_min) / image_width
height = (y_max - y_min) / image_height
一张图片包含多个目标时,标签文件中写多行。没有目标的负样本可以没有标签文件,也可以创建空的 .txt 文件。
以下标注是无效的:
0 0 0 0 0
它的宽和高都是零,不是合法的负样本,会被判定为无效框。图片中有目标时应重新标注;图片中确实没有目标时,应改为空标签文件。
4. 编写数据集描述文件
创建 dataset.yaml:
path: datasets/my_dataset
train: images/train
val: images/val
test: images/test
names:
0: target
说明:
-
path:数据集根目录。 -
train、val、test:相对于path的图片目录。 -
names:类别编号和类别名称的对应关系。 -
只有一个类别时,所有标签的
class_id都应为0。
多类别示例:
names: 0: person 1: helmet 2: vehicle
建议在项目根目录执行训练命令,使相对路径的解析保持一致。数据也可以存放在其他位置,只要 YAML 中配置的路径能够正确访问即可,并不强制放在 datasets 目录。
5. 训练前检查数据
正式训练前至少检查以下内容:
-
图片能正常打开,没有损坏文件。
-
每张正样本图片都有同名标签。
-
类别编号从
0开始,并且没有超过names的范围。 -
坐标已经归一化到
[0, 1]。 -
边界框的
width和height大于0。 -
训练集、验证集和测试集之间没有重复图片或相邻视频帧泄漏。
-
边界框尽量贴合目标,不包含大面积无关背景。
第一次训练后,还应查看输出目录中的 labels.jpg 和 train_batch*.jpg。它们可以直观暴露类别错误、边界框偏移和图片旋转异常等问题。
6. 选择 YOLOv8 模型
YOLOv8 检测模型常用规格如下:
| 模型 | 特点 | 适用场景 |
|---|---|---|
yolov8n.pt |
体积最小、速度最快 | 流程验证、边缘设备、小型数据集 |
yolov8s.pt |
速度与精度较均衡 | 常规项目 |
yolov8m.pt |
精度潜力更高、显存占用更大 | 数据较多且硬件充足 |
yolov8l.pt |
大模型 | 高精度需求 |
yolov8x.pt |
最大模型 | 充足数据和高性能硬件 |
模型越大不代表实际效果一定越好。数据量较少时,大模型更容易过拟合。建议先使用 yolov8n.pt 验证数据和代码,再根据验证集表现决定是否换成更大的模型。
7. 使用命令行训练
基础训练命令:
yolo detect train data=dataset.yaml model=yolov8n.pt epochs=100 imgsz=640
加入常用参数:
yolo detect train data=dataset.yaml model=yolov8n.pt epochs=150 imgsz=640 batch=8 device=0 workers=0 cache=ram patience=30 amp=True
主要参数说明:
| 参数 | 作用 |
|---|---|
data |
数据集 YAML 描述文件 |
model |
模型结构或预训练权重 |
epochs |
最大训练轮数 |
imgsz |
输入图片尺寸 |
batch |
每批图片数量,过大会导致显存不足 |
device=0 |
使用第 0 块 NVIDIA GPU |
device=cpu |
强制使用 CPU |
workers |
数据加载子进程数量 |
cache=ram |
将图片缓存到内存,适合可放入内存的数据集 |
patience |
验证指标连续指定轮数未提升时提前停止 |
amp=True |
使用自动混合精度训练 |
workers=0 使用主进程加载数据,适合 Windows、Jupyter 和小型数据集。大型数据集可以在标准 Python 脚本中尝试 workers=4 或 workers=8,但 Windows 必须保留主入口保护。
8. 使用 Python 代码训练
创建 train.py:
from pathlib import Path
from ultralytics import YOLO
ROOT = Path(__file__).resolve().parent
DATA_CONFIG = ROOT / "dataset.yaml"
def main() -> None:
if not DATA_CONFIG.is_file():
raise FileNotFoundError(f"找不到数据集描述文件: {DATA_CONFIG}")
# 加载 YOLOv8 Nano 预训练模型。
model = YOLO("yolov8n.pt")
model.train(
data=str(DATA_CONFIG), # 数据集描述文件。
epochs=150, # 最大训练轮数。
imgsz=640, # 输入图片尺寸。
batch=8, # 每批图片数量。
device=0, # 使用第 0 块 GPU。
workers=0, # 小数据集和 Windows 环境优先使用 0。
cache="ram", # 将数据缓存到内存。
patience=30, # 提前停止等待轮数。
amp=True, # 开启自动混合精度。
pretrained=True, # 使用预训练权重。
project="runs/detect", # 结果根目录。
name="my_yolov8_exp", # 实验名称。
plots=True, # 保存训练和评估图表。
)
if __name__ == "__main__":
main()
运行:
python train.py
Windows 使用多进程加载数据时,必须保留:
if __name__ == "__main__":
main()
如果在 Jupyter Notebook 中训练,建议将 workers 设置为 0。
9. 查看训练结果
训练结果通常保存在:
runs/detect/my_yolov8_exp/
重点文件:
weights/best.pt 验证集指标最好的权重
weights/last.pt 最后一轮权重
results.csv 每轮训练指标
results.png 损失和指标曲线
confusion_matrix.png 混淆矩阵
PR_curve.png Precision-Recall 曲线
F1_curve.png F1 曲线
labels.jpg 标签分布可视化
train_batch*.jpg 训练批次与增强效果
val_batch*_pred.jpg 验证集预测结果
实际部署或最终测试通常优先使用 best.pt,不是 last.pt。
10. 如何理解评估指标
目标检测常见指标:
-
Precision:模型预测为目标的结果中,有多少是真的。误检越少,Precision 越高。 -
Recall:真实目标中,有多少被模型检出。漏检越少,Recall 越高。 -
mAP50:IoU 阈值为 0.5 时的平均精度。 -
mAP50-95:IoU 从 0.5 到 0.95 的多个阈值平均值,更严格、更能反映定位质量。 -
box_loss:边界框定位损失。 -
cls_loss:分类损失。 -
dfl_loss:边界框分布回归损失。
不要只看训练损失。训练损失持续下降,但验证集 mAP 不再提升甚至下降,通常说明模型开始过拟合。
对于不同业务,应重点关注不同指标:
-
漏检代价高:优先关注 Recall。
-
误检代价高:优先关注 Precision。
-
对框的位置要求高:重点关注 mAP50-95。
11. 单独评估验证集
命令行方式:
yolo detect val model=runs/detect/my_yolov8_exp/weights/best.pt data=dataset.yaml split=val
Python 方式:
from ultralytics import YOLO
model = YOLO("runs/detect/my_yolov8_exp/weights/best.pt")
metrics = model.val(data="dataset.yaml", split="val")
print("mAP50:", metrics.box.map50)
print("mAP50-95:", metrics.box.map)
print("各类别 mAP50-95:", metrics.box.maps)
验证集指标用于训练期间比较模型和选择权重,不能代替最终测试集指标。
12. 在测试集上进行最终评估
只有在模型结构、训练参数和置信度策略确定后,才对测试集进行最终评估。
命令行方式:
yolo detect val model=runs/detect/my_yolov8_exp/weights/best.pt data=dataset.yaml split=test
Python 方式:
from ultralytics import YOLO
model = YOLO("runs/detect/my_yolov8_exp/weights/best.pt")
metrics = model.val(data="dataset.yaml", split="test")
print("测试集 Precision:", metrics.results_dict.get("metrics/precision(B)"))
print("测试集 Recall:", metrics.results_dict.get("metrics/recall(B)"))
print("测试集 mAP50:", metrics.box.map50)
print("测试集 mAP50-95:", metrics.box.map)
不要反复根据测试集结果修改参数,否则测试集也会逐渐变成验证集,最终指标不再客观。
13. 使用训练好的模型推理
命令行推理:
yolo detect predict model=runs/detect/my_yolov8_exp/weights/best.pt source=demo_images conf=0.25 save=True
Python 推理:
from ultralytics import YOLO
model = YOLO("runs/detect/my_yolov8_exp/weights/best.pt")
results = model.predict(
source="demo_images",
imgsz=640,
conf=0.25,
iou=0.7,
save=True,
)
for result in results:
print(result.boxes.xyxy) # 边界框坐标
print(result.boxes.conf) # 置信度
print(result.boxes.cls) # 类别编号
conf 越低,通常检出的目标越多,但误检也可能增加。阈值应根据验证集 PR 曲线和实际业务要求确定,不能只凭单张图片调整。
14. 中断后继续训练
使用最后一次保存的 last.pt 恢复训练:
yolo detect train resume model=runs/detect/my_yolov8_exp/weights/last.pt
Python 方式:
from ultralytics import YOLO
model = YOLO("runs/detect/my_yolov8_exp/weights/last.pt")
model.train(resume=True)
resume=True 会恢复优化器、学习率和训练轮数等状态。仅加载 last.pt 后重新调用普通训练,不等同于完整断点续训。
15. 导出部署模型
导出 ONNX:
yolo export model=runs/detect/my_yolov8_exp/weights/best.pt format=onnx imgsz=640 dynamic=True simplify=True
Python 方式:
from ultralytics import YOLO
model = YOLO("runs/detect/my_yolov8_exp/weights/best.pt")
model.export(format="onnx", imgsz=640, dynamic=True, simplify=True)
常见导出格式:
| 格式 | 参数 | 常见用途 |
|---|---|---|
| ONNX | format="onnx" |
ONNX Runtime、跨平台部署 |
| TensorRT | format="engine" |
NVIDIA GPU 高性能推理 |
| OpenVINO | format="openvino" |
Intel CPU/GPU 部署 |
| TorchScript | format="torchscript" |
PyTorch/C++ 环境 |
| TFLite | format="tflite" |
移动端和嵌入式设备 |
导出后应使用目标部署环境重新验证精度和速度。不同推理后端、数值精度和 NMS 实现可能造成结果差异。
总结
YOLOv8 训练本身只需要几行命令或代码,真正决定模型效果的是数据质量、合理的数据划分和可信的评估流程。训练集负责学习,验证集负责选择模型,测试集负责最终验收;三者必须严格隔离。尤其是视频抽帧数据,如果忽略相邻帧泄漏,即使得到很高的 mAP,也可能无法反映模型在新场景中的真实表现。
更多推荐




所有评论(0)