前阵子项目组接到一个让我印象挺深的任务:对X市某片区做一次街道空间快速摸底,要输出一份可直接用于后续规划分析的街景语义分割结果。原始数据是无人机在50m高度飞出来的低空街景影像,模型方面最终选用的是高精度mask2former。整个过程从数据预处理、标注、训练到工程化落地,踩的坑不少,但最终拿到的分割结果和效率都超出预期。这篇东西就把这段实操经历完整拆开讲讲,适合正在做遥感影像语义分割、或者想把手头分割任务换到Transformer路线上的朋友参考。
1. 为什么盯上50m低空视角:任务背景与数据形态
1.1 任务来源:街道空间量化摸底到底要什么
这次项目来自一个城市更新方向的合作方,他们需要在短时间内搞清楚一片老城区的街道空间现状,具体来说就是几个指标:道路实际宽度和占道情况、人行道连续性、沿街绿化覆盖率、停车位是否侵占步行空间、建筑退离道路的红线距离。这些指标靠人工现场踏勘效率太低,靠人眼盯卫星图又看不清细节,所以自然的方案就是用无人机低空拍摄,再做语义分割,从分割结果里直接统计。
这里要厘清一个概念:标题里说的“50m街景”,不是Google那种车载水平视角的街景,而是无人机在离地大约50米高度以正视略带俯角拍摄的影像。这个高度下,单像素分辨率大概在2到3厘米,能看清行道树冠、路缘石、车顶、甚至地面标线。它介于车载街景和卫星遥感之间,既有足够的地面细节,又能一次覆盖比较大的街道范围,非常适合做街道尺度的定量分析。
1.2 三种数据源对比:为什么低空航拍更合适
我最初其实考虑过直接用已有的车载街景图来跑分割,后来对比了一下三种数据源的特性,才发现低空航拍在这个场景下是不可替代的。
车载街景的优势是贴近地面、能看到建筑立面细节,但问题也很明显:视角完全水平,路面和行道树互相遮挡严重,车辆、行人角度随镜头变化,分割模型在这种视角下很难保持类别一致性;而且车载数据通常按道路中心线采集,背街小巷、人行道内侧根本拍不到。
卫星影像或者说正射影像的优势是覆盖均匀、几何关系简单,但分辨率普遍不够,0.3米已经算高分,想从里面分出人行道和停车位基本不可能。另外卫星影像只有俯视视角,对高度信息毫无感知,沿街建筑立面会被压扁成屋顶。
低空50m航拍正好卡在中间,俯角带来了一定的立面信息,又能保持2-3cm的分辨率,单张影像覆盖范围约60m×80m,一个小区块只需要几百张影像就能全覆盖。我可以直接放一个当时内部对比用的表格,大家感受会更直观。
| 维度 | 车载街景 | 卫星遥感 | 50m低空航拍 |
|---|---|---|---|
| 地面分辨率 | 厘米级 | 分米级至米级 | 厘米级 |
| 视角 | 水平、遮挡多 | 纯俯视 | 略带俯角 |
| 道路连续性覆盖 | 中线为主,缺少背街 | 全覆盖 | 全覆盖 |
| 建筑立面信息 | 丰富 | 基本缺失 | 部分可见 |
| 阴影时长 | 受建筑遮挡影响大 | 受时间影响大 | 可通过航时控制 |
| 采集成本 | 中等 | 低 | 中等偏高 |
1.3 50m视角下的成像特点与第一个直觉判断
低空影像看起来直白,但真正跑模型时就会发现它的成像规律和常规数据集完全不一样。首先是遮挡规律:树冠从上往下压,很多路灯杆、交通标志藏在树缝里,模型需要学会“猜”被遮挡的部分,这与水平视角下物体互相遮挡的形态差别很大。
其次是尺度分布很极端。一条主干道上的双黄线在影像里可能占几十个像素宽,而一个行人只有十几个像素。同一个类别在不同路段上的尺度差异能到十几倍,这对模型的scale robustness要求很高。还有一个讨厌的问题是玻璃和车顶反光,太阳角度合适的时候,一大片车窗高光过曝,纹理信息完全丢失,分割模型很容易把它误判成天空或水体。
基于这些观察,我当时的第一判断就是放弃直接用ImageNet预训练分类模型当backbone然后接FCN的老路子,而是转向自带mask机制的Transformer分割模型。原因后面详细说,但当时的直觉是:低空街景中的对象高度粘连、边界模糊,逐像素分类方法特别容易在边缘和遮挡区“吵架”,而mask分类思路天然更擅长处理这类问题。
2. mask2former为何能扛住这种“非典型街景”
2.1 从逐像素分类到mask分类:一个关键思维转变
传统的语义分割思路,不管DeepLab系列还是FCN,本质上都是逐像素分类器。每个像素独立预测一个类别,像素之间的关联全靠上下文模块(比如ASPP、PPM)去建模。这种思路在平坦视角的城市景观数据集上表现不差,但放到低空俯瞰场景就会暴露问题:树冠和道路的边界、车辆之间的间隙,这些像素在颜色特征上高度接近,逐像素分类很容易出现“棋盘格”式的抖动脉冲。
Mask分类的范式完全不同。它先把图像里面可能存在的“物体/区域”预测成一组二值mask,然后给每个mask分配一个类别标签,语义分割结果就是“类别标签+对应mask”的组合。这样类别推断和空间边界切分被解耦了:边界由mask质量决定,类别由语义特征决定,互相不拖后腿。
MaskFormer是这套范式的代表工作,它通过一组learned query来解码图像特征,每个query通过Transformer decoder生成一个mask embedding,再和特征图做点积得到mask。Mask2Former则是对这个范式的工程化加强版,在效率和精度上都明显更优,这也是我们最终选定它的核心理由。
2.2 Mask2Former的三个关键改进:masked attention、多尺度decoder、query设计
要理解Mask2Former为什么适合低空街景,先看它的三个核心改动。
第一个是masked attention。传统Transformer decoder的cross attention是每个query对整张特征图做注意力,计算量巨大,而且query会被全局无关区域干扰。Mask2Former的思路是:在每一层decoder里,先用当前query预测出一个初步mask,然后cross attention只在这个mask的前景区域内计算。换句话说,每个query只需要关注它负责的那一小块区域,被遮挡的阴影区、远处的背景就不会来“抢戏”。对于低空影像里大量存在的树冠压路灯这种局部遮挡,这个机制特别友好,query可以专注在目标区域的可见纹理上。
第二个是多尺度decoder。Mask2Former的decoder分成多层,分别处理1/32、1/16、1/8分辨率的特征。这样小目标(行人、锥桶、井盖)在大分辨率层有足够的空间细节,大目标(道路、建筑)在深层的语义特征上也有足够的上下文。我实测下来,这种从粗到细的逐层精修方式,对提升低空影像里小目标的召回率非常明显。
第三个是query设计的简化。Mask2Former去掉了object query里加的positional embedding,让query完全靠内容来学习空间位置关系。这一点在低空街景里很实用,因为不同街区的对象分布规律差别大,固定位置先验反而会拖累泛化。
2.3 与备选模型的对比:为什么不是DeepLabV3+也不是SegFormer
项目早期我其实试过两条路:DeepLabV3+配ResNet-101,以及SegFormer配MiT-B3。这两条路在常规遥感分割里都有不少成功案例,但在我们这批数据上的表现都不够理想。
DeepLabV3+的问题在于感受野和细节的平衡。ASPP虽然能捕获多尺度上下文,但本质上还是膨胀卷积堆叠,遇到树冠和路面边缘这种高频交界处,输出边界会出现明显的锯齿和色块溢出。SegFormer虽然用了分层Transformer,注意力是全图计算,区分能力不错,但计算量偏大,而且它没有显式的mask建模,在目标粘连严重的区域,类别切换处的误标还是偏多。
我在这里放一张基于小验证集跑出来的对比,注意这只是我们的内部实验,不代表绝对排名,但足够说明选型方向。
| 模型 | 输入尺寸 | mIoU(验证集) | 单张推理耗时(无优化) | 边界完整度评分 |
|---|---|---|---|---|
| DeepLabV3+ (ResNet-101) | 512x512 | 67.8 | 61ms | 中 |
| SegFormer (MiT-B3) | 512x512 | 72.4 | 78ms | 中高 |
| Mask2Former (Swin-T) | 512x512 | 76.1 | 52ms | 高 |
Mask2Former在边界完整度上明显占优,这跟masked attention的设计直接相关,它天然倾向于生成闭合且平滑的mask区域,而不是在像素级别反复横跳。
2.4 低空街景特有的“遮挡-反光-尺度”三重难题如何被mask机制缓解
低空街景最难处理的三个现象,我在前面提到过:树冠遮挡路灯、车窗反光产生高光区、同类目标尺度极端不一。传统逐像素分类面对这三个问题时,要么依赖后处理CRF来修边界,要么用多尺度输入硬抗,效果都一般。
Mask2Former的mask分类思路等于把“我要分割出什么”和“这个区域属于什么类别”分开解决。对于遮挡场景,mask的重建靠的是query对局部可见纹理的一致性感知,边缘即使被遮挡,query仍然可以延续可见部分的mask走向,而不是在遮挡处突然断掉。对于反光高光区,mask本身不会因为局部亮度异常就分裂成多个碎片,类别注意力仍然会把这些区域归并到同一类别。
尺度问题则是多尺度decoder的强项,深层特征负责道路、建筑这类大目标,浅层特征负责人行道、车位线这类中等目标,1/8层专门负责行人和车辆。每个尺度上的query数量虽然固定,但masked attention保证了每个query的有效计算只发生在它对应的目标区域,不会因为小目标周围有大片背景就浪费注意力。
3. 标注规范与预处理:决定精度上限的地基工程
3.1 影像预处理流程:匀光匀色、投影与切片
分割模型的精度上限很大程度上在标注质量阶段就锁死了,模型只是无限逼近这个上限。所以预处理环节我一点不敢糊弄。第一步是对原始航片做匀光匀色处理,消除不同架次之间的光照差异。无人机航拍经常跨时段采集,有的影像偏亮有的偏暗,直接丢进模型会把亮度当成类别特征学进去,导致推理时遇到新亮度就翻车。
我用的方案是用一个基于灰度世界假设的自动白平衡加直方图匹配,把所有影像的亮度分布对齐到参考影像。这一步不需要深度学习,OpenCV里面几个函数就能搞定,但对后面训练稳定性帮助很大。
然后是投影转换。50m高度拍摄的影像带有轻微透视形变,如果要用分割结果去做面积统计,这个形变必须处理。我们是将影像做无人机自带的POS数据输入到正射校正软件,输出带地理坐标的TIFF。不过这里要提醒一点:正射校正过程会引入重采样误差,对道路边缘这类高频区域有轻微模糊,所以校正后分辨率要控制在原影像的1.5倍以内,别为了追求像素对齐过度重采样。
最后是切片。整张航片太大直接输入显存必爆,所以切成统一尺寸的小块。我们用的是1024×1024,步长768,即重叠率25%。这个值是我试出来的,重叠太少会导致对象被切碎,训练时模型学习不到完整的道路延展;重叠太高则数据冗余,训练效率下降。切片时要保留地理坐标信息到文件名,否则后面可视化叠加会很痛苦。
3.2 类别体系定义:从实际分析需求倒推
这一步比大多数人想象的更重要。类别定义不是越多越好,而是要和下游分析指标严格对应。我们最后定了9个类别,每一个都能直接映射到规划指标。
| 类别 | 定义与包含物 | 下游用途 |
|---|---|---|
| 道路 | 机动车道、非机动车道、路面标线区域 | 道路宽度、占道率 |
| 人行道 | 铺装步行道、盲道、路缘石以上步行区 | 步行连续性 |
| 建筑 | 建筑物轮廓、裙房、门头 | 建筑退距 |
| 植被 | 行道树、灌木丛、草地 | 绿化覆盖率 |
| 车辆 | 机动车(含停放和行驶状态) | 占道停车统计 |
| 行人 | 步行中的人体 | 人流密度估算 |
| 非机动车 | 自行车、电动自行车 | 非机动车停放区识别 |
| 护栏/围墙 | 交通护栏、围墙、隔离带 | 街道界面连续度 |
| 其他 | 背景、杂物、无法归类的区域 | 误差兜底 |
这里特别想提“其他”这个类别的必要性。很多项目为了好看,不设背景类,结果模型在遇到井盖、施工挡板、垃圾堆时强行归到邻近类别,把统计指标全污染了。我们保留了“其他”类别,训练时它会吸收大量不确定区域,反而让有效类别精度更高。
3.3 标注工具与协作流程:双重质检不可省
标注工具我们用的是一套开源的多边形标注软件,支持加载GeoTIFF并导出COCO格式的JSON,也支持RLE格式的mask导出。标注人员就三个人,我定了一个规则:每张切片必须由标注者A完成初标、标注者B做二次修正,不一致区域直接合并到“其他”。这种双重质检模式虽然慢,但能保证边缘标注的一致性,对训练收敛帮助极大。
我还用了半自动辅助标注:先用初版模型对未标注区域做预测,标注人员只需要修正错误mask而不是从零画多边形,整体标注速度能提升两倍左右。这个做法很推荐,属于典型的“先用弱模型降低人工成本”思路,但要注意初版模型的错误会“教坏”标注人员,所以修正环节必须认真。
3.4 类别不均衡的应对策略
低空街景的类别分布极度不均衡:道路和植被可能占60%以上,行人和非机动车加起来不到2%。如果直接训练,模型会为了降低总loss把行人全忽略掉,这在指标上就是行人类别IoU趋近于0。
我做了三件事来缓解。第一,对每张切片按类别分布做采样:如果切片里包含行人或非机动车,就提高它的采样概率,保证每个batch里至少有1-2张小目标密集的样本。第二,loss层面给少数类加权重,权重取类别频率倒数的平方根,而不是直接取倒数,避免权重太大导致震荡。第三,标注时对行人和非机动车的mask做一点膨胀,相当于给模型更多“正样本”去学习,这个处理会在评估时报告真实IOU,因此不会作弊,只是让训练更容易起步。
4. 训练实录:从Baseline到稳定收敛的调参过程
4.1 环境与框架选型
模型训练我们用的是MMSegmentation这个开源工具箱,它内置了Mask2Former的完整实现,配置起来比从原版代码仓库自己搭要省事很多,尤其是不用自己处理DDP分布式训练和数据加载的细节。
硬件环境是4张消费级显卡,显存24GB,PyTorch 2.0以上,CUDA版本对应驱动即可。这里有一个容易被忽视的点:Mask2Former对CUDA编译有依赖,如果编译不过,很大概率是CUDA和PyTorch版本不匹配,别折腾源码,直接换满足官方要求的组合。
需要说明的是,下面配置是我在模拟项目X里反复调出来的结果,不一定对所有人的数据都最优,但作为起点非常稳。
4.2 关键配置解读
backbone选了Swin-Tiny,不是Swin-Base。原因很简单,低空街景整体范围大但单体目标不算多,Swin-Tiny在精度和速度之间是最划算的选择,实际上跑出来的效果比Swin-Base差不到1个点mIoU,但推理速度快了40%。
输入尺寸512×512。按理说1024×1024保留了更多细节,但训练时batch会急剧下降,而且Mask2Former的masked attention对尺度比较敏感,输入太小query学不到有效区域。512是一个性价比极高的平衡点,结合测试时TTA,基本能抵消分辨率损失。
优化器是AdamW,初始学习率6e-5,weight decay 0.05,用的poly学习率衰减策略,power设为0.9。总训练轮数设了12万步,但实际上到8万步就基本收敛,后面靠验证集监控做了早停。
4.3 Loss设计:为什么CE和Dice一个都不能少
Mask2Former默认用的是交叉熵加Dice loss的组合。交叉熵是逐像素的分类loss,它提供稠密的梯度信号,让mask的每一个像素都往正确方向推。Dice loss则是直接优化mask和真实mask的重叠度,它天然对前景和背景的比例不敏感,在少数类样本上作用极大。
我实际感受是:单独用CE,mask边缘会有一点模糊,尤其在人行道和道路交界处,CE只会告诉模型“这一块更可能是人行道”,但边界具体在哪个像素,CE给不出明确约束。加上Dice之后,边界处的梯度会显著增强,因为Dice是对整个mask区域做评估,边界错了整体重叠率就下降。两相叠加,边界从“像素级猜测”变成了“区域级锁定”。
4.4 训练过程中的曲线观察与过拟合信号
训练初期的loss下降非常快,前5000步loss能掉一半,这时候mask已经开始有雏形,但类别还是乱的,很多mask被归到“其他”。不要急,这是query还在“找活干”的阶段。
1万到3万步之间,主干道的mask开始稳定,车辆和行人偶尔出现碎片化的mask。3万步以后才会看到边界质量明显改善,行人和非机动车这类小目标开始稳定出现。如果这个阶段小目标还是忽有忽无,多半是数据采样策略没做到位,而不是模型结构问题。
过拟合信号我也观察到了:训练集mIoU突破到88以上,验证集却停在76不动,同时验证集上的边界抖动明显。解决方法有两个,一是加大数据增强的强度,尤其是颜色抖动和随机裁剪;二是把dropout稍调大一点。
下面是训练中比较关键的参数表,做一个记录。
| 配置项 | 设定值 | 备注 |
|---|---|---|
| Backbone | Swin-Tiny | 也可换ResNet-50,精度略降但速度更快 |
| 输入尺寸 | 512×512 | 推理时可切回1024 |
| Batch Size | 16(4卡×4) | 太小会导致query收敛不稳定 |
| 优化器 | AdamW | lr 6e-5,betas默认 |
| LR策略 | Poly Decay | power=0.9 |
| Loss | CE + Dice | Dice权重建议1.0 |
| 训练步数 | 8万步早停 | 早停阈值看验证集mIoU |
| 数据增强 | 随机翻转、随机裁剪、颜色抖动 | 颜色抖动幅度加大 |
4.5 调参尝试:num_queries、decoder层数和测试时增强
Mask2Former里num_queries的默认值是100,我在低空街景上试过从50到150。100是合理值:50个query在面积小的街区影像上会漏掉一些分散的车辆,150个query理论上能覆盖更多目标,但训练时间增加,而且多出来的query容易变成重复预测同一目标。
decoder层数我试了3层和9层。9层在量化指标上确实更高,但提升幅度有限,大概是0.8个点mIoU,而训练耗时增加了将近一倍,推理也慢了。最后用的是6层,算是个折中。如果推理速度是硬指标,3层也能用,只是小目标边缘会稍微毛糙。
测试时增强我强烈推荐用,尤其是水平翻转和多尺度融合。Mask2Former在低空影像上对翻转的敏感度不高,TTA后推理时间翻了3倍,但mIoU能提升1.5个点以上,这对做评估报告来说很值。如果有时间预算,我一定开着TTA出最终指标。
5. 评估结果与典型失败案例分析:哪些涨点,哪些翻车
5.1 量化指标:验证集上的分项IoU
最终我们在验证集上做了严格评估,这里放的是模拟项目X结果的分项IoU,给大家一个量级参考。整体mIoU走到了79.5,比初版DeepLabV3+高了将近12个点,这个提升主要来自道路边界、建筑轮廓和车辆识别。
| 类别 | mIoU(%) | 相对Baseline提升 |
|---|---|---|
| 道路 | 94.3 | +3.1 |
| 人行道 | 82.7 | +9.8 |
| 建筑 | 91.2 | +4.6 |
| 植被 | 88.5 | +5.2 |
| 车辆 | 73.9 | +18.4 |
| 行人 | 48.6 | +22.3 |
| 非机动车 | 41.2 | +19.7 |
| 护栏/围墙 | 76.8 | +8.5 |
| 其他 | 77.4 | +6.2 |
可以看到,涨幅最大的正好是车辆、行人、非机动车这几个少数类别,这跟Dice loss和数据采样策略直接挂钩。道路这类主类别涨幅不大,因为Baseline就已经很高,边际效用有限。
5.2 场景维度分析:主干道、老城区窄巷与树荫路段
只统计全局mIoU会掩盖很多问题,我做了一个按场景拆分的分析。开放式主干道的表现最好,道路IoU接近96%,因为道路开阔、遮挡少,mask分类能轻松建立完整区域。
老城区窄巷是最大的坑,道路IoU掉到70以下。原因在于两侧建筑和植被挤压,路面宽度只有五六米,再加上停放的车辆、堆放的杂物,道路区域被切割成很碎的小块,mask的空间连续性被石墩、电线杆打断。模型在窄巷里经常把阴影下的路面误判成人行道,这个偏差对道路宽度统计影响不小。
树荫路段则是“遮一半不遮一半”的典型场景。树冠在路面上投射大面积阴影,阴影里的纹理和光照衰减让模型倾向于把沥青路面识别成植被边缘或土壤。后来我加了一条经验:非常深的阴影区域如果周围邻域是明显的道路特征,模型应倾向于归类为道路。这句话看似废话,但体现在训练集上就是要多标注这些阴影下的道路区域,让模型学到“阴影只是亮度变化,类别不变”。
5.3 典型失败案例拆解:玻璃反光、低矮植被和行人重叠
玻璃反光是低空街景特有的错误来源。某栋建筑的弧形玻璃幕墙在午后阳光照射下,产生一片高光,模型把这整片归成了“其他”。失败的根本原因是训练数据里反光样本太少,模型没见过这种视觉模式,自然不敢归类。缓解方法是在数据增强阶段加入随机高光模拟,这个操作虽然简单,但能有效提升模型对反光区域的鲁棒性。
低矮植被也是常见误判源,尤其是草坪和灌木丛在50m高度看,纹理和部分深色路面很像,模型会把它误划为“其他”或“道路”。这个问题的本质是低矮植被缺乏高度信息,纯RGB图像确实难以区分。我们用的解法是给这类样本做类别权重提升,同时标注时统一规则:只要有绿色色相特征的区域一律归植被,避免标注不一致。
行人重叠是所有人像检测的经典难题,在低空场景更突出。树荫下、车辆旁边、斑马线上,行人的mask经常被周围物体吞掉,或者行人和非机动车连成一片。我注意到一个小规律:模型对单独行走的行人IoU能达到55,但一旦有遮挡就直接掉到30以下。这说明mask重建对遮挡的泛化还没完全解决,只能靠增加遮挡样本来继续改善。
5.4 错误来源归类与下一步改进方向
把验证集上预测错误的像素按来源归一下类,大致是有三个大头:第一是阴影和光照变化引起的误判,约占38%;第二是物体遮挡导致的mask断裂,约占31%;第三是类别定义本身的灰色地带(比如人行道和建筑入口的过渡带),约占25%;剩下6%是标注本身的错误。
针对第一类,下一步可以考虑加红外通道,利用热红外图像区分阴影下的道路和植被,这是比较成熟的思路,但需要额外采集数据。针对第二类,可以考虑引入时序信息,用同一地点的多帧影像做时空融合,让被遮挡的区域由前后帧补全。第三类是纯标规范题,把类别边界定义再细化就行。
6. 工程化落地:切片推理、加速与可视化交付
6.1 从PyTorch到ONNX再到TensorRT:推理链路搭建
模型训练完只是开始,实际交付要求的是能批量跑完整个片区,还得快。我们先用ONNX把PyTorch模型导出,然后转成TensorRT的FP16引擎。
转换过程中最容易翻车的点是动态尺寸。Mask2Former的输入尺寸在导出时如果设成动态,TensorRT会编译失败或极度变慢。我们的做法是固定成512×512,推理时再配合切片策略。如果想保留多尺度TTA,就分别导出几个固定尺寸的引擎,运行时候轮流用,代价是多占显存。
6.2 重叠切片推理与加权融合:消除边界撕裂
整幅影像不能直接推理,需要切片。但直接硬切会导致一个对象被分成两半,每一半可能被预测成不同类别,拼图时接缝处出现明显撕裂。
解法就是我在预处理阶段提到的重叠切片,推理后做加权融合。具体做法是:每个切片的预测概率图,中心区域的权重设为1,边缘区域权重逐步降到0,重叠部分按权重取最大类别。这样接缝处的类别由两边的预测共同决定,而不是某一侧单独说了算。实测下来,融合后的边界连续性比硬拼接好了非常多,mIoU能再提升0.8个点。
让我贴一下当时用到的融合伪代码,非常简短,却极其关键。
import numpy as np def weighted_merge(prob_map, weight_map, acc_map, weight_acc_map): # prob_map: [C, H, W] 某个切片预测概率 # weight_map: [H, W] 三角权重,中心高,边缘低 acc_map += prob_map * weight_map[None, :, :] weight_acc_map += weight_map[None, :, :] # 推理所有切片后 final_prob = acc_map / (weight_acc_map + 1e-6) final_mask = np.argmax(final_prob, axis=0)6.3 推理耗时与显存占用实测数据
我们测了三种引擎的推理表现,记录如下。
| 引擎 | 输入尺寸 | 单张耗时 | 显存占用 |
|---|---|---|---|
| PyTorch FP32 | 512×512 | 52ms | 4.2GB |
| PyTorch FP16 | 512×512 | 39ms | 2.8GB |
| TensorRT FP16 | 512×512 | 16ms | 1.5GB |
配套切片推理的逻辑,我们最终跑完整片大约3.2平方公里的区域,总共629张原始航片,切出2.5万余张推理切片,耗时约12分钟。这个效率完全满足交付要求。如果不用TensorRT,光是PyTorch推理,同样的数据量要跑40分钟以上,差距还是很明显的。
6.4 分割结果的可视化与地理叠加
工程交付不能只给一堆mask图,还得让应用方能直接看。我们把每张切片的预测类别加回地理坐标,转成带类别的GeoJSON,然后在Web端的地图上叠加展示。这样规划人员可以直接在地图上点选任意街道,查看对应路段的语义分割结果,并自动统计道路宽度、绿化覆盖率等指标。
这里提一个实操中的小坑:GeoJSON文件如果包含所有小目标的多边形,会特别大,加载起来浏览器要卡半天。解决方法是按类别做简化——道路、建筑、植被保留全精度轮廓,车辆、行人这类小目标用中心点加外接框表示。这样文件体积能压缩70%以上,而分析不受影响。
6.5 部署阶段遇到的两个实际烦恼
第一个是时序闪烁。我们采集了两个不同日期的同一街区影像,用同一个模型推理,发现车辆和停靠区域的分割结果差异极大。这是意料之中的,毕竟车辆是动态的。但在汇报时如果拿两期结果直接对比,会造成“模型不稳定”的误解。后来所有汇报和交付都统一用同一天的基准影像,并明确标注采集时间。
第二个是模型对未见过的道路材质的脆弱性。我们在一个新区测试时,遇到深红色透水砖铺装的人行道,模型居然把它归成了“道路”。后来查原因,训练集中没有这种颜色的人行道样本。这说明当前数据集的材质多样性还不够,需要继续补充采集。
7. 最后再分享一点项目体会
如果用一句话总结这次实践,我会说:50m低空街景的语义分割,关键不在模型多新,而在于数据视角适配和工程细节的打磨。Mask2Former确实在mask边界质量和小目标召回上具备天然优势,但真正让mIoU从68涨到79的,是切片策略、标注一致性、Dice loss和TTA这些看似琐碎的细节。
我个人最想提醒后来者的一件事,就是千万别忽视“其他”类别的标注规范,以及不要为了纯指标好看而把困难像素强行归到有效类别。这个项目做下来,我最大的体会是语义分割交付给规划业务方时,指标数字是次要的,分割结果能否稳定支撑下游统计才是核心。只要边界不撕裂、类别不串味,哪怕整体mIoU不是最顶尖,也是一份可用的成果;反过来,如果车辆和行人这类小目标频繁漏检,哪怕道路分割跑到98分,报告也没法写。
希望这份记录能给大家在类似低空遥感分割项目上提供一些可操作的参考。后续如果拿到带红外通道的新数据,我会再写一篇阴影场景的专项对比,到时候见。