本文介绍如何使用 Ultralytics YOLOv8 完成一个自定义目标检测项目,内容包括环境配置、训练集/验证集/测试集划分、YOLO 标注格式、数据描述文件、模型训练、指标评估、推理以及模型导出。

本文以单类别目标检测为例。多类别数据集只需要增加类别名称,并保证标签中的类别编号对应正确。

1. 训练、验证和测试集分别有什么作用

一个完整的数据集通常分为三部分:

数据集 主要用途 是否参与权重更新
训练集(train) 模型学习目标特征
验证集(val) 每轮训练后评估效果、选择最佳权重、触发提前停止
测试集(test) 模型和参数全部确定后,进行最终泛化能力评估

常用划分比例:

  • 数据量充足:训练集 80%,验证集 10%,测试集 10%。

  • 数据量一般:训练集 70%,验证集 20%,测试集 10%。

  • 数据量很少时,测试结果波动会很大,应优先增加数据,也可以使用交叉验证获得更可靠的结论。

划分数据时需要注意:

  1. 先划分原始数据,再对训练集做数据增强,不能把同一张图片的增强版本分到不同集合。

  2. 各集合应尽量保持类别、场景、光照和目标尺寸分布一致。

  3. 测试集不能用于挑选模型、调整阈值或修改训练参数。

  4. 如果图片来自视频抽帧,应按照视频或连续时间片段分组划分。不能随机打散所有相邻帧,否则训练集和测试集画面高度相似,评估指标会虚高。

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_centery_center:边界框中心点坐标。

  • widthheight:边界框宽度和高度。

  • 后四个数都必须除以图片宽高,归一化到 [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:数据集根目录。

  • trainvaltest:相对于 path 的图片目录。

  • names:类别编号和类别名称的对应关系。

  • 只有一个类别时,所有标签的 class_id 都应为 0

多类别示例:

names:
  0: person
  1: helmet
  2: vehicle

建议在项目根目录执行训练命令,使相对路径的解析保持一致。数据也可以存放在其他位置,只要 YAML 中配置的路径能够正确访问即可,并不强制放在 datasets 目录。

5. 训练前检查数据

正式训练前至少检查以下内容:

  • 图片能正常打开,没有损坏文件。

  • 每张正样本图片都有同名标签。

  • 类别编号从 0 开始,并且没有超过 names 的范围。

  • 坐标已经归一化到 [0, 1]

  • 边界框的 widthheight 大于 0

  • 训练集、验证集和测试集之间没有重复图片或相邻视频帧泄漏。

  • 边界框尽量贴合目标,不包含大面积无关背景。

第一次训练后,还应查看输出目录中的 labels.jpgtrain_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=4workers=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,也可能无法反映模型在新场景中的真实表现。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐