返回项目列表

YOLO 目标检测实战

📋 项目概览

难度⭐⭐⭐高级
时长2周
前置知识
Python 基础PyTorch 基础CNN 基础概念
关联路线图节点

🎯 学习目标

  • 理解目标检测核心概念(IoU、NMS、mAP)
  • 掌握 YOLO 数据集标注与格式转换
  • 学会训练 YOLOv8 自定义数据集
  • 实现模型导出与推理部署

📦 项目结构

datasets/# 数据集目录必需
models/# 模型权重
train.py# 训练脚本必需
detect.py# 推理脚本必需
README.md# 项目说明必需

🚀 实现步骤

1

目标检测基础与环境配置

理解目标检测核心概念,配置 Ultralytics 环境

任务清单
  • 1.1 安装 ultralytics 库:pip install ultralytics
  • 1.2 理解 IoU(交并比)计算公式:IoU = Intersection / Union
  • 1.3 理解 NMS(非极大值抑制)算法原理和作用
  • 1.4 理解 mAP(平均精度均值)评估指标含义
  • 1.5 加载 YOLOv8n 预训练模型:YOLO('yolov8n.pt')
  • 1.6 对示例图片进行推理并解析检测结果
  • 1.7 可视化边界框、置信度和类别标签
  • 1.8 验证环境配置是否正确,输出检测结果
环境验证代码
pip install ultralytics
模型加载与推理代码
from ultralytics import YOLO
import cv2

# 加载预训练模型
model = YOLO('yolov8n.pt')

# 对图片进行推理
results = model('bus.jpg')

# 打印检测结果
for r in results:
    print(f"检测到 {len(r.boxes)} 个目标")
    for box in r.boxes:
        cls_id = int(box.cls[0])
        conf = float(box.conf[0])
        xyxy = box.xyxy[0].cpu().numpy()
        print(f"  类别: {model.names[cls_id]}, 置信度: {conf:.2f}, 边界框: {xyxy}")

# 可视化结果(取第一张图的结果)
if len(results) > 0:
    annotated = results[0].plot()
    cv2.imwrite('result.jpg', annotated)
IoU计算代码
import numpy as np

def calculate_iou(box1, box2):
    """
    计算两个边界框的 IoU
    box格式: [x1, y1, x2, y2] (左上角,右下角)
    """
    x1 = max(box1[0], box2[0])
    y1 = max(box1[1], box2[1])
    x2 = min(box1[2], box2[2])
    y2 = min(box1[3], box2[3])
    
    intersection = max(0, x2 - x1) * max(0, y2 - y1)
    
    area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
    area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
    union = area1 + area2 - intersection
    
    return intersection / union if union > 0 else 0

# 测试
box_a = [50, 50, 150, 150]
box_b = [100, 100, 200, 200]
print(f"IoU: {calculate_iou(box_a, box_b):.3f}")

✓ 完成标准:能成功配置开发环境,运行验证脚本显示所有依赖正常

2

数据集标注与格式转换

掌握 YOLO 数据集格式,完成数据标注

任务清单
  • 2.1 了解 YOLO 格式:class_id x_center y_center width height(归一化坐标)
  • 2.2 安装并使用 LabelImg 标注工具:pip install labelImg
  • 2.3 使用 LabelImg 标注至少 50 张图片,设置正确的保存格式为 YOLO
  • 2.4 了解 Roboflow 在线标注平台的使用方法
  • 2.5 创建 data.yaml 配置文件,指定 train/val 路径、nc 和 names
  • 2.6 划分训练集和验证集(建议比例 8:2)
  • 2.7 验证标注文件格式是否正确
  • 2.8 检查数据集目录结构是否符合 YOLO 规范
Data Yaml配置
# data.yaml
path: ./datasets/myproject  # 数据集根目录
train: images/train  # 训练集图片路径(相对于 path)
val: images/val    # 验证集图片路径

# 类别数量
nc: 3

# 类别名称
names:
  0: cat
  1: dog
  2: bird
LabelImg启动命令
# 安装 LabelImg
pip install labelImg

# 启动 LabelImg(设置保存格式为 YOLO)
labelImg --yolo --output-dir ./labels
数据集目录结构
datasets/
└── myproject/
    ├── images/
    │   ├── train/
    │   │   ├── img001.jpg
    │   │   ├── img002.jpg
    │   │   └── ...
    │   └── val/
    │       ├── img051.jpg
    │       └── ...
    └── labels/
        ├── train/
        │   ├── img001.txt  # YOLO 格式标注
        │   └── ...
        └── val/
            └── ...
标注验证代码
import os
from pathlib import Path

def verify_yolo_annotations(dataset_path, class_names):
    """验证 YOLO 标注文件格式"""
    errors = []
    
    labels_dir = Path(dataset_path) / 'labels'
    images_dir = Path(dataset_path) / 'images'
    
    for split in ['train', 'val']:
        split_labels = labels_dir / split
        if not split_labels.exists():
            continue
            
        for label_file in split_labels.glob('*.txt'):
            img_file = images_dir / split / f"{label_file.stem}.jpg"
            if not img_file.exists():
                errors.append(f"图片缺失: {img_file}")
                continue
                
            with open(label_file, 'r') as f:
                for line_num, line in enumerate(f, 1):
                    parts = line.strip().split()
                    if len(parts) != 5:
                        errors.append(f"{label_file}:{line_num} - 格式错误,期望5个值")
                        continue
                    
                    cls_id, x, y, w, h = map(float, parts)
                    if not (0 <= cls_id < len(class_names)):
                        errors.append(f"{label_file}:{line_num} - 类别ID {int(cls_id)} 超出范围")
                    if not (0 <= x <= 1 and 0 <= y <= 1 and 0 <= w <= 1 and 0 <= h <= 1):
                        errors.append(f"{label_file}:{line_num} - 坐标值必须在 [0,1] 范围内")
    
    if errors:
        print("发现以下错误:")
        for e in errors:
            print(f"  - {e}")
    else:
        print("所有标注文件格式正确!")
    
    return len(errors) == 0

# 使用示例
verify_yolo_annotations('./datasets/myproject', ['cat', 'dog', 'bird'])

✓ 完成标准:能成功加载数据集,打印出数据样本和统计信息

3

模型训练与调优

训练自定义 YOLOv8 模型,掌握训练参数

任务清单
  • 3.1 准备预训练权重,使用 YOLOv8n.pt 作为初始化权重
  • 3.2 配置训练参数:epochs=100, batch=16, imgsz=640
  • 3.3 配置 data.yaml 路径,确保训练能正确读取数据
  • 3.4 设置模型名称(name 参数),方便后续追踪实验
  • 3.5 观察训练日志,监控 loss 曲线下降情况
  • 3.6 了解关键训练参数:learning_rate、weight_decay、optimizer
  • 3.7 掌握数据增强参数:hsv、flip、scale、degrees 等
  • 3.8 保存最佳模型权重,理解 best.pt 和 last.pt 的区别
训练代码
from ultralytics import YOLO

# 加载预训练模型
model = YOLO('yolov8n.pt')

# 开始训练
results = model.train(
    data='data.yaml',      # 数据集配置文件
    epochs=100,             # 训练轮数
    batch=16,               # 批次大小
    imgsz=640,              # 输入图像尺寸
    name='my_model',        # 实验名称
    
    # 优化器参数
    optimizer='SGD',        # 可选: SGD, Adam, AdamW
    lr0=0.01,               # 初始学习率
    lrf=0.01,               # 最终学习率 = lr0 * lrf
    momentum=0.937,         # 动量
    weight_decay=0.0005,    # 权重衰减
    
    # 数据增强
    hsv_h=0.015,            # 色调增强
    hsv_s=0.7,              # 饱和度增强
    hsv_v=0.4,              # 亮度增强
    degrees=0.0,            # 旋转角度
    translate=0.1,          # 平移
    scale=0.5,              # 缩放
    flipud=0.0,             # 上下翻转
    fliplr=0.5,             # 左右翻转概率
    mosaic=1.0,             # 马赛克增强
    mixup=0.0,              # MixUp 增强
    
    # 其他设置
    patience=50,            # 早停耐心值
    save=True,              # 保存模型
    save_period=10,         # 每隔多少轮保存一次
    device=''               # 空字符串表示自动选择
)

print("训练完成!")
print(f"最佳模型: runs/detect/my_model/weights/best.pt")
高级训练配置
# advanced_train.yaml
# 训练高级配置示例
epochs: 300
batch: 32
imgsz: 1024  # 更大尺寸提升精度但增加显存需求

# 学习率调度
lr0: 0.001
lrf: 0.0001
warmup_epochs: 3
warmup_momentum: 0.8
warmup_bias_lr: 0.1

# 骨干网络冻结(用于迁移学习)
freeze: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]  # 冻结前10层

# 多尺度训练
multi_scale: True

# 验证设置
val: True
plots: True
恢复中断训练
from ultralytics import YOLO

# 从中断点恢复训练
model = YOLO('runs/detect/my_model/weights/last.pt')
results = model.train(
    data='data.yaml',
    epochs=200,  # 继续训练到200轮
    name='my_model_resume',
    resume=True  # 关键参数!从 last.pt 恢复
)

✓ 完成标准:能成功构建/加载模型,打印模型结构和参数量

4

模型评估与错误分析

全面评估模型性能,分析错误样本

任务清单
  • 4.1 使用 model.val() 在验证集上评估模型性能
  • 4.2 理解 mAP50 和 mAP50-95 指标的含义和区别
  • 4.3 查看 PR 曲线和 F1 曲线,分析不同类别的 precision/recall
  • 4.4 分析混淆矩阵,找出容易混淆的类别对
  • 4.5 收集漏检(FN)和误检(FP)样本
  • 4.6 分析 Bad Case:错误类型包括遮挡、小目标、类别混淆、运动模糊
  • 4.7 根据分析结果制定优化策略
  • 4.8 生成评估报告,记录关键指标
评估代码
from ultralytics import YOLO

# 加载训练好的模型
model = YOLO('runs/detect/my_model/weights/best.pt')

# 在验证集上评估
metrics = model.val()

# 打印详细指标
print(f"mAP50: {metrics.box.map50:.4f}")
print(f"mAP50-95: {metrics.box.map:.4f}")
print(f"mAP per class: {metrics.box.ap50}")
print(f"Precision: {metrics.box.mp:.4f}")
print(f"Recall: {metrics.box.mr:.4f}")

# 按类别打印 AP
for i, name in enumerate(model.names):
    ap50 = metrics.box.ap50[i]
    print(f"  {name}: AP50={ap50:.4f}" if ap50 > 0 else f"  {name}: N/A")
BadCase分析代码
from ultralytics import YOLO
import cv2
import numpy as np
from pathlib import Path

def analyze_bad_cases(model_path, data_yaml, output_dir='bad_cases'):
    """分析漏检和误检样本"""
    model = YOLO(model_path)
    Path(output_dir).mkdir(exist_ok=True)
    
    # 获取验证集图片
    # 假设图片在 datasets/xxx/images/val/
    val_images = Path('datasets/myproject/images/val').glob('*.jpg')
    
    false_negatives = []  # 漏检
    false_positives = []  # 误检
    
    for img_path in val_images:
        # 读取图片和真实标签
        img = cv2.imread(str(img_path))
        label_path = Path('datasets/myproject/labels/val') / f"{img_path.stem}.txt"
        
        # 推理
        results = model(img_path)
        pred_boxes = results[0].boxes
        
        # 读取真实框(简化版,实际需要解析标注文件)
        gt_boxes = []
        if label_path.exists():
            with open(label_path) as f:
                for line in f:
                    cls, x, y, w, h = map(float, line.split())
                    gt_boxes.append((int(cls), x, y, w, h))
        
        # 简单分析:检测到的但没有对应 GT 的为 FP
        # 有 GT 但没检测到的为 FN
        if len(pred_boxes) == 0 and len(gt_boxes) > 0:
            false_negatives.append((img_path, gt_boxes))
        elif len(pred_boxes) > 0 and len(gt_boxes) == 0:
            false_positives.append((img_path, pred_boxes))
    
    print(f"漏检样本数: {len(false_negatives)}")
    print(f"误检样本数: {len(false_positives)}")
    
    return false_negatives, false_positives

# 执行分析
fn, fp = analyze_bad_cases('runs/detect/my_model/weights/best.pt', 'data.yaml')
混淆矩阵分析
from ultralytics import YOLO
import numpy as np

# 加载模型
model = YOLO('runs/detect/my_model/weights/best.pt')

# 获取验证数据源
data_source = 'data.yaml'  # 或直接指定图片路径

# 运行验证(会输出混淆矩阵)
metrics = model.val()

# 访问混淆矩阵
cm = metrics.box.confusion_matrix
print("混淆矩阵:")
print(cm)

# 分析各类别性能
for i, name in enumerate(model.names):
    # 混淆矩阵中每一行代表真实类别,每一列代表预测类别
    true_positive = cm[i, i]
    false_positive = cm[:, i].sum() - true_positive
    false_negative = cm[i, :].sum() - true_positive
    
    precision = true_positive / (true_positive + false_positive) if (true_positive + false_positive) > 0 else 0
    recall = true_positive / (true_positive + false_negative) if (true_positive + false_negative) > 0 else 0
    
    print(f"{name}: Precision={precision:.3f}, Recall={recall:.3f}")

✓ 完成标准:能成功构建/加载模型,打印模型结构和参数量

5

模型导出与部署

导出 ONNX 模型,部署推理服务

任务清单
  • 5.1 将 best.pt 导出为 ONNX 格式:model.export(format='onnx')
  • 5.2 验证 ONNX 模型推理结果与 PyTorch 模型一致
  • 5.3 使用 FastAPI 创建 HTTP 推理服务
  • 5.4 实现 /detect 接口接收图片并返回检测结果
  • 5.5 处理图片上传和预处理(缩放、归一化)
  • 5.6 后处理检测结果并返回 JSON 格式
  • 5.7 添加健康检查接口 /health
  • 5.8 测试部署服务的功能和性能
导出ONNX代码
from ultralytics import YOLO

# 加载训练好的模型
model = YOLO('runs/detect/my_model/weights/best.pt')

# 导出为 ONNX 格式
success = model.export(format='onnx', imgsz=640)
print(f"导出成功: {success}")

# 导出后会生成: runs/detect/my_model/weights/best.onnx
FastAPI推理服务
# main.py
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import JSONResponse
from ultralytics import YOLO
import cv2
import numpy as np
from io import BytesIO
from PIL import Image

app = FastAPI(title="YOLO Detection API")

# 加载模型(启动时加载一次)
model = YOLO('runs/detect/my_model/weights/best.onnx')
# 如果使用 PyTorch 模型而非 ONNX:model = YOLO('runs/detect/my_model/weights/best.pt')

@app.get("/health")
async def health_check():
    """健康检查接口"""
    return {"status": "healthy", "model": "loaded"}

@app.post("/detect")
async def detect(file: UploadFile = File(...)):
    """
    检测接口
    - 接收图片文件
    - 返回检测结果(边界框、置信度、类别)
    """
    # 读取上传的图片
    contents = await file.read()
    image = Image.open(BytesIO(contents))
    image_np = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
    
    # 执行推理
    results = model(image_np, verbose=False)
    
    # 解析结果
    detections = []
    for r in results:
        boxes = r.boxes
        for box in boxes:
            xyxy = box.xyxy[0].cpu().numpy().tolist()
            conf = float(box.conf[0])
            cls_id = int(box.cls[0])
            cls_name = model.names[cls_id]
            
            detections.append({
                "class": cls_name,
                "class_id": cls_id,
                "confidence": round(conf, 4),
                "bbox": {
                    "x1": round(xyxy[0], 2),
                    "y1": round(xyxy[1], 2),
                    "x2": round(xyxy[2], 2),
                    "y2": round(xyxy[3], 2)
                }
            })
    
    return JSONResponse({
        "image_size": {"width": image.width, "height": image.height},
        "detections_count": len(detections),
        "detections": detections
    })

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
ONNX推理验证
import onnxruntime as ort
import cv2
import numpy as np as ort
from ultralytics import YOLO
import numpy as np

# 加载 ONNX 模型
session = ort.InferenceSession('runs/detect/my_model/weights/best.onnx')

# 获取输入输出名称
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name

# 准备输入数据(需要预处理)
# 这里使用 ultralytics 的预处理
model = YOLO('runs/detect/my_model/weights/best.pt')
img = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)

# 使用 PyTorch 模型推理获取标准结果
pt_results = model(img, verbose=False)
pt_boxes = pt_results[0].boxes.xyxy.cpu().numpy()

# ONNX 推理(需要手动实现预处理)
# 1. 缩放到 640x640
# 2. BGR 转 RGB
# 3. 归一化到 [0, 1]
# 4. HWC 转 CHW
img_resized = cv2.resize(img, (640, 640))
img_rgb = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)
img_input = img_rgb.astype(np.float32) / 255.0
img_input = img_input.transpose(2, 0, 1).reshape(1, 3, 640, 640)

# 执行推理
onnx_results = session.run([output_name], {input_name: img_input})

print("PyTorch 模型结果:", pt_boxes[:3])
print("ONNX 模型推理成功!")

✓ 完成标准:能成功构建/加载模型,打印模型结构和参数量

📚 参考资源