3. 目标检测基础回顾:YOLOv5/v8原理简介、使用预训练模型进行行人/车辆检测、检测结果格式
好,咱们进入第三讲。做多目标跟踪,说白了,你得先能「看见」目标。这个「看见」的活儿,就是目标检测干的。我见过不少新手,一上来就调跟踪参数,结果发现检测框满天飞——那跟踪效果能好才怪。所以,今天咱们把检测这块地基夯实了。
3.1 YOLOv5与YOLOv8:两代经典,各有千秋
YOLO系列,全称You Only Look Once。意思就是「看一眼就够了」。它不像老派的Faster R-CNN那样分两步走,而是把目标检测当成一个回归问题,一步到位输出边界框和类别。我个人习惯把YOLO理解为「用整张图做一次前向推理,直接告诉你哪里有什么」。
3.1.1 YOLOv5:稳定可靠的老伙计
YOLOv5虽然名字里带个v5,但它其实不是官方YOLO作者的作品,而是Ultralytics团队搞的。不过这不重要,好用就行。我在项目中遇到过很多次,YOLOv5在中等算力的设备上(比如Jetson Nano)跑得飞起,精度也够用。
它的核心结构可以拆成三块:
- Backbone(骨干网络):用CSPDarknet53提取特征。说白了就是一堆卷积堆叠,把图片变成特征图。
- Neck(颈部):用FPN+PAN结构做特征融合。小目标用大特征图,大目标用小特征图,各取所需。
- Head(检测头):输出三个尺度的预测结果。每个尺度负责检测不同大小的物体。
关键点:YOLOv5的损失函数包含三部分——框回归损失(CIoU)、置信度损失(BCE)、分类损失(BCE)。训练时这三者加权求和,权重比例我一般用0.05:1.0:0.5,效果比较稳。
3.1.2 YOLOv8:新秀登场,更丝滑
YOLOv8是Ultralytics在2023年初推出的。相比v5,它做了几个重要改进:
- 无锚框检测(Anchor-Free):v5需要预设一堆锚框,v8直接预测中心点和宽高。你想想看,少了一个调参的烦恼。
- C2f模块:取代了v5的C3模块。梯度流更丰富,特征表达能力更强。
- 解耦检测头:分类和回归用不同的卷积分支。我试过,收敛速度确实快一些。
嗯,这里要注意:v8的模型体积比v5稍大,但推理速度几乎没差。如果你在边缘设备上跑,v5可能更省心;如果追求极致精度,v8是更好的选择。
| 对比项 | YOLOv5 | YOLOv8 |
|---|---|---|
| 锚框机制 | 有锚框(Anchor-Based) | 无锚框(Anchor-Free) |
| 骨干网络 | CSPDarknet53 | 改进版CSPDarknet + C2f |
| 检测头 | 耦合头 | 解耦头 |
| 推理速度 | 稍快 | 几乎持平 |
| mAP精度 | 良好 | 略高(约1-2%) |
3.2 使用预训练模型进行行人/车辆检测
讲完原理,咱们直接上手。我建议你直接用Ultralytics的官方库,几行代码就能跑起来。别自己从头训练,除非你有几十万张标注图——我曾经试过自己标了5000张,效果还不如人家预训练的零头。
3.2.1 安装与加载模型
pip install ultralytics
装好之后,加载模型就这么简单:
from ultralytics import YOLO
# 加载YOLOv8预训练模型(自动下载权重)
model = YOLO('yolov8n.pt') # n: nano版本,最快但精度最低
# 或者用YOLOv5
# model = YOLO('yolov5su.pt')
这里有个小技巧:如果你只检测行人和车辆,可以指定类别过滤,省掉不必要的计算。
3.2.2 对图片进行检测
results = model('street.jpg') # 传入图片路径
# 遍历检测结果
for result in results:
boxes = result.boxes # 边界框对象
for box in boxes:
x1, y1, x2, y2 = box.xyxy[0].tolist() # 左上角+右下角坐标
conf = box.conf[0].item() # 置信度
cls = int(box.cls[0].item()) # 类别ID
# COCO数据集中:0=人, 2=汽车, 3=摩托车, 5=公交车, 7=卡车
if cls in [0, 2, 5, 7]: # 只保留行人和车辆
print(f'检测到目标: 类别{cls}, 置信度{conf:.2f}, 坐标({x1:.0f},{y1:.0f},{x2:.0f},{y2:.0f})')
我的经验:实际项目中,置信度阈值别设太低。我一般设0.5以上。低于0.5的框,十有八九是误检。尤其是行人,树影、垃圾桶都容易误报。
3.2.3 对视频流进行检测
做多目标跟踪,你肯定要处理视频。YOLO对视频的处理也很直接:
results = model('traffic.mp4', stream=True) # stream=True表示流式处理
for frame_idx, result in enumerate(results):
# 每帧的检测结果
boxes = result.boxes
# ... 处理逻辑同上
if frame_idx % 30 == 0:
print(f'已处理 {frame_idx} 帧')
为什么会用stream=True?因为如果不加,它会等所有帧处理完才返回结果,内存直接爆炸。我刚开始就吃过这个亏,处理一段10分钟的视频,内存飙到16G——嗯,从那以后我再也不敢忘了这个参数。
3.3 检测结果格式详解
检测结果说白了就是三样东西:边界框(bbox)、置信度(confidence)、类别(class)。这三者缺一不可,是后续跟踪算法的输入。
3.3.1 边界框格式
YOLO输出两种常见的框格式:
- xyxy格式:
[x1, y1, x2, y2],左上角和右下角坐标。直观,但框的宽高需要自己算。 - xywh格式:
[x_center, y_center, width, height],中心点+宽高。SORT和DeepSORT内部常用这种。
转换很简单:
# xyxy -> xywh
x_center = (x1 + x2) / 2
y_center = (y1 + y2) / 2
width = x2 - x1
height = y2 - y1
# xywh -> xyxy
x1 = x_center - width / 2
y1 = y_center - height / 2
x2 = x_center + width / 2
y2 = y_center + height / 2
注意:坐标都是归一化到[0,1]之间的吗?不一定。YOLOv5/v8默认输出的是像素坐标(绝对坐标),除非你指定了normalized=True。做跟踪时,我建议统一用像素坐标,因为卡尔曼滤波需要绝对位置信息。
3.3.2 置信度与类别
置信度是一个0到1之间的浮点数,表示模型对这个框的「自信程度」。类别是整数ID,对应COCO数据集的80个类别。咱们做行人/车辆检测,重点关注这几个:
| 类别ID | 名称 | 说明 |
|---|---|---|
| 0 | person | 行人,跟踪的核心目标 |
| 2 | car | 小轿车,常见 |
| 5 | bus | 公交车,体积大 |
| 7 | truck | 卡车,注意和bus区分 |
3.3.3 完整的检测结果结构
在Ultralytics中,一个检测结果通常组织成这样的数据结构:
# 假设当前帧检测到3个目标
detections = [
{
'bbox': [100, 200, 150, 350], # xyxy格式,像素坐标
'confidence': 0.92,
'class_id': 0, # 行人
'class_name': 'person'
},
{
'bbox': [300, 250, 450, 400],
'confidence': 0.88,
'class_id': 2, # 汽车
'class_name': 'car'
},
{
'bbox': [500, 300, 600, 420],
'confidence': 0.45, # 置信度低,建议过滤
'class_id': 0,
'class_name': 'person'
}
]
# 过滤低置信度检测
valid_detections = [d for d in detections if d['confidence'] > 0.5]
print(f'有效检测数: {len(valid_detections)}')
避坑指南:我曾经在跟踪时直接把所有检测结果喂给SORT,结果发现跟踪器频繁切换ID。后来排查发现,是那些置信度0.3左右的误检框在捣乱。所以,检测质量直接决定跟踪上限。宁可漏检,不要误检。
3.4 小结
这一章咱们把目标检测的基础过了一遍。YOLOv5和v8各有优势,选哪个看你自己的场景。预训练模型直接拿来用,省时省力。检测结果格式要记牢——bbox、confidence、class,这三个字段是后续所有跟踪算法的输入。
下一章,咱们就要正式进入SORT算法的核心了。到时候你会发现,检测做得越扎实,跟踪调参就越轻松。嗯,咱们下节课见。