☰
RetinaNet校园建筑物识别实战:从数据处理到部署全记录
2026/10/17 19:40:40 网站建设 项目流程

做目标检测这行久了,手上经过的模型少说也有十几种,可每次接到新需求,还是会先问一句:到底用什么网络最合适。最近我完整跟进了一个校园建筑物识别与分类项目,核心任务是把校区里的教学楼、宿舍、图书馆、体育场馆等建筑从航拍和地面视角的图像中自动找出来,并精确分类。调研一圈之后,我把主力模型定在了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 Network,FPN)。骨干网(我用的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,生态成熟,调试也方便。硬件上我用的单张显卡,显存12GB,Batch 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 Size=4配0.001的学习率是合适的。如果你有大显存能跑Batch Size=8,学习率可以试着放到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。我最后得到的mAP@0.5大约在0.72,mAP@0.5:0.95大概0.46,数值上看起来还行,但分到每个类立刻看出差距:图书馆因为玻璃反光导致纹理变化大,召回率明显低一截;附属用房的分类精度则受小尺寸影响。画混淆矩阵是必须的步骤,因为总指标会掩盖类别间的不均衡。我建议项目里至少保存预测结果,做一个可视化评估页面,把每个类别的预测置信度直方图拉出来看一遍,比看十个指标都有用。

6.2 推理速度与模型压缩路径

训练完的RetinaNet,ResNet50骨干下在单卡上推理一张1333×800的图大概要80到120毫秒,换算下来约8到12帧每秒,对于静态图片的校园管理够用,但应对无人机巡检的视频流还差点意思。后面可以走TensorRT或者ONNX导出到专用推理引擎的路线,能压到40毫秒以内,基本满足实时性。这个过程要注意一个坑:原始PyTorch模型导出时,BoxCoder和AnchorGenerator那部分最好一起封装,不然部署到生产环境会出现NMS提前截断导致召回率骤降的现象。

6.3 行为准则与人工复核机制

最后也是最重要的,在真实业务落地里,模型预测结果必须经过一个轻量级的人工复核界面。校园建筑物的识别系统不是自动驾驶那样要求毫秒级响应,多一步人审可以极大避免“错框、漏框”造成的管理盲区。这个项目最终交付的时候,我在界面上增加了一个“模型置信度排序”功能,审核员按置信度从低到高快速扫一遍,优先处理模型拿不准的样本,整体审核效率提高很多。

我个人在实际操作中的体会是,RetinaNet在校园建筑物识别这种“背景多、目标少、尺度杂、类别像”的场景里,确实是一个稳定性很高的选择。它的每个组件都有存在的必要,Focal Loss不是炫技,FPN也不是堆模块,都是冲着实际问题去的。如果你手头正好也在做类似的任务,不妨参考上面的实操路径,先花时间把数据和锚框处理好,再训练也不迟。模型结构带来的增益,往往最后都体现在细节的参数取舍里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询