Grid-RCNN垃圾车目标检测,这个名字一眼就能拆出整套技术路线:ResNet101骨干网络、FPN特征金字塔、GN归一化检测头、2x训练计划,再加上COCO预训练权重做底座。这套组合要解决的真实问题,是在城市环卫场景中准确识别垃圾车,支撑清运频次统计、中转站进出场管理、违规倾倒取证这些业务。说白了,就是让摄像头替人盯着垃圾车,该记录就记录,该报警就报警。适合正在做目标检测落地项目、或者准备用Grid R-CNN做细分场景识别的开发者参考,对熟悉Faster R-CNN但没碰过Grid系方法的读者也比较友好。我这次是把这套模型应用在“某市环卫车辆视觉分析项目X”里,只检测“垃圾车”这一个类别,训练数据来自多个垃圾中转站的固定机位和道路卡口抓拍。整个过程走下来,踩了不少坑,也总结了一套可以复用的调参和排错方法,下面完整拆开讲。1. 项目目标与模型选型:为什么是Grid-RCNN这套组合1.1 垃圾车检测要解决的实际业务问题垃圾车检测和通用的车辆检测不太一样。垃圾车不是一个长得“规整”的类别,压缩式垃圾车有圆润的后厢体,勾臂式垃圾车带液压钩臂和可卸货箱,自卸式垃圾车又和普通渣土车神似,更别说还有桶装运输车、摆臂式垃圾车这些细分车型。它们和厢式货车、混凝土搅拌车在外观上有大量重叠,这就让“分类”和“定位”同时变得困难。业务侧的需求也很明确。中转站要统计每天进出场的垃圾车数量,核对清运频次,这需要模型对进站车道上的车辆做到不漏检、不误检;城市管理部门要在乱倒点位的监控画面上识别嫌疑车辆,这时候不仅要求检出,还要求框得稳,方便截图取证;还有一类场景是道路巡查,移动摄像头扫过路边停放的垃圾车,需要在快速运动、视角变化的情况下保持稳定输出。我接触的这类项目还有一个共性:摄像头视野很宽,一辆垃圾车可能出现在画面远端,只占几十个像素;也可能在近端占据大半张图。车辆还有遮挡、夜间、雨雾等干扰。这些约束直接影响了模型选型——需要一个对“定位精度”和“多尺度”都有保障的检测框架。1.2 Grid-RCNN比Faster R-CNN强在哪Grid R-CNN这个名字里的Grid,指的是目标框内部划分的网格点。它和Faster R-CNN同源,仍然用RPN产生候选框,但在检测头部分做了一个关键改动:不再把边界框定位完全交给回归分支,而是把候选框对应的目标区域划分成3x3的网格点,用分类的方式逐点判断“这个网格点是否落在目标内部”。打个比方。Faster R-CNN的框回归像是一把尺子量长度,一次量完四个边;Grid R-CNN则是在目标内部钉了九根钉子,每根钉子判断自己在不在目标里,最后用九根钉子的结果联合投票,把边界修正出来。多点多信息的联合约束,比单次回归更抗干扰,尤其适合垃圾车这种“长宽比跨度大、边界不整齐”的目标。Grid R-CNN的训练引入了Grid Loss,本质是一个交叉熵损失,监督网格点位置的预测。推理阶段则用Grid Voting机制,把相邻网格点的预测分数加权融合,对候选框的边界做精细微调。这套机制带来的直接收益是定位精度的提升,尤其是AP75这种高IoU阈值下的指标,比Faster R-CNN有明显优势。垃圾车检测这种场景,业务方经常要求框必须贴合车身,不能把旁边的绿化带也框进去当证据,Grid R-CNN的定位优势正好用得上。1.3 R101、FPN、GN-head、2x、COCO这些组合参数到底是什么意思标题里的五个关键词,每一个都是可以展开讲一整节的配置决策,我用一张表先把关系理清。配置项含义在本项目中的作用R101ResNet101骨干网络比ResNet50多一半层数,车身纹理细节提取能力更强FPN特征金字塔网络融合多层特征,解决远端小目标和近端大目标共存问题GN-headGroupNorm归一化的检测头小batch训练下比BatchNorm更稳定,不依赖batch统计量2x24轮训练计划比标准1x配置多训练一倍,让RPN和Grid head充分收敛COCOCOCO预训练权重用80类通用检测知识做迁移学习,显著加速收敛R101的选择逻辑很直接:垃圾车检测允许一定延迟(单张图几十到几百毫秒都可接受),但对精度很敏感,所以选深度更深的骨干网络。R101比R50在COCO上大约能多1-2个点的AP,放到细分场景里,配合好的数据,提升会更明显。代价是显存占用和推理时间的增加,后面会单独讲怎么平衡。FPN是处理多尺度的标配。垃圾车在监控画面里近大远小,FPN的P2到P5层分别覆盖不同尺度,让高层语义和低层细节互补。Grid R-CNN的RoI提取会从FPN对应层级取特征,没有FPN,远端的小垃圾车基本就废了。GN-head是一个容易被忽视但很重要的选择。检测模型的head通常很浅,如果head里用BatchNorm,训练时batch size又小(单卡跑2张图很常见),BN的均值方差估计就会抖动,影响收敛。GroupNorm把通道分成若干组做归一化,不依赖batch内其他样本,单卡、小batch都能稳定训练。我在项目里head和backbone都用了GN,训练过程明显比之前用BN的版本平稳。2x是训练计划的名词。在MMDetection的约定里,1x是12轮,2x是24轮,学习率分别在16轮和22轮衰减10倍。检测任务的收敛比图像分类慢得多,RPN要先稳定,RoI head才能好好学,Grid head的网格点分类又比普通分类更细,所以2x是Grid R-CNN的标准配置。实测下来,24轮对垃圾车这种类别差异较大的目标确实比12轮效果好。COCO预训练权重是整个项目的“起跑线”。直接用ImageNet分类权重初始化检测模型,RPN和RoI head基本是从零学,收敛很慢;用COCO的检测权重初始化,backbone和FPN已经学会了通用的边缘、纹理、形状特征,只需要微调,同时检测头因为是新类别,仍然是随机初始化的。这块下一节详细说。2. 数据集准备:从现场采集到COCO格式标注2.1 垃圾车检测难点与数据采集方案数据是垃圾车检测项目里最容易被低估的一环。我第一次做的时候,以为只要拉个几百张图标一标就行,结果验证集上AP很高,一上现场就翻车,原因就是数据分布没控制好。采集阶段要覆盖足够的维度。我整理的采集清单包括四个维度:天气(晴天、阴天、雨天、雾天)、时段(白天、黄昏、夜间红外或补光)、视角(中转站高机位俯视、卡口平视、移动巡查斜视)、车型(压缩式、勾臂式、自卸式、桶装式)。每个维度最好都有不低于100张的样本量。数量上,我的经验是单类别检测至少准备5000张以上图片,垃圾车实例不低于8000个。如果某个车型特别少,比如自卸式只有200个实例,宁可先不把它作为关注重点,也不要强行加进去干扰模型,或者单独给这个少样本类别做数据扩充。清洗环节同样关键。监控视频抽帧出来的图片会有大量连续近似帧,直接全量标注会造成训练集高度冗余,模型对某个固定视角过拟合。我按帧间隔抽帧,确保相邻样本的画面差异足够大。另外,目标占比过小(比如小于画面面积0.1%)的图片、严重运动模糊的图片、双曝光异常的图片,都直接删掉,这些样本只会增加训练噪声。2.2 COCO格式组织与标注规范整个项目的数据按COCO格式组织。目录结构建议这样:data/garbage_truck/ ├── annotations/ │ ├── instances_train2023.json │ └── instances_val2023.json ├── train2023/ ├── val2023/ └── test2023/COCO JSON里最核心的三个字段是images(图片信息)、annotations(标注信息)、categories(类别表)。本项目类别表只有一项:"categories": [{"id": 1, "name": "garbage_truck"}]标注时用开源标注工具画框即可,导出COCO格式。有几个规范必须遵守:bbox字段必须是[x, y, width, height]格式,且是浮点数,不要用整数截断目标被截断超过一半时,如果无法判断整体轮廓,建议标注为ignore而不是正常正样本,避免给Grid head的网格点生成造成混乱两个垃圾车紧挨着时,框不要互相包含,哪怕目标被部分遮挡,也要尽量框出可见完整轮廓我在项目里发现一个很影响精度的细节:标注框是否贴合车身对Grid R-CNN的影响比对Faster R-CNN更大,因为Grid Loss会针对框内网格点做监督,框边缘松垮,等于给网格点的标签加了噪声。所以标注时宁可多花时间“贴边”,也不要随手拉大框。2.3 训练集/验证集划分与类别平衡划分数据集不能按图片随机打乱。监控视频里同一个机位连续拍摄的画面高度相似,随机划分会让同一场景的图片同时出现在训练集和验证集,验证分数虚高,部署到新场景立刻现原形。我采用的是按“场景+时段”分组划分。比如中转站A的白天画面、中转站B的夜间画面,各自作为一个分组单元,保证同一分组的图片不会跨集合出现。比例控制在训练集占80%、验证集占20%左右。验证集的设计是另一个容易忽视的坑。如果验证集里全是好检的正面大目标,模型AP会很好看,但上线后一点参考价值都没有。我额外构造了一个“困难验证子集”,专门收录夜间、遮挡、远端小目标、雨天反光的图片,单独跑一轮评估。项目汇报时,这个困难子集的指标比总AP更能说明模型能不能真正干活。3. 模型训练实现:配置解析与完整流程3.1 环境搭建与依赖版本选择环境方面,我用的是PyTorch + MMDetection这套开源工具链。版本匹配是第一个坑,PyTorch版本和CUDA版本不对,编译出的mmcv就报错,这一类问题占了环境问题的大半。# 先安装与本地CUDA匹配的PyTorch pip install torch==1.13.1 torchvision==0.14.1 # 再安装对应编译版mmcv-full pip install mmcv-full==1.7.2 # 最后安装mmdet pip install mmdet==2.28.2注意安装顺序:先装PyTorch,再装mmcv-full,最后装mmdet。如果mmcv是源码编译,耗时会比较久,建议直接装预编译的wheel版本,省心很多。装完以后先跑一个命令验证:python -c "import mmdet; print(mmdet.__version__)"能打印出版本号,环境基本就通了。本项目在单张24G显存的显卡上训练,显存吃紧,后面会讲到显存优化的具体办法。3.2 模型配置与关键超参解析MMDetection的配置是四段式的:模型结构、数据集、训练计划、运行时配置。我在基础配置上做了针对垃圾车检测的改动,核心片段如下:model = dict( type='GridRCNN', backbone=dict( type='ResNet', depth=101, frozen_stages=1, # 冻结stem和第一个stage,减少小数据扰动 norm_cfg=dict(type='GN', num_groups=32, requires_grad=True) ), neck=dict(type='FPN', in_channels=[256, 512, 1024, 2048], out_channels=256), rpn_head=dict( type='RPNHead', anchor_generator=dict( type='AnchorGenerator', scales=[4, 8, 16, 32], ratios=[0.5, 1.0, 2.0], strides=[4, 8, 16, 32, 64] ) ), roi_head=dict( type='GridRoIHead', bbox_head=dict( type='Shared2FCBBoxHead', num_classes=1, # 关键改动:单类别 reg_decoded_bbox=True ), grid_head=dict( type='GridHead', num_classes=1, # 关键改动:单类别 num_points=9, # 3x3网格点 norm_cfg=dict(type='GN', num_groups=32) ) ) )几个关键参数的说一下我的调法。num_classes必须改成1,否则模型输出维度是80+1,类别空间错乱,训练loss不降。anchor的ratios对垃圾车很关键。垃圾车常见的长宽比其实偏大,压缩式垃圾车约1:1.8到1:2.2,勾臂车带货箱时可以达到1:2.5以上。默认的[0.5, 1.0, 2.0]对这类扁长目标覆盖不够,我在ratios里加了0.33和3.0,让anchor能拉长到1:3左右,实测对压缩式垃圾车的召回升了大约2个点。frozen_stages设置为1,意思是冻结backbone最前面的低层特征。小数据集上低层特征已经足够通用,冻结它们可以显著减少显存和计算量,同时避免早期训练扰动底层的稳定特征。如果数据量足够大(大于2万张),可以改成0做全量微调。数据处理部分,我给训练pipeline增加了多尺度训练:train_pipeline = [ dict(type='LoadImageFromFile'), dict(type='LoadAnnotations', with_bbox=True), dict(type='Resize', img_scale=[(800, 800), (1333, 800)], multiscale_mode='range', keep_ratio=True), dict(type='RandomFlip', flip_ratio=0.5), dict(type='Normalize', mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375]), dict(type='DefaultFormatBundle'), dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']) ]多尺度训练让模型在不同大小下都见过垃圾车,对远端小目标和近端大目标都有更强的适应力。代价是每个epoch的耗时增加了大概30%,但精度收益明确,值得花。3.3 预训练权重加载与迁移学习策略预训练权重是这个项目启动的关键。我直接加载了COCO上训练好的同名模型权重文件。加载时的核心逻辑是:backbone和FPN的权重会完整复用,因为它们的层结构和COCO模型完全一致;roi_head里的bbox_head和grid_head因为num_classes从80改成了1,最后一层分类器的权重shape不匹配,会自动跳过并随机初始化。load_from = 'checkpoints/grid_rcnn_r101_fpn_gn_head_2x_coco.pth'启动训练后,第一件事是看日志里是否有如下提示:“Weights of xxx not used”:某些层没被加载,通常是neck或head,需要检查“The shape is not matched in xxx”:这是正常的,说明类别层被重新初始化了我的迁移策略是:先让backbone的浅层冻结,检测头从头学,等训练到第8-10轮,模型已经对垃圾车类别有一定的响应后,再把frozen_stages改为0,用一个较低的学习率(0.0005左右)做几个epoch的“解冻微调”。这个操作在项目里大约给最终AP带来了1-2个点的提升。学习率这里有一个必须说清楚的缩放逻辑。COCO标准配置是8卡、batch size 16、lr为0.02。单卡训练时batch size只有2,按线性缩放原则,lr大概在0.0025到0.005之间。我实测下来,lr取0.005配合500轮warmup比较稳,lr取0.01就容易在前几轮loss爆炸。这就是典型的小batch大lr事故。3.4 训练启动与过程监控配置写好以后,启动训练的命令很简单:python tools/train.py configs/garbage_truck_grid_rcnn.py训练过程中重点盯的指标包括:loss_rpn_cls(RPN分类损失)、loss_rpn_loc(RPN回归损失)、loss_grid(网格点分类损失)、loss_bbox(边界框回归损失)。我记录了本项目一次典型训练过程的loss走势:EpochRPN Cls LossGrid Loss备注10.451.80warmup结束,整体下降50.180.62检测头开始稳定100.110.38AP50接近70160.080.29学习率衰减节点220.060.21第二次衰减240.050.18达到可用水平一个非常值得注意的现象是:第16轮学习率衰减后,AP50和AP75会同时跳涨大约2-3个点。这是检测模型训练的正常“衰减红利”,所以一定不要提前终止训练,哪怕看起来第20轮已经收敛,也要让完整的2x计划跑完。训练中我每2个epoch保存一次checkpoint,用验证集跑一次bbox评估,记录最优权重。这里建议开启MMDetection的checkpoint机制里的“保留最优模型”选项,而不是手动挑选,避免training过程中间那个过拟合的节点被覆盖。4. 训练效果评估与推理部署4.1 评价指标解读:mAP、AP50、AP75与业务指标目标检测的标准评价指标是COCO风格的mAP,即IoU阈值从0.5到0.95每隔0.05取一个档位,共10个档位求平均。这个指标非常严格,对框的位置精度要求很高。此外还有两个更直观的指标:AP50(IoU阈值0.5时的平均精度)和AP75(IoU阈值0.75时的平均精度)。对垃圾车检测业务来说,这两类指标各有用途。做“车辆进出场统计”的业务,不需要框特别精确,AP50高就够用;做“违规倾倒取证”的业务,框必须贴合车身,AP75才是关键指标。我项目里最后训练出的模型,在独立测试集上AP50约88%,AP75约72%,mAP约52%。这个成绩属于单类别、场景相对聚焦的正常水平。除了整体mAP,我还特别关注AP_s(小目标)、AP_m(中目标)、AP_l(大目标)三个细分指标。垃圾车检测里面,远端的小目标AP_s往往只有大目标AP_l的三分之一,这是正常的,但如果AP_s低于10,说明模型基本检不出远处目标,需要从数据增强、输入分辨率上想办法,而不是盲目加训练时长。4.2 模型推理与可视化检测训练完成后,推理用MMDetection提供的高层API就能直接跑:from mmdet.apis import init_detector, inference_detector config_path = 'configs/garbage_truck_grid_rcnn.py' checkpoint_path = 'checkpoints/best_bbox_mAP_epoch_24.pth' model = init_detector(config_path, checkpoint_path, device='cuda:0') results = inference_detector(model, 'data/garbage_truck/test2023/000001.jpg') model.show_result( 'data/garbage_truck/test2023/000001.jpg', results, out_file='output/000001_result.jpg', score_thr=0.5 )init_detector的推理输出包含一个列表,第一个元素是bbox的Nx5数组(N为检测框数量,每行是[x1, y1, x2, y2, score])。我在项目里写了一个简单的后处理,把score_thr设为0.5用于日常巡检,0.3用于夜间低置信度场景的召回,0.7用于取证场景减少误报。这个阈值不能只看总AP来定,最好结合业务方对“漏报”和“误报”的容忍度来调。4.3 模型导出与工程部署要点训练只是第一步,部署才是落地的关键。如果业务方要求的是实时处理视频流,PyTorch模型直接跑吞吐量往往不够,需要导出加速。R101的PyTorch模型文件大约在300MB左右,导出为FP16的ONNX后可以减半。这里有一个Grid R-CNN特有的坑:grid_head在推理时的网格点收集和投票操作有自定义的逻辑,直接转ONNX会碰到不支持的算子。我的做法是先把bbox_head单独导出,grid的投票逻辑保留在Python侧做后处理,这样既兼顾了精度又降低了导出难度。如果整套都转TensorRT,建议用支持动态网格点操作的版本,先跑通精度再谈加速。如果业务方对延迟不敏感(比如夜间巡检、事后分析),直接用PyTorch服务化也够用。单张24G显存卡跑R101推理,batch size设为8时,每秒能处理大约20-30张图,完全够拿来做视频抽帧分析。5. 高频踩坑与排查笔记5.1 训练Loss异常与不收敛的排查这是所有检测项目里最常见的故障。我遇到过的场景和排查办法整理成下表:现象可能原因排查操作loss直接变NaN学习率过大或标注数据异常降低lr到0.0025重试;检查JSON中bbox是否越界、宽高是否为负数loss不下降预训练权重未正确加载启动时看“The shape is not matched”提示;用单张图跑一次前向验证输出维度grid_loss不降标注框边缘严重不贴合GT抽查标注可视化,重新修整松垮的框;检查是否有多边形但不带segmentation的脏数据RPN loss正常但整体AP很低anchor设置不合理根据目标长宽比分布重新设计ratios;打印每层anchor的匹配数一个有效的通用排查方法是:先用原始COCO配置、80类别、原始数据跑5轮,确认代码链路是通的;然后再改成单类别、换自己的数据。对比两次行为,能快速定位问题是出在数据侧还是配置侧。5.2 小目标与遮挡场景漏检的优化垃圾车在监控远端只有几十个像素,漏检是这类项目的“老大难”。我实测有效的优化手段有四个。第一,调整输入分辨率。把测试和训练的长边从默认的1333提到1600,短边从800提到1000,小目标检测会明显改善。代价是显存涨得厉害,需要配合梯度累积或者减小batch size。我这里显存不够后,把samples_per_gpu降到1,同时设置了accumulative_counts=8做梯度累积,效果等效于batch size 8。第二,优化anchor设计。小目标主要在FPN的P2层(stride=4)上匹配,如果P2层的anchor scale太小,小目标就没有合适的anchor匹配。我把anchor scales增加到[2, 4, 8, 16, 32],覆盖更小的目标。第三,滑窗切图推理。对特别大的监控画面(比如4K),直接缩放会导致小垃圾车更小。我是把大图切成上下两半分别推理,再用NMS合并结果。这个方法简单粗暴但非常有效,AP_s能从15提到25以上。第四,遮挡目标用ignore标注。前面说过,框内混杂遮挡物会让grid head学到错误的网格点分布,导致同一个目标时而检出时而丢。把这些目标标成ignore后,模型不再被迫拟合它们,干净样本上的精度反而提升了。5.3 显存溢出与训练加速技巧R101加FPN在24G显存上也不能太任性。batch size设为2时大概占用11G左右,如果开多尺度训练会涨到14G。显存溢出的标准处理套路是:samples_per_gpu降到1,开启梯度累积;关闭不用的测试增强;把workers_per_gpu从默认4降到2,减少数据加载时的CPU内存挤占。还有一个容易忽略的点:如果只是训练,可以把验证阶段的可视化输出关掉,只保留指标计算,这样每个验证epoch能省不少显存和计算。我在项目里把eval时的show标志全部关闭,验证时间从20分钟降到了8分钟。速度方面,如果业务从“离线分析”变成“实时接入”,R101就有点吃力了。我的建议是先用批次推理把吞吐提上去(batch size 8到16),再考虑换成R50或对head做剪枝。不过要留意,R50相比R101通常要损失2-3个点的mAP,如果业务对精度要求极高,优先保精度,用多卡分流的思路扛吞吐,而不是砍模型。最后再分享一点个人体会。Grid R-CNN这套模型在垃圾车这种“形状不规整、长宽比跨度大”的目标上,确实比普通Faster R-CNN定位更稳,尤其是AP75的提升很明显。但真正决定项目成败的往往不是模型本身,而是数据处理和验证集设计。我第一次拿到项目时,训练集里压缩式垃圾车占了八成,验证集又恰好全是勾臂式车,结果训练曲线一路高歌,上线后第一天就漏检了一大半勾臂车。后来我把数据按场景和车型重新均衡,又专门拉了一个困难样本验证集,模型才算真正能打。如果你也要做类似的车种检测,我的建议是:先把数据分布彻底看清,把验证集设计得足够“刁钻”,再回头决定用R101还是R50、用2x还是1x。工具链已经很成熟了,模型选型反而是整个项目里最不花时间的环节。
Grid-RCNN垃圾车目标检测,这个名字一眼就能拆出整套技术路线:ResNet101骨干网络、FPN特征金字塔、GN归一化检测头、2x训练计划,再加上COCO预训练权重做底座。这套组合要解决的真实问题,是在城市环卫场景中准确识别垃圾车,支撑清运频次统计、中转站进出场管理、违规倾倒取证这些业务。说白了,就是让摄像头替人盯着垃圾车,该记录就记录,该报警就报警。适合正在做目标检测落地项目、或者准备用Grid R-CNN做细分场景识别的开发者参考,对熟悉Faster R-CNN但没碰过Grid系方法的读者也比较友好。我这次是把这套模型应用在“某市环卫车辆视觉分析项目X”里,只检测“垃圾车”这一个类别,训练数据来自多个垃圾中转站的固定机位和道路卡口抓拍。整个过程走下来,踩了不少坑,也总结了一套可以复用的调参和排错方法,下面完整拆开讲。1. 项目目标与模型选型:为什么是Grid-RCNN这套组合1.1 垃圾车检测要解决的实际业务问题垃圾车检测和通用的车辆检测不太一样。垃圾车不是一个长得“规整”的类别,压缩式垃圾车有圆润的后厢体,勾臂式垃圾车带液压钩臂和可卸货箱,自卸式垃圾车又和普通渣土车神似,更别说还有桶装运输车、摆臂式垃圾车这些细分车型。它们和厢式货车、混凝土搅拌车在外观上有大量重叠,这就让“分类”和“定位”同时变得困难。业务侧的需求也很明确。中转站要统计每天进出场的垃圾车数量,核对清运频次,这需要模型对进站车道上的车辆做到不漏检、不误检;城市管理部门要在乱倒点位的监控画面上识别嫌疑车辆,这时候不仅要求检出,还要求框得稳,方便截图取证;还有一类场景是道路巡查,移动摄像头扫过路边停放的垃圾车,需要在快速运动、视角变化的情况下保持稳定输出。我接触的这类项目还有一个共性:摄像头视野很宽,一辆垃圾车可能出现在画面远端,只占几十个像素;也可能在近端占据大半张图。车辆还有遮挡、夜间、雨雾等干扰。这些约束直接影响了模型选型——需要一个对“定位精度”和“多尺度”都有保障的检测框架。1.2 Grid-RCNN比Faster R-CNN强在哪Grid R-CNN这个名字里的Grid,指的是目标框内部划分的网格点。它和Faster R-CNN同源,仍然用RPN产生候选框,但在检测头部分做了一个关键改动:不再把边界框定位完全交给回归分支,而是把候选框对应的目标区域划分成3x3的网格点,用分类的方式逐点判断“这个网格点是否落在目标内部”。打个比方。Faster R-CNN的框回归像是一把尺子量长度,一次量完四个边;Grid R-CNN则是在目标内部钉了九根钉子,每根钉子判断自己在不在目标里,最后用九根钉子的结果联合投票,把边界修正出来。多点多信息的联合约束,比单次回归更抗干扰,尤其适合垃圾车这种“长宽比跨度大、边界不整齐”的目标。Grid R-CNN的训练引入了Grid Loss,本质是一个交叉熵损失,监督网格点位置的预测。推理阶段则用Grid Voting机制,把相邻网格点的预测分数加权融合,对候选框的边界做精细微调。这套机制带来的直接收益是定位精度的提升,尤其是AP75这种高IoU阈值下的指标,比Faster R-CNN有明显优势。垃圾车检测这种场景,业务方经常要求框必须贴合车身,不能把旁边的绿化带也框进去当证据,Grid R-CNN的定位优势正好用得上。1.3 R101、FPN、GN-head、2x、COCO这些组合参数到底是什么意思标题里的五个关键词,每一个都是可以展开讲一整节的配置决策,我用一张表先把关系理清。配置项含义在本项目中的作用R101ResNet101骨干网络比ResNet50多一半层数,车身纹理细节提取能力更强FPN特征金字塔网络融合多层特征,解决远端小目标和近端大目标共存问题GN-headGroupNorm归一化的检测头小batch训练下比BatchNorm更稳定,不依赖batch统计量2x24轮训练计划比标准1x配置多训练一倍,让RPN和Grid head充分收敛COCOCOCO预训练权重用80类通用检测知识做迁移学习,显著加速收敛R101的选择逻辑很直接:垃圾车检测允许一定延迟(单张图几十到几百毫秒都可接受),但对精度很敏感,所以选深度更深的骨干网络。R101比R50在COCO上大约能多1-2个点的AP,放到细分场景里,配合好的数据,提升会更明显。代价是显存占用和推理时间的增加,后面会单独讲怎么平衡。FPN是处理多尺度的标配。垃圾车在监控画面里近大远小,FPN的P2到P5层分别覆盖不同尺度,让高层语义和低层细节互补。Grid R-CNN的RoI提取会从FPN对应层级取特征,没有FPN,远端的小垃圾车基本就废了。GN-head是一个容易被忽视但很重要的选择。检测模型的head通常很浅,如果head里用BatchNorm,训练时batch size又小(单卡跑2张图很常见),BN的均值方差估计就会抖动,影响收敛。GroupNorm把通道分成若干组做归一化,不依赖batch内其他样本,单卡、小batch都能稳定训练。我在项目里head和backbone都用了GN,训练过程明显比之前用BN的版本平稳。2x是训练计划的名词。在MMDetection的约定里,1x是12轮,2x是24轮,学习率分别在16轮和22轮衰减10倍。检测任务的收敛比图像分类慢得多,RPN要先稳定,RoI head才能好好学,Grid head的网格点分类又比普通分类更细,所以2x是Grid R-CNN的标准配置。实测下来,24轮对垃圾车这种类别差异较大的目标确实比12轮效果好。COCO预训练权重是整个项目的“起跑线”。直接用ImageNet分类权重初始化检测模型,RPN和RoI head基本是从零学,收敛很慢;用COCO的检测权重初始化,backbone和FPN已经学会了通用的边缘、纹理、形状特征,只需要微调,同时检测头因为是新类别,仍然是随机初始化的。这块下一节详细说。2. 数据集准备:从现场采集到COCO格式标注2.1 垃圾车检测难点与数据采集方案数据是垃圾车检测项目里最容易被低估的一环。我第一次做的时候,以为只要拉个几百张图标一标就行,结果验证集上AP很高,一上现场就翻车,原因就是数据分布没控制好。采集阶段要覆盖足够的维度。我整理的采集清单包括四个维度:天气(晴天、阴天、雨天、雾天)、时段(白天、黄昏、夜间红外或补光)、视角(中转站高机位俯视、卡口平视、移动巡查斜视)、车型(压缩式、勾臂式、自卸式、桶装式)。每个维度最好都有不低于100张的样本量。数量上,我的经验是单类别检测至少准备5000张以上图片,垃圾车实例不低于8000个。如果某个车型特别少,比如自卸式只有200个实例,宁可先不把它作为关注重点,也不要强行加进去干扰模型,或者单独给这个少样本类别做数据扩充。清洗环节同样关键。监控视频抽帧出来的图片会有大量连续近似帧,直接全量标注会造成训练集高度冗余,模型对某个固定视角过拟合。我按帧间隔抽帧,确保相邻样本的画面差异足够大。另外,目标占比过小(比如小于画面面积0.1%)的图片、严重运动模糊的图片、双曝光异常的图片,都直接删掉,这些样本只会增加训练噪声。2.2 COCO格式组织与标注规范整个项目的数据按COCO格式组织。目录结构建议这样:data/garbage_truck/ ├── annotations/ │ ├── instances_train2023.json │ └── instances_val2023.json ├── train2023/ ├── val2023/ └── test2023/COCO JSON里最核心的三个字段是images(图片信息)、annotations(标注信息)、categories(类别表)。本项目类别表只有一项:"categories": [{"id": 1, "name": "garbage_truck"}]标注时用开源标注工具画框即可,导出COCO格式。有几个规范必须遵守:bbox字段必须是[x, y, width, height]格式,且是浮点数,不要用整数截断目标被截断超过一半时,如果无法判断整体轮廓,建议标注为ignore而不是正常正样本,避免给Grid head的网格点生成造成混乱两个垃圾车紧挨着时,框不要互相包含,哪怕目标被部分遮挡,也要尽量框出可见完整轮廓我在项目里发现一个很影响精度的细节:标注框是否贴合车身对Grid R-CNN的影响比对Faster R-CNN更大,因为Grid Loss会针对框内网格点做监督,框边缘松垮,等于给网格点的标签加了噪声。所以标注时宁可多花时间“贴边”,也不要随手拉大框。2.3 训练集/验证集划分与类别平衡划分数据集不能按图片随机打乱。监控视频里同一个机位连续拍摄的画面高度相似,随机划分会让同一场景的图片同时出现在训练集和验证集,验证分数虚高,部署到新场景立刻现原形。我采用的是按“场景+时段”分组划分。比如中转站A的白天画面、中转站B的夜间画面,各自作为一个分组单元,保证同一分组的图片不会跨集合出现。比例控制在训练集占80%、验证集占20%左右。验证集的设计是另一个容易忽视的坑。如果验证集里全是好检的正面大目标,模型AP会很好看,但上线后一点参考价值都没有。我额外构造了一个“困难验证子集”,专门收录夜间、遮挡、远端小目标、雨天反光的图片,单独跑一轮评估。项目汇报时,这个困难子集的指标比总AP更能说明模型能不能真正干活。3. 模型训练实现:配置解析与完整流程3.1 环境搭建与依赖版本选择环境方面,我用的是PyTorch + MMDetection这套开源工具链。版本匹配是第一个坑,PyTorch版本和CUDA版本不对,编译出的mmcv就报错,这一类问题占了环境问题的大半。# 先安装与本地CUDA匹配的PyTorch pip install torch==1.13.1 torchvision==0.14.1 # 再安装对应编译版mmcv-full pip install mmcv-full==1.7.2 # 最后安装mmdet pip install mmdet==2.28.2注意安装顺序:先装PyTorch,再装mmcv-full,最后装mmdet。如果mmcv是源码编译,耗时会比较久,建议直接装预编译的wheel版本,省心很多。装完以后先跑一个命令验证:python -c "import mmdet; print(mmdet.__version__)"能打印出版本号,环境基本就通了。本项目在单张24G显存的显卡上训练,显存吃紧,后面会讲到显存优化的具体办法。3.2 模型配置与关键超参解析MMDetection的配置是四段式的:模型结构、数据集、训练计划、运行时配置。我在基础配置上做了针对垃圾车检测的改动,核心片段如下:model = dict( type='GridRCNN', backbone=dict( type='ResNet', depth=101, frozen_stages=1, # 冻结stem和第一个stage,减少小数据扰动 norm_cfg=dict(type='GN', num_groups=32, requires_grad=True) ), neck=dict(type='FPN', in_channels=[256, 512, 1024, 2048], out_channels=256), rpn_head=dict( type='RPNHead', anchor_generator=dict( type='AnchorGenerator', scales=[4, 8, 16, 32], ratios=[0.5, 1.0, 2.0], strides=[4, 8, 16, 32, 64] ) ), roi_head=dict( type='GridRoIHead', bbox_head=dict( type='Shared2FCBBoxHead', num_classes=1, # 关键改动:单类别 reg_decoded_bbox=True ), grid_head=dict( type='GridHead', num_classes=1, # 关键改动:单类别 num_points=9, # 3x3网格点 norm_cfg=dict(type='GN', num_groups=32) ) ) )几个关键参数的说一下我的调法。num_classes必须改成1,否则模型输出维度是80+1,类别空间错乱,训练loss不降。anchor的ratios对垃圾车很关键。垃圾车常见的长宽比其实偏大,压缩式垃圾车约1:1.8到1:2.2,勾臂车带货箱时可以达到1:2.5以上。默认的[0.5, 1.0, 2.0]对这类扁长目标覆盖不够,我在ratios里加了0.33和3.0,让anchor能拉长到1:3左右,实测对压缩式垃圾车的召回升了大约2个点。frozen_stages设置为1,意思是冻结backbone最前面的低层特征。小数据集上低层特征已经足够通用,冻结它们可以显著减少显存和计算量,同时避免早期训练扰动底层的稳定特征。如果数据量足够大(大于2万张),可以改成0做全量微调。数据处理部分,我给训练pipeline增加了多尺度训练:train_pipeline = [ dict(type='LoadImageFromFile'), dict(type='LoadAnnotations', with_bbox=True), dict(type='Resize', img_scale=[(800, 800), (1333, 800)], multiscale_mode='range', keep_ratio=True), dict(type='RandomFlip', flip_ratio=0.5), dict(type='Normalize', mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375]), dict(type='DefaultFormatBundle'), dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']) ]多尺度训练让模型在不同大小下都见过垃圾车,对远端小目标和近端大目标都有更强的适应力。代价是每个epoch的耗时增加了大概30%,但精度收益明确,值得花。3.3 预训练权重加载与迁移学习策略预训练权重是这个项目启动的关键。我直接加载了COCO上训练好的同名模型权重文件。加载时的核心逻辑是:backbone和FPN的权重会完整复用,因为它们的层结构和COCO模型完全一致;roi_head里的bbox_head和grid_head因为num_classes从80改成了1,最后一层分类器的权重shape不匹配,会自动跳过并随机初始化。load_from = 'checkpoints/grid_rcnn_r101_fpn_gn_head_2x_coco.pth'启动训练后,第一件事是看日志里是否有如下提示:“Weights of xxx not used”:某些层没被加载,通常是neck或head,需要检查“The shape is not matched in xxx”:这是正常的,说明类别层被重新初始化了我的迁移策略是:先让backbone的浅层冻结,检测头从头学,等训练到第8-10轮,模型已经对垃圾车类别有一定的响应后,再把frozen_stages改为0,用一个较低的学习率(0.0005左右)做几个epoch的“解冻微调”。这个操作在项目里大约给最终AP带来了1-2个点的提升。学习率这里有一个必须说清楚的缩放逻辑。COCO标准配置是8卡、batch size 16、lr为0.02。单卡训练时batch size只有2,按线性缩放原则,lr大概在0.0025到0.005之间。我实测下来,lr取0.005配合500轮warmup比较稳,lr取0.01就容易在前几轮loss爆炸。这就是典型的小batch大lr事故。3.4 训练启动与过程监控配置写好以后,启动训练的命令很简单:python tools/train.py configs/garbage_truck_grid_rcnn.py训练过程中重点盯的指标包括:loss_rpn_cls(RPN分类损失)、loss_rpn_loc(RPN回归损失)、loss_grid(网格点分类损失)、loss_bbox(边界框回归损失)。我记录了本项目一次典型训练过程的loss走势:EpochRPN Cls LossGrid Loss备注10.451.80warmup结束,整体下降50.180.62检测头开始稳定100.110.38AP50接近70160.080.29学习率衰减节点220.060.21第二次衰减240.050.18达到可用水平一个非常值得注意的现象是:第16轮学习率衰减后,AP50和AP75会同时跳涨大约2-3个点。这是检测模型训练的正常“衰减红利”,所以一定不要提前终止训练,哪怕看起来第20轮已经收敛,也要让完整的2x计划跑完。训练中我每2个epoch保存一次checkpoint,用验证集跑一次bbox评估,记录最优权重。这里建议开启MMDetection的checkpoint机制里的“保留最优模型”选项,而不是手动挑选,避免training过程中间那个过拟合的节点被覆盖。4. 训练效果评估与推理部署4.1 评价指标解读:mAP、AP50、AP75与业务指标目标检测的标准评价指标是COCO风格的mAP,即IoU阈值从0.5到0.95每隔0.05取一个档位,共10个档位求平均。这个指标非常严格,对框的位置精度要求很高。此外还有两个更直观的指标:AP50(IoU阈值0.5时的平均精度)和AP75(IoU阈值0.75时的平均精度)。对垃圾车检测业务来说,这两类指标各有用途。做“车辆进出场统计”的业务,不需要框特别精确,AP50高就够用;做“违规倾倒取证”的业务,框必须贴合车身,AP75才是关键指标。我项目里最后训练出的模型,在独立测试集上AP50约88%,AP75约72%,mAP约52%。这个成绩属于单类别、场景相对聚焦的正常水平。除了整体mAP,我还特别关注AP_s(小目标)、AP_m(中目标)、AP_l(大目标)三个细分指标。垃圾车检测里面,远端的小目标AP_s往往只有大目标AP_l的三分之一,这是正常的,但如果AP_s低于10,说明模型基本检不出远处目标,需要从数据增强、输入分辨率上想办法,而不是盲目加训练时长。4.2 模型推理与可视化检测训练完成后,推理用MMDetection提供的高层API就能直接跑:from mmdet.apis import init_detector, inference_detector config_path = 'configs/garbage_truck_grid_rcnn.py' checkpoint_path = 'checkpoints/best_bbox_mAP_epoch_24.pth' model = init_detector(config_path, checkpoint_path, device='cuda:0') results = inference_detector(model, 'data/garbage_truck/test2023/000001.jpg') model.show_result( 'data/garbage_truck/test2023/000001.jpg', results, out_file='output/000001_result.jpg', score_thr=0.5 )init_detector的推理输出包含一个列表,第一个元素是bbox的Nx5数组(N为检测框数量,每行是[x1, y1, x2, y2, score])。我在项目里写了一个简单的后处理,把score_thr设为0.5用于日常巡检,0.3用于夜间低置信度场景的召回,0.7用于取证场景减少误报。这个阈值不能只看总AP来定,最好结合业务方对“漏报”和“误报”的容忍度来调。4.3 模型导出与工程部署要点训练只是第一步,部署才是落地的关键。如果业务方要求的是实时处理视频流,PyTorch模型直接跑吞吐量往往不够,需要导出加速。R101的PyTorch模型文件大约在300MB左右,导出为FP16的ONNX后可以减半。这里有一个Grid R-CNN特有的坑:grid_head在推理时的网格点收集和投票操作有自定义的逻辑,直接转ONNX会碰到不支持的算子。我的做法是先把bbox_head单独导出,grid的投票逻辑保留在Python侧做后处理,这样既兼顾了精度又降低了导出难度。如果整套都转TensorRT,建议用支持动态网格点操作的版本,先跑通精度再谈加速。如果业务方对延迟不敏感(比如夜间巡检、事后分析),直接用PyTorch服务化也够用。单张24G显存卡跑R101推理,batch size设为8时,每秒能处理大约20-30张图,完全够拿来做视频抽帧分析。5. 高频踩坑与排查笔记5.1 训练Loss异常与不收敛的排查这是所有检测项目里最常见的故障。我遇到过的场景和排查办法整理成下表:现象可能原因排查操作loss直接变NaN学习率过大或标注数据异常降低lr到0.0025重试;检查JSON中bbox是否越界、宽高是否为负数loss不下降预训练权重未正确加载启动时看“The shape is not matched”提示;用单张图跑一次前向验证输出维度grid_loss不降标注框边缘严重不贴合GT抽查标注可视化,重新修整松垮的框;检查是否有多边形但不带segmentation的脏数据RPN loss正常但整体AP很低anchor设置不合理根据目标长宽比分布重新设计ratios;打印每层anchor的匹配数一个有效的通用排查方法是:先用原始COCO配置、80类别、原始数据跑5轮,确认代码链路是通的;然后再改成单类别、换自己的数据。对比两次行为,能快速定位问题是出在数据侧还是配置侧。5.2 小目标与遮挡场景漏检的优化垃圾车在监控远端只有几十个像素,漏检是这类项目的“老大难”。我实测有效的优化手段有四个。第一,调整输入分辨率。把测试和训练的长边从默认的1333提到1600,短边从800提到1000,小目标检测会明显改善。代价是显存涨得厉害,需要配合梯度累积或者减小batch size。我这里显存不够后,把samples_per_gpu降到1,同时设置了accumulative_counts=8做梯度累积,效果等效于batch size 8。第二,优化anchor设计。小目标主要在FPN的P2层(stride=4)上匹配,如果P2层的anchor scale太小,小目标就没有合适的anchor匹配。我把anchor scales增加到[2, 4, 8, 16, 32],覆盖更小的目标。第三,滑窗切图推理。对特别大的监控画面(比如4K),直接缩放会导致小垃圾车更小。我是把大图切成上下两半分别推理,再用NMS合并结果。这个方法简单粗暴但非常有效,AP_s能从15提到25以上。第四,遮挡目标用ignore标注。前面说过,框内混杂遮挡物会让grid head学到错误的网格点分布,导致同一个目标时而检出时而丢。把这些目标标成ignore后,模型不再被迫拟合它们,干净样本上的精度反而提升了。5.3 显存溢出与训练加速技巧R101加FPN在24G显存上也不能太任性。batch size设为2时大概占用11G左右,如果开多尺度训练会涨到14G。显存溢出的标准处理套路是:samples_per_gpu降到1,开启梯度累积;关闭不用的测试增强;把workers_per_gpu从默认4降到2,减少数据加载时的CPU内存挤占。还有一个容易忽略的点:如果只是训练,可以把验证阶段的可视化输出关掉,只保留指标计算,这样每个验证epoch能省不少显存和计算。我在项目里把eval时的show标志全部关闭,验证时间从20分钟降到了8分钟。速度方面,如果业务从“离线分析”变成“实时接入”,R101就有点吃力了。我的建议是先用批次推理把吞吐提上去(batch size 8到16),再考虑换成R50或对head做剪枝。不过要留意,R50相比R101通常要损失2-3个点的mAP,如果业务对精度要求极高,优先保精度,用多卡分流的思路扛吞吐,而不是砍模型。最后再分享一点个人体会。Grid R-CNN这套模型在垃圾车这种“形状不规整、长宽比跨度大”的目标上,确实比普通Faster R-CNN定位更稳,尤其是AP75的提升很明显。但真正决定项目成败的往往不是模型本身,而是数据处理和验证集设计。我第一次拿到项目时,训练集里压缩式垃圾车占了八成,验证集又恰好全是勾臂式车,结果训练曲线一路高歌,上线后第一天就漏检了一大半勾臂车。后来我把数据按场景和车型重新均衡,又专门拉了一个困难样本验证集,模型才算真正能打。如果你也要做类似的车种检测,我的建议是:先把数据分布彻底看清,把验证集设计得足够“刁钻”,再回头决定用R101还是R50、用2x还是1x。工具链已经很成熟了,模型选型反而是整个项目里最不花时间的环节。