1. 三大目标检测框架的演进脉络与选型逻辑
目标检测这个方向,从2014年RCNN横空出世到现在,十年间经历了从“两阶段精雕细琢”到“单阶段暴力回归”再到“Transformer端到端”的范式迁移。RCNN、DETR、YOLO这三个名字,基本代表了三条截然不同的技术路线。我最早接触的是YOLOv3,那会儿还在用Darknet框架,配置文件写得人头大;后来做工业质检项目被迫啃Faster RCNN的源码,被ROI Pooling和RPN绕得够呛;再后来DETR出来,一看论文觉得“这也能行?”,真正跑起来才发现收敛慢得让人想砸键盘。这篇文章就把这三条路线拆开揉碎讲清楚,从核心原理到实操踩坑,从环境搭建到训练调参,尽量让刚入门的同学少走点弯路。
先给一个全局认知:RCNN系列是“先找候选框、再分类”的两阶段思路,精度高但速度慢;YOLO系列是“一眼看全图、直接回归框和类别”的单阶段思路,速度快但小目标容易漏;DETR则是用Transformer把目标检测变成“集合预测”问题,彻底去掉了锚框和非极大值抑制,架构最优雅但训练最磨人。三条路线没有绝对优劣,选哪个完全看你的场景——要精度且不差算力,Faster RCNN依然能打;要实时部署到边缘设备,YOLO是首选;要做多模态或者需要端到端可微分的检测头,DETR值得一试。
1.1 从RCNN到Faster RCNN:两阶段检测的进化史
RCNN在2014年由Ross Girshick提出,核心思路非常“手工”:先用Selective Search从每张图里抠出约2000个候选区域,然后把这些区域逐个缩放到固定尺寸,送进AlexNet提取特征,最后用SVM分类、用线性回归微调框的位置。这个流程在PASCAL VOC上把mAP从之前的30%多直接拉到58%左右,效果震撼,但问题也极其明显——2000个候选区域每个都要过一遍CNN,一张图推理要几十秒,训练更是要分好几步走,磁盘上还得存一堆特征文件,工程上非常笨重。
Fast RCNN在2015年做了关键改进:不再对每个候选区域单独跑CNN,而是整张图先过一次卷积网络得到特征图,然后通过ROI Pooling把每个候选区域对应的特征块池化成固定尺寸。分类和框回归合并到一个网络里多任务训练,速度提升了几十倍。但候选区域的生成仍然依赖Selective Search,这个环节在CPU上跑,成了新的瓶颈。
Faster RCNN在同年晚些时候提出,用RPN(Region Proposal Network)彻底替换了Selective Search。RPN本身是一个全卷积网络,在特征图上每个位置预设9种不同尺度和长宽比的锚框,直接预测每个锚框是前景还是背景、以及框的偏移量。RPN和检测头共享卷积特征,整个网络可以端到端训练。这一步把候选区域生成也搬到了GPU上,推理速度在V100上能到5-10 FPS(取决于输入分辨率和骨干网络),精度在COCO上达到37%左右的mAP。Faster RCNN的架构影响深远,后来的Mask RCNN、Cascade RCNN都是在它基础上做的扩展。
1.2 YOLO系列:单阶段检测的暴力美学
YOLO(You Only Look Once)在2016年由Joseph Redmon提出,思路和RCNN完全相反:把整张图分成S×S的网格,每个网格负责预测中心点落在该网格内的目标。每个网格输出B个边界框(含坐标、置信度)和C个类别概率。整张图只需要过一次网络,输出就是一个S×S×(B×5+C)的张量,直接解码就能得到检测结果。YOLOv1在Titan X上能跑到45 FPS,精度虽然比Faster RCNN低一些,但速度优势太大了,立刻在工业界火了起来。
YOLOv2(也叫YOLO9000)引入了锚框机制、批量归一化、高分辨率分类器微调等技巧,mAP从63.4%提升到78.6%(VOC 2007)。YOLOv3换用了Darknet-53骨干网络,引入FPN式的多尺度预测,在小目标检测上有了明显改善,至今仍是很多工业项目的基线。YOLOv4由Alexey Bochkovskiy接手,堆了大量“免费包”(Bag of Freebies)和“特价包”(Bag of Specials),在COCO上达到43.5% mAP,速度65 FPS(Tesla V100)。YOLOv5由Ultralytics用PyTorch重写,工程化做得极好,提供了从n到x五种规模的模型,一键训练、一键导出ONNX/TensorRT,成了目前工业部署最常用的版本。后续的YOLOv6、YOLOv7、YOLOv8、YOLOv9、YOLOv10各有侧重,YOLOv8在实例分割和姿态估计上做了统一框架,YOLOv10则主打无NMS的端到端推理。
1.3 DETR:Transformer带来的端到端革命
DETR(DEtection TRansformer)在2020年由Facebook AI提出,是第一个把Transformer成功用于目标检测的工作。它的架构极其简洁:CNN骨干网络提取特征图,展平后加上位置编码送入Transformer编码器,解码器用固定数量的可学习查询向量(object queries)去“询问”图像中有什么目标,每个查询输出一个框和类别。训练时用匈牙利算法做二分图匹配,把预测框和真实框一一对应后计算损失。整个流程没有锚框、没有NMS、没有ROI Pooling,真正端到端。
DETR在COCO上达到42% mAP,和大模型版本的Faster RCNN相当,但小目标检测明显偏弱,而且训练收敛极慢——需要500个epoch才能收敛,是Faster RCNN的10倍以上。后续的Deformable DETR引入可变形注意力,只关注参考点周围的少量采样点,收敛速度提升10倍,小目标精度也上来了。DINO、RT-DETR等工作进一步把DETR推向实时检测领域,RT-DETR在V100上能跑到100+ FPS,精度还超过同级别的YOLO。DETR系列目前最大的优势在于架构统一、易于多模态扩展,比如把文本编码器和图像编码器一起送进Transformer,就能做开放词汇检测。
2. 核心原理拆解:从锚框到集合预测
2.1 Faster RCNN的RPN与ROI Align
Faster RCNN的核心创新是RPN。在骨干网络输出的特征图上(比如VGG16的conv5_3,stride=16),用一个3×3卷积滑窗,每个位置生成9个锚框(3种尺度×3种长宽比)。对于每个锚框,RPN输出两个分支:2个分数(前景/背景)和4个偏移量(dx, dy, dw, dh)。训练时,与真实框IoU大于0.7的锚框标为正样本,小于0.3的标为负样本,中间的不参与训练。RPN损失是分类交叉熵和Smooth L1回归损失的加权和。
ROI Pooling在Fast RCNN中引入,但存在两次量化误差:第一次是把候选框坐标从原图映射到特征图时取整,第二次是把特征块划分成固定数量的bin时取整。对于小目标,这些量化误差会显著影响精度。Mask RCNN提出了ROI Align,用双线性插值代替取整,在每个bin里采样4个点做插值,精度提升明显。如果你现在还在用ROI Pooling,建议换成ROI Align,代码改动不大但效果立竿见影。
2.2 YOLO的损失函数演进与正负样本分配
YOLOv1的损失函数很直接:坐标误差用均方误差,置信度误差和分类误差也用均方误差,只是给不同项加了权重。问题是对大框和小框的坐标误差同等对待,导致小框的定位精度差。YOLOv2开始用锚框后,坐标预测改为预测相对于锚框的偏移量。YOLOv3用二元交叉熵替换了softmax分类,支持多标签。YOLOv4引入了CIoU损失,同时考虑重叠面积、中心点距离和长宽比,收敛更快。
YOLOv5的损失函数由三部分组成:分类损失用BCEWithLogitsLoss,置信度损失也用BCE,框回归用CIoU。正负样本分配策略是:每个真实框至少匹配一个锚框(通过宽高比筛选),然后对每个锚框计算与真实框的IoU,超过阈值的作为正样本。YOLOv8改用了TaskAlignedAssigner,同时考虑分类得分和IoU来动态分配正样本,效果更好。YOLOv10则彻底去掉了NMS,用一致性双分配策略在训练时同时优化一对多和一对一的分配,推理时只用一对一分支,直接输出最终结果。
2.3 DETR的匈牙利匹配与集合预测损失
DETR把目标检测看作集合预测问题:输入一张图,输出N个预测(N通常设为100),每个预测是一个二元组(类别,框)。训练时用匈牙利算法在N个预测和M个真实框之间找最优匹配,匹配代价同时考虑分类概率和框的L1距离与GIoU。匹配完成后,只对匹配上的预测计算分类交叉熵和框回归损失,未匹配的预测只计算“无目标”分类损失。这种设计让DETR不需要NMS,因为每个真实框只会被一个预测匹配,不会出现重复框。
但匈牙利匹配有个问题:训练初期预测很乱,匹配结果不稳定,导致梯度震荡,收敛慢。Deformable DETR通过可变形注意力让每个查询只关注参考点周围的K个采样点,大大加速了收敛。DINO在Deformable DETR基础上引入了对比去噪训练、混合查询选择等技巧,在COCO上达到63% mAP,成为DETR系列的新标杆。RT-DETR则针对实时场景优化了编码器和解码器结构,用高效混合编码器和IoU感知查询选择,在保持精度的同时把速度拉到实时水平。
3. 实操环境搭建与训练自己的数据集
3.1 环境配置:从零搭建YOLOv8训练环境
YOLOv8是目前工业界最常用的版本,Ultralytics的工程化做得非常好。我推荐用conda建一个独立环境,避免和系统Python冲突。以下是我在Ubuntu 20.04 + CUDA 11.8 + V100上的实测步骤:
conda create -n yolov8 python=3.10 -y conda activate yolov8 pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics装完后用yolo checks验证环境,会输出PyTorch版本、CUDA是否可用、GPU型号等信息。如果CUDA不可用,大概率是PyTorch版本和驱动不匹配,用nvidia-smi看驱动支持的CUDA版本,然后去PyTorch官网找对应的安装命令。V100是Volta架构,算力7.0,支持FP16和Tensor Core,训练时开AMP(自动混合精度)能省不少显存。
数据集组织成YOLO格式:每张图对应一个txt标注文件,每行是类别id 中心x 中心y 宽 高,坐标都归一化到0-1。目录结构如下:
dataset/ images/ train/ val/ labels/ train/ val/然后写一个data.yaml:
path: /home/user/dataset train: images/train val: images/val nc: 3 names: ['person', 'car', 'dog']训练命令一行搞定:
yolo detect train data=data.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=32 device=0 amp=Trueyolov8s.pt是预训练模型,会自动下载。如果网络不通,可以手动去Ultralytics的GitHub Release页面下载后放到当前目录。训练过程中会实时打印损失、mAP、学习率等信息,训练完在runs/detect/train/下能看到权重文件、混淆矩阵、PR曲线等。
3.2 Faster RCNN训练自己的数据:以电力红外数据集为例
Faster RCNN的训练比YOLO麻烦一些,因为需要自己写Dataset和DataLoader。我以电力红外数据集(firc-dataset)为例,讲一下用PyTorch官方实现训练自己数据的流程。假设你已经把数据整理成VOC格式,每张图对应一个XML标注文件。
首先继承torchvision.datasets.VOCDetection,重写__getitem__返回图像张量和目标字典:
import torch from torchvision.models.detection import fasterrcnn_resnet50_fpn from torchvision.datasets import VOCDetection from torchvision.transforms import functional as F class InfraredDataset(VOCDetection): def __getitem__(self, idx): img, target = super().__getitem__(idx) boxes = [] labels = [] for obj in target['annotation']['object']: bbox = obj['bndbox'] xmin = float(bbox['xmin']) ymin = float(bbox['ymin']) xmax = float(bbox['xmax']) ymax = float(bbox['ymax']) boxes.append([xmin, ymin, xmax, ymax]) labels.append(1) # 假设只有一类 target = { 'boxes': torch.tensor(boxes, dtype=torch.float32), 'labels': torch.tensor(labels, dtype=torch.int64) } img = F.to_tensor(img) return img, target然后写collate_fn把batch里的图像和标注分别堆叠:
def collate_fn(batch): return tuple(zip(*batch))训练循环里,模型在train模式下返回损失字典,直接求和反向传播:
model = fasterrcnn_resnet50_fpn(pretrained=True) model.train() optimizer = torch.optim.SGD(model.parameters(), lr=0.005, momentum=0.9, weight_decay=0.0005) for images, targets in dataloader: images = [img.to(device) for img in images] targets = [{k: v.to(device) for k, v in t.items()} for t in targets] loss_dict = model(images, targets) losses = sum(loss for loss in loss_dict.values()) optimizer.zero_grad() losses.backward() optimizer.step()注意Faster RCNN的预训练权重是在COCO上训的,类别数是91,你要改成自己的类别数。修改model.roi_heads.box_predictor为一个新的FastRCNNPredictor,输入通道是in_features,输出是num_classes + 1(背景)。学习率建议用0.005起步,每3个epoch降一次,训练10-20个epoch通常就能收敛。V100上batch size设8左右,显存占用约12GB。
3.3 DETR训练自己数据:从COCO格式到自定义数据集
DETR的官方实现依赖COCO API,训练自己数据最省事的方式是把数据转成COCO格式。假设你有VOC格式的标注,可以用脚本转成COCO的json。COCO格式的核心是一个json文件,包含images、annotations、categories三个字段。每张图在images里有一条记录(id、file_name、width、height),每个标注在annotations里有一条记录(id、image_id、category_id、bbox、area、iscrowd),bbox格式是[x, y, width, height],注意是左上角坐标加宽高,不是右下角。
DETR的官方repo是facebookresearch/detr,克隆后安装依赖:
git clone https://github.com/facebookresearch/detr.git cd detr pip install -r requirements.txt然后修改datasets/coco.py里的num_classes,或者直接在main.py里传--num_classes参数。训练命令:
python main.py --dataset_file coco --coco_path /path/to/coco --output_dir /path/to/output --num_classes 3 --epochs 300 --batch_size 4DETR训练极慢,300个epoch在单张V100上大概要跑3-5天。如果算力有限,建议用Deformable DETR,收敛快10倍,50个epoch就能出不错的结果。Deformable DETR的repo是fundamentalvision/Deformable-DETR,训练命令类似,但需要编译CUDA算子,环境配置稍微麻烦一点。如果不想折腾,可以直接用HuggingFace的transformers库,里面集成了DETR和Deformable DETR,API非常友好:
from transformers import DetrForObjectDetection, DetrImageProcessor processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50", num_labels=3)HuggingFace版本的好处是训练循环可以自己写,和PyTorch生态无缝衔接,而且支持TrainerAPI,几行代码就能启动训练。
4. 常见问题与排查技巧实录
4.1 YOLO训练中BN崩溃与混淆矩阵不唯一
YOLO训练中最常见的问题之一是BN(Batch Normalization)崩溃,表现为损失突然变成NaN,或者mAP骤降到0。根本原因通常是batch size太小,导致BN层统计量估计不准。YOLOv5/v8默认batch size是16,如果你显存不够只能设4或8,BN就容易出问题。解决办法有两个:一是改用SyncBN(跨GPU同步BN),二是冻结BN层(设置model.train()后把BN层的momentum设为0,或者直接model.eval()但这样会关掉dropout)。我实测下来,V100单卡训练YOLOv8s,batch size至少设16才稳,如果显存不够就减小输入分辨率(从640降到416)或者换更小的模型(n或s)。
另一个高频问题是混淆矩阵总合不唯一。YOLO训练完会生成confusion_matrix.png,正常情况下矩阵所有元素之和应该等于验证集的总标注数。如果你发现总和对不上,大概率是验证集里有类别id超出了data.yaml里nc定义的范围,或者标注文件里有空行、格式错误。用yolo detect val命令重新跑一遍验证,加上--verbose参数看详细输出,通常能定位到具体是哪张图出了问题。还有一种可能是验证时用了--conf阈值过滤,低置信度的预测被滤掉了,导致矩阵总合偏小,把--conf设为0.001再跑一次就能对上。
4.2 Faster RCNN训练不收敛与显存溢出
Faster RCNN训练不收敛的典型表现是损失震荡、mAP一直上不去。我踩过的坑包括:学习率太大(V100上超过0.01就容易炸)、锚框尺度和目标尺寸不匹配(电力红外数据集里目标普遍偏小,默认锚框太大)、正负样本比例失衡(RPN里负样本太多)。针对锚框问题,可以用K-means对训练集的真实框做聚类,重新设置锚框的尺度和长宽比。torchvision的Faster RCNN支持通过anchor_generator参数自定义锚框:
from torchvision.models.detection.rpn import AnchorGenerator anchor_generator = AnchorGenerator( sizes=((16, 32, 64),), aspect_ratios=((0.5, 1.0, 2.0),) ) model.rpn.anchor_generator = anchor_generator显存溢出通常发生在batch size太大或者输入分辨率太高时。Faster RCNN的ROI Align会为每个候选框分配显存,候选框数量越多显存占用越大。可以通过model.rpn._pre_nms_top_n和model.rpn._post_nms_top_n限制候选框数量,训练时分别设为2000和1000,推理时设为1000和300。另外,把model.roi_heads.detections_per_img从默认的100降到50也能省显存。
4.3 DETR收敛慢与查询向量可视化
DETR训练慢是出了名的,官方说500个epoch,实际用Deformable DETR也要50个epoch。如果你发现训练了20个epoch mAP还是0,先别慌,DETR前期就是这样的。检查几个点:学习率是不是太大了(DETR对学习率敏感,backbone用1e-5,Transformer用1e-4比较稳)、权重衰减是不是设了1e-4、梯度裁剪是不是开了(DETR必须开梯度裁剪,max_norm=0.1)。另外,DETR的损失权重也要调,分类损失、L1损失、GIoU损失的默认权重是1、5、2,如果小目标多,可以适当加大L1损失的权重。
DETR的查询向量(object queries)可视化是个很有意思的调试手段。把训练好的模型解码器的查询向量拿出来,用PCA降到2维画散点图,你会看到每个查询向量在特征空间里聚成一簇,每一簇对应一种空间位置或目标类型。如果所有查询向量都挤在一起,说明模型没学好,可能是学习率太大或者训练不够。如果查询向量分布很散但mAP很低,可能是匈牙利匹配出了问题,检查一下匹配代价里分类概率的权重是不是太小。
4.4 边缘部署:RK3588与树莓派上的YOLO优化
把YOLO部署到RK3588或树莓派上,最大的挑战是算力有限。RK3588有6TOPS的NPU,但只支持INT8量化,FP16支持有限。树莓派4B只有CPU,跑YOLOv8n大概2-3 FPS,基本没法做实时检测。我的经验是:RK3588上用RKNN-Toolkit2把ONNX转成RKNN模型,量化时用训练集的子集做校准,INT8量化后精度损失通常在1-2个mAP以内。转换命令:
python convert.py yolov8s.onnx rk3588 i8 yolov8s.rknn树莓派上如果非要用YOLO,建议用NCNN或ONNX Runtime,模型选YOLOv8n,输入分辨率降到320,能跑到5-8 FPS。如果要做视频监控,建议用间隔抽帧的方式,每3帧检测一次,中间帧用跟踪算法(如ByteTrack)补上,这样整体帧率能到15 FPS以上。边缘部署监控误检率高的问题,通常是因为训练数据和实际场景分布不一致,建议在目标场景下采集一些数据做微调,哪怕只有几百张图,效果也会明显提升。
5. 模型改进与前沿方向
5.1 YOLO与Transformer结合:从YOLOv7到RT-DETR
YOLO和Transformer的结合是近两年的热门方向。早期的工作如YOLOv7中的E-ELAN结构其实已经借鉴了Transformer的多分支设计思想,但真正把Transformer塞进YOLO的是YOLOv8之后的版本。YOLOv9引入了可编程梯度信息(PGI)和广义高效层聚合网络(GELAN),虽然不是纯Transformer,但设计理念受到了Transformer的启发。YOLOv10则直接用了Transformer风格的注意力模块,同时去掉了NMS。
RT-DETR是百度提出的实时DETR,在V100上达到100+ FPS,精度超过同级别的YOLO。它的核心改进是高效混合编码器(Efficient Hybrid Encoder)和IoU感知查询选择(IoU-aware Query Selection)。高效混合编码器把多尺度特征先做尺度内交互(用注意力),再做跨尺度融合(用卷积),比原始DETR的编码器快很多。IoU感知查询选择在解码器初始化时,从编码器输出里挑出IoU最高的top-k个特征作为查询向量的初始位置,而不是用随机初始化的可学习查询,这样收敛更快。如果你要做实时检测又想用Transformer架构,RT-DETR是目前最值得尝试的方案。
5.2 Mamba YOLO与多模态检测
Mamba是2023年底提出的状态空间模型(SSM),在长序列建模上比Transformer更高效。Mamba YOLO把YOLO的骨干网络换成Mamba块,在保持线性复杂度的同时获得了全局感受野。我复现过一版Mamba YOLO,在COCO上比同规模的YOLOv8s高1-2个mAP,推理速度差不多。但Mamba的CUDA算子编译比较麻烦,需要装causal-conv1d和mamba-ssm,而且对PyTorch版本有要求,建议用PyTorch 2.0以上。
多模态目标检测是另一个热点。YOLO图文多模态的做法通常是把文本编码器(如CLIP的文本分支)和图像特征做对齐,然后用文本嵌入作为类别分类器,实现开放词汇检测。具体来说,把类别名(如“person”、“car”)送进CLIP文本编码器得到文本嵌入,图像特征经过YOLO骨干后和文本嵌入做点积,得到每个位置的类别得分。这样训练时只学框回归,分类靠CLIP的零样本能力,新增类别不需要重新训练。我实测下来,在自定义数据集上零样本mAP能到20-30%,如果加少量标注做微调,能到40%以上。
5.3 损失函数改进:从CIoU到WIoU
YOLO的框回归损失从IoU发展到GIoU、DIoU、CIoU,再到最新的WIoU(Wise IoU)。CIoU同时考虑重叠面积、中心点距离和长宽比,但有个问题:对于小目标,长宽比的惩罚项会主导损失,导致定位不准。WIoU引入动态非单调聚焦机制,根据锚框的质量动态调整梯度增益,质量差的锚框获得更大的梯度,质量好的锚框梯度被抑制。在电力红外数据集上,我把CIoU换成WIoU后,小目标的mAP提升了约3个百分点。WIoU的代码实现很简单,在YOLOv8的loss.py里把bbox_iou函数的CIoU改成WIoU即可,具体实现可以参考WIoU的官方repo。
分类损失方面,YOLOv8默认用BCEWithLogitsLoss,对于类别不平衡的数据集,可以换成Focal Loss。Focal Loss通过调制因子降低易分类样本的权重,让模型聚焦难样本。在校园检测场景中,如果“person”类别远多于其他类别,用Focal Loss能明显提升稀有类别的召回率。实现时把BCEWithLogitsLoss替换为FocalLoss(alpha=0.25, gamma=2.0),注意alpha要根据类别频率调整,频率低的类别给更大的alpha。
6. 数据集构建与标注经验
6.1 数据集格式转换:VOC、COCO与YOLO互转
做目标检测最耗时的环节不是训练,而是数据准备。VOC、COCO、YOLO三种格式互转是家常便饭。VOC是XML,每个文件对应一张图,包含filename、size、object列表,每个object有name、bndbox(xmin, ymin, xmax, ymax)。COCO是单个json,包含images、annotations、categories。YOLO是每张图一个txt,每行class_id cx cy w h,坐标归一化。
VOC转YOLO的脚本核心逻辑:读XML,取图像的宽高,对每个object计算归一化坐标,写入txt。注意类别id要从0开始连续编号,和data.yaml里的names顺序一致。COCO转YOLO类似,但要注意COCO的bbox是[x, y, width, height],需要先转成[xmin, ymin, xmax, ymax]再归一化。YOLO转VOC就是反过来,把归一化坐标乘回图像宽高,写入XML。我写过一个通用转换脚本,支持三种格式任意互转,核心就是解析和重写,代码量不大但很实用。
6.2 数据增强策略:从Mosaic到MixUp
YOLOv4引入的Mosaic增强是提升小目标检测效果的利器。Mosaic把4张图拼成一张,每张图随机缩放、随机裁剪、随机排布,这样一张图里包含了4张图的目标,batch size等效扩大了4倍,而且小目标出现的频率更高。YOLOv8默认开了Mosaic,但最后10个epoch会关掉,因为Mosaic的拼接边缘会引入不自然的上下文,影响最终精度。我实测下来,Mosaic对小目标mAP的提升在2-3个百分点,但对大目标有轻微负面影响,如果数据集里大目标为主,可以调低Mosaic的概率。
MixUp是另一类增强,把两张图按比例混合,标签也按比例混合。MixUp能提升模型的泛化能力,但对目标检测来说,混合后的图里目标边界模糊,框回归会受影响。我的经验是MixUp适合分类任务,检测任务里用Mosaic加随机翻转、随机缩放就够了。另外,HSV色彩空间增强对红外数据集没用(红外图是单通道灰度),但对可见光数据集很有效,能提升模型对光照变化的鲁棒性。
6.3 标注工具与质量控制
标注工具我用过LabelImg、CVAT、Roboflow。LabelImg最轻量,适合小数据集,但不支持多人协作。CVAT支持多人协作和自动标注,适合团队使用。Roboflow是在线平台,支持自动标注和增强,但免费版有数据量限制。如果要做实例分割,LabelMe更合适,能画多边形。标注质量控制的关键是制定标注规范:框要贴紧目标边缘,遮挡目标要标可见部分,极小目标(小于10×10像素)建议忽略,因为标注误差太大反而影响训练。
标注完成后要做一致性检查。我通常随机抽10%的图,让另一个人重新标一遍,计算两次标注的IoU,如果平均IoU低于0.85,说明标注规范不清晰,需要重新培训。另外,用脚本检查标注文件里的异常值:框超出图像边界、宽高为0、类别id超出范围、坐标不是归一化值等。这些异常在训练时会导致损失NaN或者mAP异常低,提前检查能省很多调试时间。
7. 模型部署与推理优化
7.1 ONNX导出与TensorRT加速
YOLOv8导出ONNX很简单:
yolo export model=yolov8s.pt format=onnx opset=12 simplify=Truesimplify=True会用onnx-simplifier优化计算图,去掉冗余算子。导出后可以用Netron可视化检查输入输出节点。ONNX模型在CPU上推理用ONNX Runtime,GPU上用TensorRT。TensorRT加速需要先把ONNX转成TensorRT引擎:
trtexec --onnx=yolov8s.onnx --saveEngine=yolov8s.engine --fp16--fp16开启半精度,V100上能提速30-50%,精度损失不到0.5个mAP。如果要做INT8量化,需要提供校准数据集:
trtexec --onnx=yolov8s.onnx --saveEngine=yolov8s_int8.engine --int8 --calib=calibration_data校准数据用训练集的子集(500-1000张)就行,注意校准数据要和实际推理数据分布一致。TensorRT引擎和硬件绑定,换GPU需要重新生成。
7.2 推理后处理:NMS与置信度阈值调优
YOLO的推理输出是原始预测框,需要做NMS过滤重复框。NMS的IoU阈值默认0.45,置信度阈值默认0.25。这两个参数对最终结果影响很大:IoU阈值太低会误删相邻目标,太高会保留重复框;置信度阈值太低会引入大量误检,太高会漏检。我的调参经验是:先固定IoU=0.5,调置信度阈值,看PR曲线,选F1最高的点;然后固定置信度阈值,调IoU,看mAP变化。对于密集场景(如人群检测),IoU阈值要调低到0.3-0.4,因为目标之间重叠严重。对于稀疏场景,IoU可以调到0.5-0.6。
DETR不需要NMS,但需要设置一个置信度阈值来过滤“无目标”预测。DETR的输出是100个预测,每个有类别概率,通常取概率最高的类别,如果最高概率低于阈值(如0.5),就认为该预测是背景。DETR的阈值对结果影响比YOLO小,因为匈牙利匹配已经保证了预测的唯一性。
7.3 边缘设备部署:RK3588与树莓派实战
RK3588部署YOLO的流程:先把PyTorch模型导出ONNX,然后用RKNN-Toolkit2转成RKNN模型,最后用RKNN Runtime在板子上推理。RKNN-Toolkit2的安装比较麻烦,建议用Docker镜像。转换脚本:
from rknn.api import RKNN rknn = RKNN() rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3588') rknn.load_onnx(model='yolov8s.onnx') rknn.build(do_quantization=True, dataset='calibration.txt') rknn.export_rknn('yolov8s.rknn')calibration.txt里是校准图片的路径列表。RK3588的NPU支持INT8,量化后模型大小缩小4倍,推理速度提升2-3倍。实测YOLOv8s在RK3588上能跑到30-40 FPS(INT8),精度损失约1-2个mAP。树莓派4B没有NPU,只能用CPU推理,YOLOv8n在320分辨率下能到5-8 FPS,如果加NCNN优化能到10 FPS左右。树莓派上建议用NCNN,安装pip install ncnn,然后把ONNX转成NCNN格式,推理速度比ONNX Runtime快20-30%。
8. 个人实操体会与建议
踩了这么多坑,我最大的体会是:不要一上来就追求最新最复杂的模型。YOLOv8s在大多数场景下已经足够好,Faster RCNN在精度要求极高的场景下依然能打,DETR适合做研究和多模态扩展。选模型之前先明确三个问题:你的场景对速度的要求是多少FPS?对精度的要求是多少mAP?你的算力预算有多少?这三个问题想清楚了,选型基本就定了。
数据质量比模型结构重要得多。我见过太多人花几周调模型结构,mAP只涨了0.5个点,但花两天清洗数据、修正标注,mAP涨了5个点。标注规范要提前定好,标注过程中要定期抽查,训练前要用脚本检查异常值。数据增强不要堆太多,Mosaic加随机翻转、随机缩放就够了,堆太多增强反而会拖慢训练、降低精度。
训练过程中要养成看日志的习惯。损失曲线、学习率曲线、mAP曲线、混淆矩阵、PR曲线,这些都要看。损失震荡可能是学习率太大,mAP不涨可能是过拟合或者欠拟合,混淆矩阵能看出哪些类别容易混。我通常每10个epoch保存一次权重,训练完把所有权重在验证集上跑一遍,选mAP最高的那个,而不是直接用最后一个epoch的权重。
最后说一个容易被忽略的点:推理后处理的调参。很多人训练完模型直接就用默认阈值推理,结果误检漏检一大堆。花半小时调一下置信度阈值和NMS的IoU阈值,效果可能比重新训练一个模型还好。PR曲线是最好的调参工具,找到F1最高的点,那个阈值就是最优的。如果场景对误检容忍度低(如安防监控),就把置信度阈值调高;如果对漏检容忍度低(如自动驾驶),就把阈值调低。