做目标检测这行久了手上经过的模型少说也有十几种可每次接到新需求还是会先问一句到底用什么网络最合适。最近我完整跟进了一个校园建筑物识别与分类项目核心任务是把校区里的教学楼、宿舍、图书馆、体育场馆等建筑从航拍和地面视角的图像中自动找出来并精确分类。调研一圈之后我把主力模型定在了RetinaNet上基于PyTorch跑通了从数据采集、标注、训练、调优到部署的完整链路。这篇文章不打算做原理复读机重点讲讲我在这个项目里踩过的坑、调过的参数、以及RetinaNet在校园场景下表现好的地方和不如人意的地方。无论你是刚开始接触目标检测还是已经跑过几个模型想横向对比应该都能从中拿到一些可以直接用的东西。1. 需求拆解校园建筑物识别到底难在哪1.1 场景特点决定了模型选型方向校园建筑物识别不是简单地“把房子框出来”那么简单。我一开始以为这活儿和通用目标检测差不多真正铺开数据以后才发现校园场景有几个很磨人的特点。第一是尺度差异极大。同样是“建筑物”十几层的宿舍塔楼和一间平房值班室在画面里的尺寸差出十倍以上。无人机高视角航拍图里一栋完整的教学楼可能只占几十个像素而地面近景拍摄时同一栋楼能撑满整个画面。多尺度检测能力是硬需求。第二是类别之间的视觉相似度很高。教学楼上贴了白色瓷砖实验楼也贴了从正上方看都是长方形的浅色屋顶图书馆带个玻璃穹顶体育馆也有弧形屋面。算法一旦只盯着纹理特征很容易把这两类彻底搞混。这个项目分类的精细度要求和“车、人、猫、狗”那类粗粒度检测完全不是一个难度。第三是背景干扰严重。树木遮挡、操场跑道、楼顶太阳能板、阴影、甚至连地面停车场都能在特征图上形成类似屋顶边缘的响应。如果不做针对性的数据清洗和后处理策略模型训练出来会有一堆“幻影框”。基于这些特点选型方向就清晰了第一模型必须有多尺度特征融合能力第二正负样本不平衡问题必须专门处理——画面里大部分区域是背景建筑物只占很小一部分第三推理速度最好能跑到实时或者近实时因为后面可能要接无人机巡检的视频流。1.2 为什么是RetinaNet而不是YOLO或Faster R-CNN很多朋友问我为什么不直接用YOLOv5或者YOLOv8又简单又快。这个选择我仔细权衡过。YOLO系列在通用检测上确实很强但我这个项目有几个绕不开的诉求。校园建筑物的长宽比很有规律绝大多数是矩形而且方向基本贴着主路网格YOLO那套通用锚框策略不见得能充分利用这种先验。另外正负样本极端不平衡的校园航拍图里YOLO的损失函数设计对简单负样本的抑制不够训练出来的模型会倾向于“保守”漏检率偏高而漏检对建筑物识别来说是很伤的结果——漏掉一整栋楼往往意味着这套系统在业务层面不可用。Faster R-CNN我也考虑过。它的两阶段设计RPN加ROI Pooling在精度上确实稳但速度是短板。我手头只有单张消费级GPU如果跑Faster R-CNN后续在无人机视频流场景里基本跑不动只能靠抽帧这就浪费了算法能力。更重要的是Faster R-CNN的RPN本身也要做正负样本挖掘面对航拍图像那种背景占绝对多数的情况需要额外调很多参数改起来比较痛苦。RetinaNet站在这两种方案中间单阶段结构保证推理够快不用单独跑两遍网络特征金字塔网络直接加分支处理多尺度Focal Loss专门针对正负样本不平衡把“背景容易分、建筑物难分”这种落差用损失权重拉平。这套组合几乎就是这个场景的定制答案。事实证明在同样的数据集、同样的训练轮数下RetinaNet的漏检率比当时配置下的YOLO要低整体mAP也高出一截。2. 数据准备模型上限在哪里从数据第一天就定了2.1 数据采集策略不能只靠一种视角数据是这类项目的命门。我见过太多同学上来就下载公开数据集拿通用目标检测场景的图片直接fine-tune结果在真实校园场景里掉点惨重。建筑物识别尤其吃数据分布航拍视角和地面视角的特征差异极大如果训练数据只有航拍模型到了地面摄像头视角就会“失忆”反过来也一样。我的采集策略是分三条线走。第一条线是无人机绕校飞行采集高度控制在60到120米镜头角度以正射为主适当加一点倾斜摄影因为正射视角能拿到完整的屋顶轮廓。第二条线是地面定点采集校园道路两侧、广场周边模拟以后可能部署的监控摄像头视角。第三条线是地图影像补充用公开的高分辨率影像截取部分片段虽然时效性一般但能补充一些屋顶俯视结构板的多样性。这样交叉采集的好处很明显模型能学到建筑物在不同视角下的不变特征而不是记忆某个特定角度下的纹理。整个数据集最终整理了大概6000多张有效图像里面包含约17000个标注实例。这里有一点必须提醒数据量不是“多多益善”关键是类别的实例数量要均衡。如果教学楼标了7000个附属用房只标了300个训练出来附属用房的精度会惨不忍睹。我后来做了一个最简单的统计分析把每个类别的实例数量画成柱状图看到差距后立刻返回去补采目标类别的样本才算把分布拉平。2.2 标注规范一栋楼框到哪里直接影响模型学到什么标注这件事看似就是画个框但画法不同模型学到的边界特征完全不一样。校园建筑识别有一个很容易犯的错误把突出的小型构造物也包进主框里。比如教学楼顶层有个消防水箱间如果框的时候把它并进去模型的回归分支就会拿到混乱的监督信号同一个类别在特征空间里对应着多套形状模板收敛难度直线上升。我在项目里定了一条硬性规范只框主体结构不框附属物。主体结构指的是能独立成为几何体的部分出挑的门廊、外挂楼梯、裙房突出的雨棚如果面积很小就先忽略如果面积大于主体面积的五分之一宁可单独标注为另一个实例也不要合并。这样定义之后同类目标的框形状高度一致Anchor匹配时的IoU计算也能更稳定。标注工具我建议优先选开源可离线标注的那类图形化工具数据结构输出为Pascal VOC格式或者COCO格式。VOC格式对初学者友好每个XML对应一张图COCO格式更利于后续做Focal Loss和评估脚本的整合我最终全部转成了COCO格式因为标注的类别号、分割信息和边界框信息可以用一个JSON文件管理训练时IO方便很多。标注过程最好由至少两个人做交叉核验另一个人抽检重点检查边界框是否贴边、遮挡严重的实例有没有漏标。2.3 数据增强与类别平衡的实操心得数据增强这块我的原则是“既懂物理规律又懂特征不变性”。校园建筑是刚性结构不像猫猫狗狗那样有姿态变化所以弹性形变类的增强意义不大但几何变换和光度变换非常重要。最后我启用了随机水平翻转、随机垂直翻转、随机旋转90、180、270度、随机亮度对比度扰动、以及随机裁剪缩放。注意旋转增强时mind一下旋转后图像尺寸的变化矩形的长和宽会互换标注坐标也跟着转很多工具会自动处理但如果你是自己写数据管线这个坑一定要确认到位。类别平衡不单靠增加样本我还用了一个“按实例数采样”的小技巧。训练时不是简单地从所有图像里随机抽而是统计每张图里各类别实例的数量给含稀有类别的图像更高的采样权重。这样每个epoch里稀有类别被模型看到的次数明显增加直观理解就是“点名让模型多看实验楼几眼”。加了这一步后实验楼这个类别的召回率提升了接近4个百分点效果非常直接。3. RetinaNet核心机制精读它凭什么能扛住校园场景3.1 特征金字塔网络FPN在多尺度建筑下的神效RetinaNet之所以对尺寸差异极大的建筑物有效根本原因是它的骨干网络后面接了特征金字塔网络Feature Pyramid NetworkFPN。骨干网我用的ResNet50逐层下采样得到从浅到深的特征层。浅层分辨率高、语义信息弱但空间位置和边缘细节保留得好深层分辨率低、语义信息强适合识别目标类别但位置信号已经模糊。FPN做的事是把这两者横向连起来自顶向下把深层的语义信息逐层回传每一层都和对应的横向连接合并让每一个输出层都同时具备“高分辨率”和“强语义”。在RetinaNet里FPN最后输出P3到P7五层特征分辨率从原图的1/8到1/128递减。这样一来大建筑可以由深层大感受野的层负责——低层屋顶整体外观一目了然小建筑由浅层高分辨率的层负责——值班室、附属房这些不容易漏掉。实际操作中我观察到一个现象如果去掉P3一层小目标的召回率几乎腰斩。校园里的小型附属房只有几十个像素大小在P3层还能留下足够强的响应在P4层已经开始模糊。这也是为什么我在调参时特别小心不敢乱剪FPN分支的原因。3.2 Focal Loss正负样本差距过大时光用交叉熵是撑不住的RetinaNet最出名的核心是Focal Loss。为什么需要它我们回到训练细节里去理解。一张航拍图里网格化铺开的锚框动不动就是几万个其中包含建筑物的实例框可能只有几十个其余都是背景。标准的交叉熵损失里背景这一类简单样本的损失会压过真正重要的前景样本模型梯度被大量“已经很容易判断的负样本”稀释掉。Focal Loss的公式并不复杂在交叉熵前面乘上一个调制因子(1 - p_t)^γ再加上一个类别均衡权重α。这里p_t是模型对该类别的预测概率γ是聚焦参数我最终设定为2.0。当预测概率很高时比如0.9(1-0.9)^2等于0.01损失被压制到接近零相反当预测概率在0.3这种不确信区域调制因子是0.49损失被保留。这相当于把模型注意力逼向难例。α我设成0.25从经验上看这样对类别整体精度更友好。有同学会问γ设得再大一点不是更好吗我试过γ3训练曲线倒是更快收敛但最后mAP反而掉了因为太强的调制因子让大量难例也变成“半放弃”状态模型学到的东西过于保守。这个平衡值得根据具体数据微调不要迷信默认值。3.3 锚框Anchor设计与参数化逻辑RetinaNet是Anchor-Based模型锚框的设置直接决定模型能够匹配到多少真实目标。默认配置下每层特征图的每个位置铺9个锚框3种尺度乘以3种长宽比。尺度在基础锚框面积上按2^{1/3}等比放大长宽比默认是{0.5, 1.0, 2.0}。校园建筑物多为横向展开的长条矩形屋顶的长宽比经常超过2.0甚至能达到3到4。默认的2.0上限会把这些目标架在“凑合匹配”的位置IoU很难超过0.5的正确匹配阈值造成大量真实目标被当作负样本丢掉。我对照数据分布统计后把长宽比从{0.5, 1.0, 2.0}改成{0.8, 1.0, 2.5}并且在P3层额外增加了一组小尺度的锚框。改完之后正样本匹配数量明显上升训练前几个epoch的loss下降幅度也更快。锚框和真实框的回归参数用的是常见的四参数法中心点偏移的t_x、t_y和宽高比例的t_w、t_h。回归目标做归一化的时候要除以锚框自身的宽高这个细节决定了不同尺度锚框的损失量级是否均衡不然大锚框回归误差天然就比小锚框大训练会偏向修正大目标小目标完全学不到位。4. 训练实战从配置文件到模型收敛的完整复盘4.1 环境配置与数据格式转换训练框架我选的是开源的目标检测工具箱MMDetection类它以配置文件驱动支持开箱即用的RetinaNet实现背后是PyTorch生态成熟调试也方便。硬件上我用的单张显卡显存12GBBatch Size设为4输入图像统一缩放到1333×800左右。这个尺寸比常用输入略小一些主要是为了平衡显存占用和训练速度实测下来精度损失很小。数据格式转换这一步看似机械但最容易出问题。我用脚本把标注XML文件转成COCO JSON格式其中有几个字段必须仔细核对图片的id要唯一且连续类别id从1开始而不是从0开始标注里的bbox必须是[x, y, width, height]的绝对像素值。如果从别的格式转过来别忘了类别映射索引查一遍——我有一次就是把“教学楼”和“实验楼”的类别号在映射文件里写反了训练两轮后发现混淆矩阵对角线特别低排查半天才揪出来。4.2 超参数选择不是照抄别人的数字超参数这块我最不建议直接抄官方默认配置。官方配置是针对COCO数据集设计的COCO有80类图像内容五花八门我的任务只有8类且图像风格统一需要单独定参数。学习率我设置初始值为0.001优化器用带动量的SGD动量0.9权重衰减设为1e-4。为什么选SGD而不是Adam在目标检测这种高维参数空间里SGD搭配动量在后期收敛的稳定性更好尤其是回归分支的坐标参数Adam有时会震荡得更明显。当然Adam也能用但经验上SGD在检测任务里泛化更稳。训练策略上我用了线性预热。前500个step学习率从0.0001逐渐升到0.001避免模型在刚起步时因为梯度过大而震荡。预热之后保持0.001训练12个epoch再在最后3个epoch把学习率降到0.0001做精细收敛。这个“阶梯下降”比一直用固定学习率效果好很多尤其是到了后半段损失曲线会出现一个明显的二次下降mAP的上涨也主要发生在那里。Batch Size值得多说一句。显存不够时很多人会直接把Batch Size从8降到2却没有同步调整学习率。根据线性缩放规则Batch Size减半、学习率也最好跟着减半不然小批次下梯度估计噪声更大大学习率很容易让损失曲线在早期就飙升。我自己调整下来Batch Size4配0.001的学习率是合适的。如果你有大显存能跑Batch Size8学习率可以试着放到0.002。4.3 训练过程观察与损失曲线判读训练不是把脚本挂上就完事了我基本每两个epoch都会拉一次损失曲线看情况。RetinaNet的损失打印出来一般包含三部分分类损失、回归损失和总损失。网络初期分类损失占大头因为大量锚框被正确分为背景后剩余的难例也在慢慢学。如果分类损失下降速度过慢大概率是锚框和真实目标匹配数量太少多数锚框都是背景模型没机会学到正样本如果回归损失波动剧烈则要怀疑学习率偏大或某些层的BatchNorm没有正确开启。重点观察指标是验证集上的mAP曲线但这个指标在训练中不是一直单调上涨的。我遇到过第6个epoch时mAP突然倒退排查后发现是验证集里这部分图全是无人机低空斜拍视角训练集里这部分视角的样本不足。于是回到数据层面补了一些斜拍图才把mAP拉回来。这个经验很值钱指标异常时先别急着调模型参数回头看数据是不是存在分布缺口。5. 踩坑记录与调优实录5.1 小目标建筑物漏检不是模型不行是锚框和损失的问题项目进行到一半我遇到最头疼的问题是值班室和附属用房这类小目标漏检率极高。画面上明明有模型就是不框。开始怀疑FPN不够深加了P2层反而降低了精度因为P2层分辨率太高噪声多。后来统计正样本匹配情况发现在默认锚框配置下真实目标与锚框的最大IoU超过0.5的样本占总目标的不到一半也就是说大量小目标没有被Anchor覆盖到位。解决办法是上面提到的在小特征层增加小尺寸锚框同时把较小的长宽比加上。另一个配套操作是把训练时正样本匹配的IoU阈值从0.5放宽到0.45。注意这个阈值是训练期间内部使用的匹配阈值和评估时的mAP阈值是两回事不要混淆。放宽之后模型能学会更多小目标的形状模板漏检明显改善因为紧跟着误检率也还在可控范围内。5.2 教学楼和实验楼互相误认特征相似的分类灾难第二个重灾区是教学楼和实验楼的混淆。“看起来差不多”是对这类问题的第一印象但深入分析后我发现混淆不是因为他们长得一样而是数据里这两类出现的时空分布高度相关不少实验楼紧挨着教学楼甚至在同一张图里挨着出现标注框距离很近锚框经常会同时覆盖到两个目标造成分类分支的学习信号交叉污染。我的处理分为两步。第一步是检查标注质量有些图里两栋楼贴得太近标注框确实有了重叠我统一调整后消除了这部分错误信号。第二步是给分类分支加了一点难例挖掘的权重把预测概率在0.4到0.6之间的锚框在损失中的权重适当调高让模型更专注区分“模棱两可”的样本。这两步做完混淆矩阵里这两类的误判率下降了接近10个百分点。5.3 NMS参数与后处理策略模型输出的一堆推荐框要靠非极大值抑制NMS来去重。默认的IoU阈值是0.5在建筑物密集区域不够用两栋紧挨着的楼输出框重叠度经常超过0.6默认阈值会误杀其中一个。我把训练时的NMS阈值提到0.65推理时的单独设置放到0.7但代价是同一栋楼可能会输出两个框所以又在后处理里加了一个“框合并”逻辑若两个框的欧氏距离小于宽度的40%就合并。另外一个经验是给不同类别设置不同的置信度阈值。教学楼这种大目标0.3的置信度阈值就够了附属用房由于特征细碎阈值提高到0.5能有效过滤误检。这个操作很简单但很有效建议有分类需求的项目都试一下。6. 评估指标与部署落地的一些补充6.1 mAP之外还要看混淆矩阵和单类召回率模型评估不能只看总mAP。我最后得到的mAP0.5大约在0.72mAP0.5:0.95大概0.46数值上看起来还行但分到每个类立刻看出差距图书馆因为玻璃反光导致纹理变化大召回率明显低一截附属用房的分类精度则受小尺寸影响。画混淆矩阵是必须的步骤因为总指标会掩盖类别间的不均衡。我建议项目里至少保存预测结果做一个可视化评估页面把每个类别的预测置信度直方图拉出来看一遍比看十个指标都有用。6.2 推理速度与模型压缩路径训练完的RetinaNetResNet50骨干下在单卡上推理一张1333×800的图大概要80到120毫秒换算下来约8到12帧每秒对于静态图片的校园管理够用但应对无人机巡检的视频流还差点意思。后面可以走TensorRT或者ONNX导出到专用推理引擎的路线能压到40毫秒以内基本满足实时性。这个过程要注意一个坑原始PyTorch模型导出时BoxCoder和AnchorGenerator那部分最好一起封装不然部署到生产环境会出现NMS提前截断导致召回率骤降的现象。6.3 行为准则与人工复核机制最后也是最重要的在真实业务落地里模型预测结果必须经过一个轻量级的人工复核界面。校园建筑物的识别系统不是自动驾驶那样要求毫秒级响应多一步人审可以极大避免“错框、漏框”造成的管理盲区。这个项目最终交付的时候我在界面上增加了一个“模型置信度排序”功能审核员按置信度从低到高快速扫一遍优先处理模型拿不准的样本整体审核效率提高很多。我个人在实际操作中的体会是RetinaNet在校园建筑物识别这种“背景多、目标少、尺度杂、类别像”的场景里确实是一个稳定性很高的选择。它的每个组件都有存在的必要Focal Loss不是炫技FPN也不是堆模块都是冲着实际问题去的。如果你手头正好也在做类似的任务不妨参考上面的实操路径先花时间把数据和锚框处理好再训练也不迟。模型结构带来的增益往往最后都体现在细节的参数取舍里。
阅读完成 · 觉得有帮助?