去年接了一个某高校校园建筑普查项目,需求听着简单:让无人机巡拍和地面监控的画面自动识别出校园里每一栋建筑,并且给建筑打上功能标签,从“知道这是一栋楼”升级成“知道这是3号教学楼还是学生食堂”。我们一开始直接套用通用目标检测模型跑了一版,结果相当惨——树冠遮挡、屋顶附属物、教学楼和实验楼长得几乎一样,模型在验证集上的表现完全没有可用性。后来把方案切换到基于RetinaNet的校园建筑物识别与分类系统,从数据标注规范、锚框设计到损失函数一项项重新调,才真正落地。这篇文章会完整梳理这套系统的实践过程,包括模型选型逻辑、数据工程、训练细节、踩坑记录和部署验证,给正在做建筑目标检测、遥感图像识别或校园智能化管理的朋友一个可以直接参考的基线。
1. 项目背景与选型逻辑:校园建筑检测任务为什么绕不开RetinaNet
1.1 校园场景给检测模型出的三道难题
校园建筑物识别看起来是通用目标检测的子集,但真正做起来会发现它比普通的目标检测场景更刁钻。
第一,类别定义是功能性的,不是物理外观性的。校园里要识别的是教学楼、实验楼、宿舍楼、食堂、图书馆、体育馆、行政楼、校医院等,但功能标签和视觉外观并不是一一对应的。物理上,教学楼和综合楼可能长得一模一样,唯一的区别是门口挂的牌子;实验楼很多带明显的排气井、冷却塔和密集窗墙,但老校区里也有把实验功能塞进普通教学楼的情况。模型看到的只是像素,它没法像人一样去读楼门口的标牌,这就要求训练数据必须把同类建筑的各种外貌变化都覆盖到。
第二,正样本占比低且尺度跨度大。一张校园航拍图里,建筑区域的像素占比可能只有10%到20%,剩下全是操场、绿地、道路、树木和阴影。用交叉熵损失直接训练一阶段检测器,大量易分类的背景样本会把损失函数淹没,模型学到的是“这里没有建筑”而不是“建筑长什么样”。同时,无人机俯视视角下建筑尺度浮动明显,地面监控画面里一栋楼可能只占据很小的区域,多尺度问题比一般目标检测更要命。
第三,相似类之间的边界模糊。宿舍楼和公寓式办公楼几乎无法从外观区分,体育馆和大型报告厅都拥有大跨度屋顶,实验楼与教学楼的窗墙比差异也不稳定。这种类间相似会造成持续的分类混淆,不是简单加大训练轮数能解决的,需要在数据采样策略和分类头设计上做针对性处理。
这三个问题叠加,决定了不能直接用一个通用检测模型就交差,而是需要找一种在类别不平衡和难例挖掘方面有天然优势的模型结构。
1.2 一阶段与二阶段的取舍,以及focal loss为什么重要
当时我们对比过三类方案:二阶段检测器、经典一阶段检测器和基于anchor-free的检测器。二阶段检测器第一轮先做区域提议,第二轮再精细分类,精度通常更高,但训练和推理的工程链路更重;anchor-free模型实现简洁,但在小目标和密集场景下需要额外调中心采样策略。最终选RetinaNet,是因为它精准踩中了校园建筑检测的痛点:用FPN处理多尺度,用focal loss处理正负样本极度不平衡。
RetinaNet的核心贡献就是一个公式层面的改动。在标准交叉熵里,每个样本的损失权重是平等的,大量简单负样本贡献的梯度会压过少量正样本。focal loss引入调制因子:loss = -α(1-p_t)^γ log(p_t)。当预测概率p_t接近1时,(1-p_t)^γ趋近于0,这个样本的损失被大幅压低;当p_t很小、样本难以分类时,调制因子接近1,损失基本保留。这相当于一种内置的难例挖掘,只是它通过连续权重完成,而不是硬性选出top-k样本。
| 对比项 | RetinaNet | 二阶段检测器 | 其他一阶段检测器 |
|---|---|---|---|
| 多尺度特征 | FPN天然支持 | 通常需要额外设计 | 部分支持 |
| 类别不平衡处理 | focal loss直接解决 | RPN+采样缓解 | 依赖采样策略 |
| 推理速度 | 快 | 较慢 | 通常更快 |
| 训练稳定性 | 较好 | 好 | 对超参更敏感 |
实际对比中,我们在相同数据下用某一二阶段检测器做基线,mAP确实高了约1.5个百分点,但推理速度慢了近3倍;而另一个一阶段模型在食堂、校医院这些少样本类别上的AP比RetinaNet低了一大截。用focal loss的RetinaNet在长尾类别上的表现更稳,这一点对校园建筑这种“几栋楼撑起一个类别”的场景太重要了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据集建设:决定模型上限的隐性工程
2.1 类别定义:从12类精简到8类的过程
建数据集的第一步不是打开标注工具,而是想清楚分类体系。我们最初贪多,列了12类:教学楼、实验楼、图书馆、宿舍楼、食堂、体育馆、行政楼、校医院、综合楼、活动中心、附属用房和其他建筑。听起来很完整,训练起来却很痛苦:综合楼和教学楼外观重叠严重,活动中心和体育馆在大屋顶下几乎无法区分,附属用房里面的配电房、车棚、垃圾站形态各异。标注员经常犹豫,标注一致性直线下降。
后来参考传统校园规划的用能分类习惯,把类别压缩成8个:教学楼、实验楼、宿舍楼、食堂、图书馆、体育场馆、行政办公楼、其他建筑。“综合楼”被并入教学楼,“活动中心”被并入体育场馆,“附属用房”全部归入其他建筑。类别数量减少后,每个类别的样本量更集中,标注分歧明显减少,模型在多数类别上的AP提升了2到3个百分点。这条经验我觉得适用于绝大多数建筑分类任务:先保证类别之间的视觉可分性,再考虑管理的精细度。
2.2 多视角采集与标注边界规范
校园建筑的数据来源主要有三个:无人机航拍、卫星影像和地面巡查拍摄。无人机俯视图能看清屋顶结构和建筑平面轮廓,适合识别宿舍楼的规则排列和教学楼的内庭院;卫星图覆盖范围大但分辨率有限,只能用于验证模型的跨尺度泛化能力;地面图能看到外立面细节,是区分教学楼和行政楼这类相似建筑的关键。
多视角带来的问题是标注框的标准必须统一。第一次标注时有人按照屋顶投影画框,有人按照墙体边界画框,结果同一个楼在不同图像里的标注框形状差异很大。我们最终确定的规则是:统一标注建筑主体在画面中的最小外接矩形;如果屋顶被树冠遮挡,优先根据墙基线和周围地物推断边界;屋顶的楼梯间、冷却塔、水箱不单独标注,只在建筑框内作为语义线索保留;临时板房、施工工棚不算永久建筑,归入“其他建筑”但单独标记一个属性,方便后续做违建检测时的过滤。
这个规范写清楚之后,我们又做了两轮标注一致性抽检。抽检方法很简单:随机抽10%的图让另一个标注员重标,计算同一个目标的IoU低于0.7的框,要求返工。这一步看似枯燥,但它决定了训练数据的天花板,模型后期出现的很多诡异错误,最后追根溯源都是标注边界不一致。
2.3 质检与数据清洗:标注错误才是最大的bad case来源
训练过程中我们发现一个反直觉的现象:验证集loss降不下去,但训练集loss已经很漂亮。第一反应是过拟合,但加了数据增强后没有明显变化。后来做了细致的bad case分析,才发现问题出在数据本身。某一张食堂图上标注框缺了一半,另一张校园鸟瞰图里活动板房被标成了“食堂”,模型自然学到错误映射。
所以我们把数据清洗变成了和模型训练并行的固定流程。每训练完一个中间版本,就导出所有置信度高于0.8但类别预测错误的检测框,以及置信度低于0.3但真实存在目标的漏检框,按图像编号回看标注。几次清洗下来,我们删掉了三百多张有明显问题的样本,修正了两百多个框,模型直接涨了3个点。这个过程给了我很深的印象:调参之前先修数据,大多数时候比调参有效。
3. 训练细节:锚框、骨干网络与收敛策略的调优记录
3.1 锚框设计的统计依据
RetinaNet的默认锚框长宽比是1:2、1:1、2:1,尺寸从32像素逐步递增到813像素。通用目标检测里这个配置基本够用,但校园建筑不适用。板式教学楼和宿舍楼的长宽比经常达到4:1甚至5:1,而塔式办公楼接近1:1,默认锚框对极窄长条形的建筑覆盖严重不足。
我们没有拍脑袋改参数,而是先对训练数据里所有标注框做了统计:计算每个框的宽度高度比,画分布图。结果显示,建筑框的长宽比主峰出现在0.4到0.7之间,也就是宽高比接近1:2到1:1.5;同时有大量长宽比在0.2到0.3之间的极端板楼样本。根据这个分布,我们把锚框长宽比调整为1:3、1:2、1:1、2:1、3:1,并且在FPN的最高层增加一档大尺寸锚框,以覆盖整栋建筑占据画面大部分区域的场景。仅仅这一项改动,宿舍楼和板式教学楼的召回率就提高了约5个百分点。
锚框尺寸的设定也同理。校园无人机的飞行高度通常固定,建筑在图像里的尺度范围相对集中,不需要像遥感卫星图那样覆盖从几十像素到上千像素的跨度。我们用K-Means在统计框上聚类了一组更贴近真实数据的锚框尺寸,替代了原生的线性递增方案,让正样本的IoU分布更集中,匹配到的正样本数量也更多。
3.2 骨干网络、学习率与focal loss参数
骨干网络我们对比了ResNet50和ResNet101。ResNet101在验证集上mAP高出约2个点,但推理耗时增加了将近30%,对于最终要跑边缘设备的项目来说代价太高。保守起见选择ResNet50作为主干,后续通过提升输入分辨率和数据增强来弥补精度。如果你的场景不要求实时推理,且建筑尺度偏小,可以换ResNet101;如果是边缘部署,可以用MobileNet系列作为backbone,但要注意FPN和检测头才是参数量的大头,只换骨干不见得能瘦身多少。
训练超参上,我们用了SGD优化器,初始学习率0.01,momentum=0.9,weight_decay=1e-4,batch_size=8,总轮数80轮。前10轮做线性warmup,学习率从1e-3逐步升到0.01,之后按cosine退火衰减到1e-4。focal loss的超参数α取0.25、γ取2.0。这个组合在多数目标检测任务里是稳定结果,我们在建筑场景下也验证过:γ从1.0调到2.0后,少样本类别如食堂、校医院的AP提升明显;但继续调到3.0以上,模型对简单样本过于不敏感,损失曲线开始震荡,反而掉点。
数据增强我们只用了随机翻转、随机尺度缩放(0.8到1.2倍)、色彩抖动和随机裁剪。没有用mixup或mosaic这类会拼接多张图的方法,因为建筑在真实场景里是连续整体,拼接会产生不自然的边界和扭曲的几何结构,对检测器的定位能力可能是负优化。
3.3 训练过程中的观察窗口与早停策略
训练期间我们不像很多人那样只盯着一张loss曲线图。我习惯同时看三个信号:总loss、验证集mAP、以及每一类的AP排序。第一是看训练loss和验证loss的间距,如果验证loss在某一轮开始不降反升,基本可以判断过拟合开始,需要在那一轮前后做early stop;第二是看mAP曲线是否存在震荡,如果mAP反复横跳,多半是学习率偏大或锚框匹配异常,而不是模型能力问题;第三是看类别AP排序,如果某个人数较多的类别AP反而很低,问题大概率在标注不一致,而不是模型不收敛。
因为在第60轮左右验证mAP基本不再上升,我们最后把训练轮数从80轮压缩到65轮,省了不少GPU时间。如果你复现这个任务,建议直接用早停策略,观察验证mAP连续10轮不涨就停止,一般能在第50到70轮之间收敛。
4. 常见问题排查:从accuracy低谷到bad case闭环
4.1 类别不均衡与重采样实践
校园建筑数据集的unbalance问题比预想严重得多。教学楼、宿舍楼动辄上千个标注框,食堂可能只有一百多个,校医院甚至只有三四十个。直接训练的结果是食堂和校医院的AP惨不忍睹,因为训练过程中模型看到这些类别的机会太少,focal loss虽然能压低简单负样本,但无法解决正样本本身稀缺的问题。
我们做了两个层面的处理。第一是图像级别的重采样:给包含少样本类别的图像一个更高的采样权重,让模型每个epoch都尽可能看到它们。具体做法是把少样本类别所在的图像复制若干份并轻微变换,放入训练列表。第二是在focal loss的α参数里按类别频率做权重缩放,给少样本类别更高的正样本权重。这两种手段叠加后,食堂AP从0.35提升到了0.71,校医院也达到0.6以上。不过要注意,重采样不能太猛,否则模型会对少样本类别过拟合,表现在正常场景下反而更容易把其他建筑误判成食堂。
4.2 相似建筑的区分:教学楼和实验楼真的分不清吗
这是整个项目里最困扰我们的问题。教学楼和实验楼在红砖校区里外观极度相似,部分老实验楼甚至就是由教学楼改造来的,外侧看不出任何明显标志。分类错误集中在这两个类别之间,几乎成了系统的主要误差来源。
我们用了几种方式来解决。一是做hard negative挖掘:把置信度较高但分类错误的框提取出来,对每个框做局部裁剪并重新标注,再把这些难样本放回训练集。这相当于主动让模型反复看它最容易错的地方。二是引入屋顶附属物信息:实验楼通常带排风井、冷却塔和各类管道,教学楼顶部相对简洁。我们尝试在检测头的分类分支上增加一个辅助任务,用弱标签方式告诉模型“这一栋楼有没有明显屋顶设备”,但这个做法需要改动模型结构,工程周期较长,最终没有进入正式版本。三是直接调整分类阈值,对教学楼和实验楼这两个混淆对使用更高的阈值,减少低置信度输出,宁可漏检也不胡乱报。
如果你想在类似任务上少走弯路,我建议一开始就把相似类别合并,或者准备充足的hard negative样本,不要指望纯调参能解决视觉上几乎不可分的问题。
4.3 遮挡、阴影与玻璃反射的影响
校园里树木密度高,无人机俯视图经常被树冠切成一块块绿色碎片,建筑边框被遮挡后模型很难给出完整的检测框。我们试过图像预处理里的去阴影算法,效果不稳定,自适应阈值搞不好还会把建筑屋顶的纹理一起抹掉。更有效的方法是利用多帧检测结果做投票:同一栋建筑在连续多帧航拍里都被检出,但某一帧漏了,就通过前一帧和后一帧补齐。监控视角下的玻璃反射则是无解的难题,只能靠扩充不同时间、不同季节的样本,让模型看到同一种建筑在不同光照下的形态。
这些问题的排查顺序,我总结成了一条链路:先看数据是否干净,再看锚框是否覆盖真实目标尺度,然后看损失曲线是否收敛,最后才考虑换模型结构和调超参。按这条链路走,大部分精度低谷都能找到真实原因。
4.4 一条清晰的排查list
遇到mAP不理想时,我的建议是严格按照以下顺序做记录。
- 确认训练loss和验证loss都收敛,剔除学习率和过拟合因素。
- 按类别拆分AP,找到最差的2到3个类别。
- 导出这些类别的全部错检框和漏检框,按错误类型归类:标注错误、遮挡严重、尺度极端、相似类别混淆、目标过小。
- 统计每类错误占比,优先解决占比最高的一个。
- 如果标注错误占比超过30%,先修数据再调参。
在项目后期,我们发现大量所谓漏检其实是标注漏标,模型没有错,是评价标签本身缺了目标。这个问题只有通过bad case回看才能发现,光看mAP数字永远不会知道。
5. 部署与验证:检测系统真正跑起来的最后一公里
5.1 评估口径选择:mAP、每类AP与混淆矩阵
校园建筑识别系统的评估不能只看一个总mAP。我们最终在汇报里同时提交了三类指标:整体mAP(IoU=0.5)、每类mAP、以及类别混淆矩阵。mAP=0.5适合位置精度要求不高的场景,如果后续要做建筑面积量算,就得用mAP@[.5:.95],把框的位置敏感度也考进去。
混淆矩阵的作用在相似类别上尤其明显。我们的混淆矩阵显示,教学楼和实验楼之间的错误占了所有分类错误的近半数。这个数字比任何指标都直白,也让后续优化有了明确方向。评估时还应该按视角分组:无人机图、卫星图、地面图分开测评,因为部署后不同来源的图像会走不同处理流程,如果只看总指标,地面图上的糟糕表现会被无人机图的优秀表现掩盖。
5.2 推理加速与边缘部署的实际收益
RetinaNet的原始PyTorch模型在GPU服务器上推理一张约1300像素的航拍图需要约90毫秒,对月度巡检场景完全够用。但考虑到现场可能只有一台小型边缘工作站,我们尝试了TensorRT导出FP16,推理时间降到50毫秒左右,mAP下降不到0.5个百分点,相当划算。导出时要注意把anchor解码和NMS操作也放进TensorRT引擎,避免每个batch都在CPU和GPU之间来回传输检测框数据,瓶颈会明显降低。
如果完全依赖CPU推理,可以在ONNX Runtime上跑,但速度会比较慢,一张图可能要200毫秒以上。对于非实时任务勉强能用,实时视频流就别指望了。我们还试过把骨干换成轻量化网络,检测头保持原结构,模型体积缩小了近一半,但小目标的AP下降比较明显,最终没有采用。
5.3 实际运行中的一个巡检案例
系统上线后第一个完整运行月,我们跟着无人机飞了一次某高校园区,单次任务采集约2000张航拍影像,在GPU服务器上跑完整个检测流程耗时约4分钟。输出结果不仅包含每栋建筑的类别和置信度,还通过eigenvalue分析把相邻帧同一建筑的检测框做了合并,最终生成了校园建筑分布图。
人工核对结果表明,检测系统在宿舍楼、教学楼这类主体建筑上的识别准确率很高,能够满足每周一次的安全巡查和资产盘点需求;但在校医院、活动板房这类少样本或非永久建筑上,输出仍需要人工二次确认。这让我意识到,建筑物识别系统的目标不是完全替代人工,而是把大量重复性识别工作自动化,让人只处理模型低置信度的少量结果。
6. 如果让我重做一遍,最先改的三件事
项目收尾后,我对整套方法有了更清醒的认识。如果现在重开一个类似的校园建筑识别项目,我会在第一时间做三件事。
第一,开工第一天就把类别精简到5到6个大概率可分的大类,而不是先把12个功能类别列全。类别粒度可以在系统上线后逐步细化,但训练基线必须建立在干净、均衡的数据上。第二,数据完成标注后先做一次锚框长宽比统计,有了这个统计再去修改模型配置,而不是等模型跑完一轮才发现默认锚框覆盖不了板楼。第三,把bad case可视化流程从第1天就搭建起来,每次训练迭代后自动导出低置信度、错检、漏检的样例图,让数据问题及时暴露。这样才能让模型调试的每一轮都建立在事实基础上,而不是猜测上。
这套基于RetinaNet的校园建筑物识别与分类系统,最终没有用特别复杂的技巧,也没有堆特别深的网络,最大的调整其实都在数据和工程规范里。希望这篇实践梳理能帮你少走一点我们走过的弯路,在有类似任务时,能先抓住主要矛盾。
