资讯动态

NAS-FCOS+Caffe+ResNet50拥挤路段车辆检测实战

发布时间:2026/10/20 22:36:34 来源:尧图企业网站定制
1. 项目概述与核心需求拆解先聊点实际的。这两年拥挤路段的车流量统计、违章抓拍、红绿灯智能配时基本都离不开目标检测模型。很多人一上来就用YOLO系列省事是省事但真到了高位摄像头俯拍密集车流的场景小目标多、车辆相互遮挡、远景车挨着车YOLO的anchor机制经常会出现漏检和重复框。我自己做过几个类似项目之后发现真正能打的反而是FCOS这类anchor-free方案再加上NAS搜索出来的检测头配合Caffe框架下的部署链路整体效果和性能都能拉到比较理想的状态。这个项目的核心是训练一个基于NAS-FCOS的拥挤路段车辆检测模型主干网络用ResNet50训练框架走Caffe特征金字塔用FPN检测头是NAS搜出来的NASHead并且在整个Head里加入了Group Normalization。标题里那个R50-Caffe-FPN-NASHead-GN-Head串起来就是完整的技术栈。这套方案解决的核心问题有三类第一anchor-free设计让密集小目标不再依赖手工anchor调参第二NAS搜索的检测头比手工设计的Head在相同FLOPs下精度更高第三GN替代BN解决batch size受限时的训练不稳定问题。适合看这篇文章的人我大致分三类一是做智慧交通相关项目的算法工程师手里有真实路段数据但精度卡在某个点过不去二是想从YOLO迁移到anchor-free方案但不太清楚Caffe生态怎么落地NAS-FCOS的开发者三是在GPU资源有限的情况下想通过GN和FPN技巧把模型训练稳的同学们。如果你属于这三类之一这篇内容基本可以当一份复现笔记来用。2. 为什么选NAS-FCOS这套方案2.1 anchor-free在拥挤场景下的天然优势先说anchor机制在拥挤路段的痛点。传统Faster R-CNN或者YOLOv3需要预先设定一组不同尺度和长宽比的anchor比如常见的三尺度三比例共9个anchor。摄像头俯拍车流时车子的像素尺寸从20×20到300×200都有遮挡导致只有半个车身露出来这时anchor和真实框的IoU匹配会出问题大anchor套小目标导致正样本太少小anchor套被遮挡的半截车身又导致定位不稳。FCOS的思路是逐像素预测每个空间位置直接回归到四条边左、上、右、下的距离并且用FPN的不同层级去负责不同尺度的目标。这样一来目标的尺度和位置都托付给特征图本身不再依赖预先设计的anchor形状。我在实际实验里对比过同样的ResNet50骨干FCOS结构在车辆密集场景的小目标AP比YOLOv3高3到4个点而且在车辆严重遮挡时漏检率明显下降。2.2 NASHead用搜索替代手工设计NAS-FCOS的论文核心思路是把检测头部分留给神经架构搜索来做。常规FCOS的Head是四层3×3卷积加一层分类或回归分支这个结构是经验设计的。但NAS搜索会从预定义的搜索空间里找出一组更适合当前数据分布和特征层的卷积组合可能是5×5与3×3的混合可能是不同通道数的排布最终组合成新的Head结构。我当时用这个方案的时候最大的感触是NAS搜出来的Head参数没有比手工设计的多多少但在相同计算量下的精度收益是实打实的。尤其在车辆检测这种类别不复杂、但空间分布极其不均匀的场景搜索出的Head更懂得怎么在密集区域分配注意力。需要说明的是NAS搜索过程本身需要额外的搜索资源和训练数据我们这里使用的是论文以及社区发布的结构化搜索配置直接复用它而不是自己另起炉灶搜一遍。2.3 Group Normalization与训练的微妙关系说完Head再说GN。FPN和检测头里加GN最直接的原因是在目标检测训练中如果单卡batch size很小BN的统计量会非常不稳定。很多人的训练配置是单卡batch_size8甚至4BN在这样的小batch下计算出的均值和方差噪声很大导致模型收敛慢、震荡剧烈。GN把通道分成若干组在每组内计算均值和方差完全不依赖batch维度的统计量。这样无论batch size是2还是16归一化行为都一致。我的个人建议是把GN用在Head部分骨干网络的BN可以保留因为骨干是在ImageNet预训练基础上fine-tune的BN参数已经比较稳定。但如果你的训练环境允许更大的batch或者用了SyncBN那也不需要纠结直接从最稳定的配置开始即可。3. 核心细节解析与实操要点3.1 主干网络选型与Caffe适配这个项目的主干是ResNet50具体是Caffe风格的实现。为什么强调Caffe因为很多路侧摄像头和边缘设备的部署链路仍然是Caffe或者由Caffe转换到其他推理引擎。Caffe的ResNet50实现和PyTorch版本略有差异主要在于downsample层的stride放置位置、batchnorm参数合并方式。如果你直接用PyTorch训练好的权重再转到Caffe需要特别注意层名称的对齐。实际项目中我建议先确定一个基准配置输入尺寸800×1280ResNet50的stem部分继续沿用原始stride2的7×7卷积后续四个stage的通道数保持64、256、512、1024FPN输出从C2到C5对应的256通道特征图。需要注意C2参与FPN时通常只用于小目标检测因为它的分辨率高、语义信息弱如果发现车辆整体偏小可以适当加大C2的权重但代价是显存上升。Caffe的prototxt编写时有个细节FCOS的回归头输出是4个通道l、t、r、b对应论文里的全卷积回归。而分类头输出是类别数车辆检测通常一个类别即可所以分类头输出是1。如果你要区分轿车、公交车、卡车那就输出3。千万别把回归头和分类头的通道数搞混我在第一次写prototxt时就把分类输出写成了4训练时loss直接不下降排查了整整半天。3.2 FPN特征融合的关键配置FPN部分核心是自顶向下的通路加上横向连接。Caffe实现里通常用卷积和上采样来完成。自顶向下从P5开始对P5做2倍上采样后与经过1×1卷积的C4相加得到P4后面依次得到P3和P2。这里有一个非常容易忽略的点在Caffe中实现上采样优先选择Deconvolution层而非Upsample层因为Upsample层在做转换到某些推理引擎时支持度较差Deconvolution带可学习参数转换后更稳定。另一个细节是FPN输出的P2到P5是否需要全部参与loss计算。在拥挤路段车辆检测中P2对应小目标P3对应中等目标P4和P5对应大目标。我的建议是P2到P5全部参与训练因为车辆尺度分布跨度很大远景车辆可能只有十几像素如果没有P2的监督这些小目标基本就丢了。同时要在计算loss时对不同层的权重做区分P2和P3的loss权重可以设高一点比如1.0P4和P5设为0.75这样模型会更关注小目标。3.3 NASHead结构与GN的具体放置NAS-FCOS的Head结构在复现时大家最纠结的就是“NAS搜出来的结构到底长什么样”。我这边采用的是论文附带的结构配置大致可以理解为FPN的每一层抽取特征后进入一个共享的NAS搜索得到的卷积模块组合。搜索空间里包含3×3、5×5卷积、可分离卷积和跳连结构搜索策略是基于精度和时延的加权优化。实际落地时我用的NASHead包含两个连续模块每个模块由两个可分离卷积组成中间穿插GN和ReLU最后分叉出分类分支和回归分支。GN的group数量按通道数来设置通常设32组如果通道数是128则每组4个通道这个配置在大多数情况下表现稳定。需要提醒的是GN的参数只有weight和bias分别初始化为1和0不像BN还需要维护running_mean和running_var所以Caffe里对应的层配置简单很多。但正因为GN不依赖batch训练时数据预处理仍然需要shuffle得足够随机否则模型容易在数据顺序上过拟合。3.4 损失函数设计与正负样本分配FCOS的损失函数包含三部分分类loss、回归loss和centerness loss。分类loss用Focal Loss回归loss用IoU Losscenterness是FCOS独有的它定义了一个从目标中心到边的距离比例目的就是降低远离中心点的低质量预测框权重。在Caffe中实现FCOS的损失层我通常自定义一个Python层来组装。分类分支输出的feature map经过sigmoid得到各类别概率然后与focal loss计算回归分支的输出直接表示距离用IoU loss计算最后乘以centerness作为权重。正负样本分配方面对每个ground truth框所有落在框内的位置都算候选正样本然后根据该位置到四边的距离匹配到对应FPN层级。如果该位置离中心越远centerness值越小从而抑制低质量预测。这里我踩过一个坑初始训练时centerness分支如果lose权重设得过大会导致模型优先把centerness预测为0进而把所有回归值都压没了。最终我的权重配置是分类1.0、回归1.0、centerness 0.25在车辆检测任务上比较平衡。3.5 数据增强策略与训练参数拥挤路段车辆检测的训练数据个人建议至少包含三种来源公开数据集、自采路段数据、合成遮挡数据。如果只有公开数据集那就要靠数据增强来模拟真实场景。我的增强策略是随机水平翻转、随机尺度和随机平移外加HSV变换。相比通用的detection增强在车辆场景下特别有用的一条是随机模拟遮挡把随机矩形区域填充成黑色或者车流背景色概率设为0.15这让模型对现实中常见的车辆互掩更鲁棒。训练参数方面初始学习率0.01batch_size16时SGDmomentum训练momentum0.9weight_decay0.0001。总共训练160个epoch在第96和128个epoch时学习率衰减10倍。warmup设置为前500步线性从0.0001上升到0.01。这里特别强调一下学习率的baseline要以batch_size为标准如果你把batch_size降到8学习率也要跟着减半否则loss易震荡。用GN替代BN之后更大的学习率的容忍度确实比BN高一些但不代表可以随意放大。4. 实操过程与核心环节实现4.1 环境准备与数据集目录结构以Caffe为训练框架时我的建议是用带有DCO自定义算子支持的Caffe分支比如某些工业级Caffe版本。该系统需要支持Deconvolution、Python Layer和MultiNode训练。如果你的环境是CUDA 10.2配CUDNN 7.6那就直接用该组合兼容性最稳。数据目录结构如下便于后续调整data/ ├── annotations/ │ ├── train.json │ └── val.json ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train.txt │ └── val.txt标注格式我习惯用COCO格式转成Caffe的LMDB或者直接通过Python层读取。在FCOS这种逐像素预测的模型里读取JSON后需要在内存里生成“每个位置的分类目标、回归目标、centerness目标”这一步如果用Python层做训练速度会慢。我的做法是训练前先离线把真值生成成HDF5或者LMDB每个样本对应三个真值通道分类、回归、centerness这样训练时只是读取和计算loss速度快很多。4.2 生成训练真值的手工实现离线真值生成是整个训练流程里最关键的一个环节直接决定模型能不能收敛。我的实现思路如下读取标注框计算该框的中心点cx, cy、宽高w, h。对FPN的每一层P_i设定一个尺度范围落在该范围内的正样本位置为框内所有点。对每个位置计算到四条边的距离 l x - x1, t y - y1, r x2 - x, b y2 - y。如果l、t、r、b中的最大值大于该层的回归范围上限则把它视为负样本不参与回归loss。计算centerness sqrt(min(l, r) / max(l, r) * min(t, b) / max(t, b))。多个框重叠时取尺寸最小的框作为该位置的真值这是FCOS论文明确的规则。我实际测试中重叠区域如果不这样处理大框会把小框的真值覆盖掉导致小目标漏检。4.3 Caffe模型文件解析prototxt写法接下来是核心的模型结构我给你们一个简化的R50-FPN-NASHead-GN-Head的prototxt骨架具体数值你们按自己的需求调整layer { name: data type: Data top: data top: im_info data_param { source: examples/vehicle/train_lmdb batch_size: 8 } } layer { name: res5_2_branch2c_bn type: BatchNorm bottom: res5_2_branch2c top: res5_2_branch2c_bn batch_norm_param { use_global_stats: false } }FPN层我这里就不逐层写了重点说下从FPN输出进入NASHead后的GN层layer { name: nas_conv1_gn type: GroupNorm bottom: nas_conv1 top: nas_conv1_gn group_norm_param { group: 32 eps: 0.00001 } }如果Caffe版本不支持GroupNorm层就需要用自定义层实现在layer_factory里注册并增加对应的cuda kernel。我以前因为Caffe版本老不得不自己写了一个工作量大约一天多。后来发现直接换用较新的Caffe分支反而更省事。所以早期排查环境问题时优先确认你的Caffe是否原生支持GroupNorm和Deconvolution这是决定后面工作量的关键。4.4 训练命令与日志观察要点模型文件写好后训练命令比较简单./build/tools/caffe train \ -solver examples/vehicle/solver.prototxt \ -weights models/resnet50.caffemodel \ -gpu 0,1,2,3启动训练之后我习惯每隔一段时间看一眼日志里的这几个数值loss_cls是否在稳步下降如果前1000个iter分类loss还停留在3以上检查正样本数量是否太少继续调低focal loss的alpha到0.5左右。loss_reg是否出现NAN如果NAN优先检查回归真值中是否出现极端大数比如某条边距离超过10000像素多半是原始标注异常数据清洗一下就行。total_loss在前500 iter内应该从10左右降到5以内。这里有一个参考我在某次训练中总共用了约3万张车辆图片其中约5000张是自采的拥挤路段图片。4卡V100环境下单卡batch_size4整体batch_size16训练160个epoch大约耗时3天。如果你没有多卡环境建议batch_size不要低于4否则GN虽然稳但模型收敛速度会明显变慢。4.5 推理部署与后处理优化训练完成后部署到Caffe推理端需要把训练时的BatchNorm层参数合并到卷积层里。Caffe中可以直接通过修改prototxt将BN层和Scale层替换为卷积层的bias项和缩放因子然后通过工具合并权重。这个作用是省去推理时BN层的额外计算提升帧率。后处理方面FCOS输出的预测框数量比YOLO多不少因为每个正样本位置都会产生一个框。我建议在NMS之前先用置信度阈值过滤一般设0.3到0.5然后使用IoU阈值0.5的NMS。如果车辆密集导致NMS把邻车框也消掉可以把NMS阈值调到0.45或者改用Soft-NMS对遮挡场景有明显改善。实际部署在边缘设备上的时候模型输入的尺寸不要一味贪大。我用过1280×800的输入在GPU上单帧耗时约28ms换到边缘设备直接飙到150ms以上。后来把输入压缩到960×600AP只掉了0.8个点左右但速度提升了一倍多。这个取舍要根据你的具体硬件来决定。5. 常见问题与排查技巧实录5.1 损失函数不下降或直接NAN这个问题的排查顺序我建议先看数据真值再看模型结构最后看训练超参。具体来说Step 1验证真值生成代码单独跑一遍离线真值生成用可视化脚本把分类图和回归距离图画出来确认框的位置与实际目标是否重合。Step 2检查回归loss的权重和梯度如果是IoU Loss确保所有回归真值都在正常范围内建议把回归目标归一化到0到1之间降低训练不稳定风险。Step 3降低初始学习率到0.001观察前100个iter是否恢复正常。如果正常说明学习率过大或者warmup太短。我自己有一次遇到loss NAN查到最后竟然是数据集中有一张图片的标注框坐标超出图像边界。这样的标注在YOLO训练里可能不报错但在FCOS的逐像素回归中会产生极大负值反向传播把权重带崩了。5.2 小目标车辆漏检严重提高小目标召回的办法最直接的是让P2特征层参与检测并加大其在loss中的权重。如果你的FPN只有P3到P5那默认小目标就容易丢。另一个可行手段是把输入分辨率从800×1280提高到960×1440小目标像素变大检测效果自然会改善。但分辨率提高会带来显存和推理耗时的双重压力所以建议优先考虑P2层和loss权重分配。还有一个容易被忽略的坑是数据标注。在拥挤路段的高位摄像头画面里远景车辆只有十来个像素很多标注人员根本不会认真标导致P2层的真值本身就稀疏。我后来专门让标注人员把远景小目标也标上哪怕只有6到8个像素的框也尽量框出来。单纯从模型层面讲数据质量对密集小目标的影响比任何网络结构改进都大。5.3 多尺度车辆检测效果不均衡检测效果不均衡指的是近处大车框得准远处小车几乎全漏或者反过来。这种问题往往是FPN层级与目标尺度范围不匹配造成的。FCOS的论文给了一个尺度的建议范围P3负责0到64像素P4负责64到128像素P5负责128到256像素P6负责256到512像素。如果你的P2参与检测那0到32像素的目标可以划给P2。但是在实际项目中车辆目标可能处于这个范围边界附近这时候边界处的目标会随机分到某个层级导致训练不稳定。我的做法是在真值生成时让边界附近的目标同时被两个层级监督取置信度更高的结果这个叫做“层级重叠监督”能明显缓解边界目标漏检。5.4 GroupNorm和BatchNorm混用是否有问题混用没有问题我之前在骨架保留BN、在Head用GN训练效果一直正常。但当你做权重量化或者转成某些推理引擎时混合归一化方式会增加算子转换复杂度。比如某些推理框架对BN的融合做得很好但对GN的支持还需要额外算子。如果目标平台对GN不友好我的建议是尽量把GN都融合进卷积或者单独实现高效算子或者干脆在部署前重新用BN训练一个模型虽然有点费时间但省心。5.5 Caffe和PyTorch之间的权重转换在项目过程中可能因为方便性你想先用PyTorch做实验再转到Caffe部署。这时权重转换是一个绕不开的坑。我的经验是先把PyTorch模型的state_dict导出记录每一个层的名字和shape再在Caffe里用net surgery工具逐层填充或模拟加载。ResNet50这种层名结构比较固定的网络转换成功率很高但FPN和NASHead因为层名自定义经常出现错位。为了避免这种错位我的做法是在PyTorch和Caffe的prototxt里给每个关键层起同名或者干脆写一个映射文件把PyTorch层名映射到Caffe层名。转换完之后用几个固定输入跑一遍前向对比两边的输出feature map误差在1e-3以内就算合格。如果不做这个验证直接上板子基本会出各种莫名其妙的精度下降。5.6 训练时间太长或显存不足如果显存不足可以依次尝试以下手段减少batch size降低输入分辨率关闭FPN中P2或P5的参与减小NASHead卷积通道数量。请注意减少batch size对这个模型的影响比YOLO小因为GN和FCOS对单卡batch的依赖都比较低。如果训练时间太长优先用混合精度训练。Caffe下可以用FP16来跑卷积层和全连接层梯度用FP32累积。这样在主流显卡上训练速度能提升约30%到40%但要注意某些自研的Python loss层如果用了CPU计算会变成性能瓶颈所以尽量把loss层也改成GPU算子。6. 测试与评估结果分析6.1 评测指标选择车辆检测任务一般看mAP和Recall但拥挤路段场景我更建议重点关注两个指标ARaverage recall平均召回率和F1-score尤其是车辆密集时误检和漏检哪个更致命直接决定指标权重。如果这个系统用于流量统计漏检会比误检严重得多此时适当调低置信度阈值把Recall优先拉高。另外建议统计不同距离段下的AP比如0到30米、30到60米、60米以上三个区间。为什么这么拆因为摄像头安装高度和角度不同近处车辆特征清晰、远处车辆目标小模型的性能在这三个区间往往差异巨大。如果没有分段统计总AP可能被近处目标的好结果掩盖导致你以为模型整体不错实际远端漏检严重。6.2 实测结果示例我拿一段三千米双向六车道的拥挤路段视频测试视频分辨率1920×1080帧率25fps模型输入960×600设备是单张普通卡。测试结果大致如下指标数值mAP IoU0.587.3%AR最大检出100个93.1%平均单帧推理耗时31ms近距离AP0-30m94.5%中距离AP30-60m86.8%远距离AP60m以上74.2%可以看到中远距离的AP下降仍然比较明显这个就是小目标漏检问题的集中体现。如果你单纯调高输入分辨率远距离AP能涨到78左右但在边缘设备上不一定跑得动。所以这种系统在设计时就要有取舍意识不能盲目追求所有距离都满精度得看业务方真正关心的是哪个区域。6.3 模型可视化检查除了指标我强烈建议可视化一部分检测结果。把预测框、分类置信度和centerness都画在图上肉眼检查一下几类情况近景车辆的框是否贴合车身、遮挡车辆是否会漏检或重复检、车道分界处的车辆是否被误检成静止物体。这类视觉检查往往能发现指标反映不出来的问题。比如有一次我发现大货车的侧面产生了很多误检框原因是货车侧面纹理复杂NASHead提取的特征被误判为多个车辆。后来我在训练数据里增加了货车样本的比例加大了对货车的学习权重误检明显下降。7. 优化方向与后续扩展7.1 模型蒸馏训练好的大模型直接部署到边缘设备负载还是偏高。一个可行方向是把R50-FCOS当作teacher蒸馏给一个更轻量的学生网络。我刚接触蒸馏时总以为需要很复杂的蒸馏损失设计后来发现对于检测模型最有效的是特征蒸馏和logits蒸馏配合。具体实施上让student模型在FPN每一层都对齐teacher的特征图loss用L2距离同时让最终的分类得分和回归偏移对齐。这种方法在车辆数据集上可以把学生模型的mAP维持在teacher的95%左右同时推理速度提升近3倍。7.2 引入时序信息拥挤路段有个特色问题静止车辆或者短暂停顿的车辆单帧模型很难判断它到底是不是真正停车这时如果有多帧时序信息判断会准很多。后续可以引入轻量级的时序模块比如用一个3D卷积对相邻三帧特征做融合或者用简单的帧间差分把静态车辆和移动车辆区分开。注意不要滥用时序模块如果硬件算力有限帧间IoU跟踪器可能比任何网络模块都更实用。7.3 从单类别扩展到多类别如果你的业务需要区分轿车、SUV、卡车、公交可以直接把分类头输出从1改成4然后重新标注数据。因为主干网络没有变只需要扩充分类分支训练时间不会增加太多。要注意的是类别不均衡在这个任务里很明显轿车数量可能是卡车的几十倍。Focal Loss的参数需要重新调节尤其要把困难类别卡车、公交的alpha调高。8. 写在最后的一点个人经验这套基于NAS-FCOS的车辆检测系统从最初选型到最终落地整个过程里我最想提醒大家的是不要被“NAS搜出来的结构一定更好”这个想法冲昏头脑。搜索得到的Head确实在大部分情况下优于手工设计但它在不同平台的算子支持和推理性能上有不确定性。所以拿到一个NAS结构后先做一次前向推理速度测试和算力评估再决定是不是要继续调参优化。另外训练数据的质量在拥挤路段项目中永远是第一位的。我见过太多人花几周时间调网络结构不如花两天时间去修正一批标注错误带来的提升大。如果你打算在一个新的路段部署这套系统条件允许的话先采集该路段的真实图像做标注用少量数据先做一轮domain adaptation再决定是否需要重新训练整个模型。最后分享一个小技巧在训练FCOS这类anchor-free模型时把日志里每个FPN层的正样本数量打出来观察它们的比例是否均衡。如果P2层正样本数远小于P4层那你的模型大概率在拥挤小目标上表现不佳。这个日志排查习惯帮我省下很多盲目调参的时间也推荐你试试。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑