返回项目列表
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 模型推理成功!")
✓ 完成标准:能成功构建/加载模型,打印模型结构和参数量