简介:这是一份面向遥感图像处理、计算机视觉方向研究者与高年级学生的深度学习参考文献,聚焦高分辨率遥感图像中的建筑物识别难题,尤其针对现有方法识别结果过度分割、小目标识别效果不佳的痛点,适合作为语义分割入门与进阶的参考资料。资源为单篇PDF论文,共1个文件,压缩包约2.62MB,内容为《计算机工程与应用》期刊原文,包含摘要、引言、方法设计、实验与结论等完整章节,便于系统研读与引用。论文提出基于SegNet改进的AA-SegNet网络模型,引入增强型空间金字塔池化模块与空间注意力融合模块,以强化特征传播、抑制低级特征噪声并提升小目标学习能力;基于高分二号遥感影像构建数据集开展实验,总体识别准确率达96.61%,在识别率、F1分数与训练时间上均优于SegNet、U-Net、DeepLab-V3。目前已有416人学习,适合希望借鉴网络改进思路、复现对比实验或撰写相关论文的读者参考。
1. 高分辨率遥感图像里的建筑物识别,拼的不是模型大小
拿到一景 0.3 米分辨率的航拍正射影像,先别急着套 U-Net。真正卡住进度的,往往是屋顶反光、树冠遮挡、阴影压边,以及相邻楼栋之间那条只有两三个像素宽的缝。高分辨率遥感图像的建筑物识别,本质是逐像素二分类:每个像素回答“是否落在建筑物轮廓内”。它服务违建排查、国土变更调查、灾后损失评估、三维建模底面提取。适合已经跑通过语义分割 demo、想把这套深度学习流程搬到遥感数据上的工程师,也适合手上有带标注正射影像、正在选主干网络的技术负责人。难点不在网络有多深,而在数据管道、标签质量、推理拼接、边界规整这四件事。
2. 建筑物识别任务的数据底座:从高分辨率遥感影像到可训练标签
2.1 分辨率、波段与瓦片尺寸的取舍
高分辨率遥感图像通常指地面采样距离在 0.1 到 1 米之间的影像,来源包括航拍正射、亚米级商业卫星和无人机拼接图。分辨率越高,建筑物识别越像纹理识别:屋顶的瓦片排列、通风口、水箱、空调外机都会变成干扰项。把 0.3 米影像降采样到 0.5 米再训练,经常比硬上原始分辨率更容易收敛——边界模糊一点,但类内差异也小了很多,模型的泛化误差界反而好看。
波段方面,RGB 三通道是底线,近红外对植被与建筑的可分性帮助很大,能拿到四通道就别只用三通道。近红外在这里的用法是先算植被指数,把树冠遮挡区域标成“不确定”,避免模型把树冠下的建筑学成“不是建筑”,这是新手最容易忽略的一步。
瓦片尺寸的常见做法是 512×512 配 64 或 128 像素重叠。1024×1024 显存吃紧但上下文更完整,256×256 训练快却容易把大楼切碎。经验值:单栋建筑平均占地小于 256 像素时用 512 瓦片;大厂房、连片城中村这种长条目标,用 512 加 50% 重叠。
| 场景 | 建议 GSD | 瓦片 | 重叠 | 说明 |
|---|---|---|---|---|
| 城市密集住宅 | 0.3~0.5 m | 512 | 128 | 楼间距小,重叠一次不够就加到两次 |
| 工业园区大厂房 | 0.5 m | 512 | 256 | 长条目标跨瓦片概率高 |
| 城郊农村自建房 | 0.8~1.0 m | 256 | 64 | 目标小,瓦片太大反而稀释正样本 |
2.2 从 Shapefile 或 GeoJSON 到二值掩膜
标签转换最容易写出静默 bug。常见做法是先按瓦片范围裁矢量,再用 rasterio 的 rasterize 烧录成与影像严格对齐的单通道 uint8 掩膜。关键点是 all_touched 的取值与像素中心点判定,口径不一致会让边界整体偏移半像素,训练时表现为预测轮廓系统性外扩。
import rasterio from rasterio.features import rasterize import geopandas as gpd from shapely.geometry import box def vector_to_mask(tif_path, shp_path, out_path): with rasterio.open(tif_path) as src: # 掩膜必须复用影像本身的仿射变换和尺寸,自己算迟早错位 transform, width, height, crs = src.transform, src.width, src.height, src.crs bounds = src.bounds gdf = gpd.read_file(shp_path).to_crs(crs) # CRS 必须与影像统一 gdf = gdf[gdf.intersects(box(*bounds))] # 只烧录与瓦片相交的几何 shapes = ((geom, 1) for geom in gdf.geometry if geom.is_valid) mask = rasterize( shapes=shapes, out_shape=(height, width), transform=transform, fill=0, dtype="uint8", all_touched=False, # 仅像素中心落多边形内才置 1,避免边界胖一圈 ) with rasterio.open(out_path, "w", driver="GTiff", height=height, width=width, count=1, dtype="uint8", crs=crs, transform=transform, compress="lzw") as dst: dst.write(mask, 1) vector_to_mask("tile_001.tif", "buildings.shp", "tile_001_mask.tif")逻辑说明:rasterize 把矢量几何按地理坐标打点到栅格上,fill=0 保证背景为 0、建筑为 1。all_touched=False 是标准口径;改成 True 会让每栋楼胖一圈,边界 IoU 掉的往往就是这一圈。
参数说明:out_shape 取自影像而不是自己推算,否则瓦片裁切范围一改就全错;to_crs 漏掉的话掩膜会飞到另一个半球;compress="lzw" 对二值图压缩比很高,几万张瓦片的磁盘占用差别可观。
2.3 正负样本分布与瓦片级过滤
建筑物像素在整景里通常只占 5% 到 20%,直接训练会让模型偏向全预测背景,recall 虚高、precision 崩掉。常见做法分三层:瓦片级过滤,把建筑占比低于 5% 的瓦片丢弃或降采样;损失函数层面的正样本加权,放在第 3 章讲;在线难例挖掘,把每个 epoch 里 loss 最高的若干瓦片固定重采样。
瓦片级过滤最省事,代价是丢失纯背景样本,模型在空旷区域的误检会略升。实践中保留 10% 左右的低占比瓦片,能让模型记住“空地长什么样”,误检率下降比多训十个 epoch 有效。
提示:转换完成后随机抽 20 张瓦片做目视叠加,掩膜半透明压在影像上,边界偏移一眼能看出来,比盯着 loss 曲线猜快得多。
3. 面向建筑物识别的主干网络与损失函数选型
3.1 编码器:CNN 系与 Swin 系怎么选
深度学习模型在遥感分割上的差异,往往没有数据质量影响大,但主干选型仍有明确取舍。CNN 主干胜在成熟、显存友好、迁移权重好找;窗口注意力类结构在密集城区(楼挨楼、边界共享)上更稳,代价是显存和预处理复杂度上一个台阶。
| 主干 | 感受野 | 参数量量级 | 边界表现 | 适用场景 |
|---|---|---|---|---|
| ResNet-34 / 50 + UNet | 局部堆叠 | 20~30M | 中 | 数据少,先跑通基线 |
| EfficientNet-B4 + UNet | 局部堆叠 | 20M 左右 | 中上 | 算力受限,要推理速度 |
| Swin-T / SegFormer-B2 | 全局 + 局部 | 30~50M | 上 | 瓦片数够,边界要求高 |
数据少于两千张瓦片时,先用 ResNet 系把整套流程跑通,再换主干做消融;否则你分不清涨点是来自主干还是来自换了损失函数。
3.2 解码器与跳跃连接的处理
UNet 的解码器靠跳跃连接把浅层高分辨率特征接回来,这对建筑物边界至关重要。常见坑是浅层特征通道数太大,直接 concat 会让显存爆掉,所以先做一次 1×1 卷积把通道压到 64 以内再接。
另一个选择是 FPN 式的多尺度融合:把 stride 8、16、32 三层特征都上采样到同一尺度再相加,好处是对大小建筑都友好。密集城区里小目标多,FPN 的召回通常比纯 UNet 高;代价是参数量和推理耗时小幅上升。工业界还有一条路线是用 Halcon 的深度学习工具做分割,标注和训练流程封装得比较完整,适合产线级部署,但自定义损失和结构改动的空间有限,做研究调参时不如 PyTorch 那条路自由。
3.3 损失函数:BCE 与 Dice 的组合写法
单个 BCE 在正样本稀薄时梯度会被背景淹没;单独用 Dice 又会在训练早期不稳定。组合起来是最稳的起点:BCE 负责像素级判别,Dice 负责整体重叠度,正样本权重压一压背景。
import torch import torch.nn as nn class BCEDiceLoss(nn.Module): def __init__(self, pos_weight=2.0, bce_w=1.0, dice_w=1.0): super().__init__() # pos_weight > 1 用来提升正样本像素的梯度占比 self.register_buffer("pos_w", torch.tensor([pos_weight])) self.bce = nn.BCEWithLogitsLoss(pos_weight=self.pos_w) self.bce_w, self.dice_w = bce_w, dice_w def forward(self, logits, target): bce = self.bce(logits, target) prob = torch.sigmoid(logits) # 逐样本算 Dice 再平均,避免大图把小图的贡献稀释掉 num = 2 * (prob * target).sum(dim=(2, 3)) + 1.0 den = prob.sum(dim=(2, 3)) + target.sum(dim=(2, 3)) + 1.0 dice = 1 - (num / den).mean() return self.bce_w * bce + self.dice_w * dice逻辑说明:logits 直接喂给 BCEWithLogitsLoss,内部已经带 sigmoid,不用自己再激活一次,重复激活会让数值剧烈抖动。Dice 的分子分母都加 1.0 是平滑项,防止某些瓦片整张没有建筑时出现除零。
参数说明:pos_weight 从 2 开始试,建筑占比低于 5% 时提到 3 到 5;bce_w 与 dice_w 保持 1:1 起步,如果验证集上边界糊,把 dice_w 提到 1.5 更看重重叠度,代价是收敛略慢。
4. 训练、滑窗推理与后处理:把建筑物识别的 IoU 提上去
4.1 深度学习环境配置与训练超参
环境这一步别照抄别人的版本,按本机显卡驱动对应挑构建,否则大概率卡在 CUDA 不匹配上。
# 1) 建一个独立环境,Python 用 3.10 这一档,主流分割库都跟得上 conda create -n bldg python=3.10 -y && conda activate bldg # 2) 框架按本机 CUDA 驱动挑对应构建,别直接复制别人的命令 pip install torch torchvision # 3) 遥感 IO 三件套:rasterio 读 GeoTIFF,geopandas 读矢量,shapely 做几何运算 pip install rasterio geopandas shapely # 4) 训练辅助:分割模型库、数据增强、进度条 pip install segmentation-models-pytorch albumentations tqdm参数说明:torch 的 wheel 索引要跟驱动匹配,装完先跑一句 torch.cuda.is_available() 自检,返回 False 就别往下走了,白等一晚上。rasterio 依赖 GDAL,用 conda 装通常比 pip 稳。
训练超参的常用起点:
| 参数 | 起点值 | 调整方向 |
|---|---|---|
| 初始学习率 | 1e-4 | 主干用预训练权重时降到 3e-5 |
| 批大小 | 8(512 瓦片) | 显存不够就降到 4,同步调小学习率 |
| epoch | 60~100 | 看验证集 IoU 连续 10 轮不涨就停 |
| 优化器 | AdamW | 数据量大可换 SGD + cosine |
| 权重衰减 | 1e-4 | 过拟合时提到 5e-4 |
| 学习率调度 | cosine | 配合 warmup 3 轮更稳 |
epoch 不是越多越好。遥感数据标注噪声大,训到 150 轮以后模型往往开始拟合标注错误,验证集 IoU 掉、训练 loss 还在降,这时候早停比继续调参有用。
4.2 滑窗推理与重叠拼接
整景影像动辄上万像素,没法一次性塞进显存。滑窗推理加重叠加权是标准做法:预测值在重叠区按到瓦片中心的距离做加权,边缘权重低,中心权重高,接缝就不明显。
import numpy as np import torch def sliding_infer(model, image, tile=512, stride=384, device="cuda"): # image: (C, H, W) float32,已归一化 model.eval() c, h, w = image.shape prob = np.zeros((h, w), dtype=np.float32) weight = np.zeros((h, w), dtype=np.float32) # 汉宁窗做重叠加权,中心权重高、边缘权重低,拼缝自然消失 win = np.hanning(tile) win2d = np.outer(win, win).astype(np.float32) + 1e-6 for y in range(0, h, stride): for x in range(0, w, stride): y2, x2 = min(y + tile, h), min(x + tile, w) patch = image[:, y:y2, x:x2] ph, pw = patch.shape[1], patch.shape[2] pad = np.zeros((c, tile, tile), dtype=np.float32) pad[:, :ph, :pw] = patch # 边缘瓦片补零到整块 with torch.no_grad(): logits = model(torch.from_numpy(pad[None]).to(device)) p = torch.sigmoid(logits)[0, 0].cpu().numpy() prob[y:y2, x:x2] += p[:ph, :pw] * win2d[:ph, :pw] weight[y:y2, x:x2] += win2d[:ph, :pw] return prob / np.maximum(weight, 1e-6)逻辑说明:stride 小于 tile 就产生重叠,重叠区被多次预测,按权重累加再归一化,等于对多个预测做了空间加权平均。补零到整块是为了让模型输入尺寸恒定,避免 BatchNorm 在奇异尺寸下统计异常。
参数说明:stride 取 tile 的 0.5 到 0.75,0.5 接缝最干净但耗时翻倍;win 用 np.hanning,别用矩形窗,矩形窗在瓦片边界权重突变,接缝会以竖条纹形式出现在成果图上。
4.3 后处理:连通域过滤与轮廓规整
概率图阈值二值化之后还有两件事。一是连通域过滤,去掉面积小于 30 像素的碎斑,这些大多是屋顶设备和阴影;二是轮廓规整,把锯齿状边界做一次形态学闭运算再接 Douglas-Peucker 简化,导出到 GIS 时相邻楼的粘连会明显减少。
阈值不要固定 0.5。用验证集扫一遍 0.35 到 0.65,画 precision-recall 曲线挑拐点,密集城区通常落在 0.45 附近,稀疏区域可以到 0.55。
4.4 指标与误差诊断
评估用 IoU、F1、precision、recall 四个就够。诊断时把错误分成三类看:边界带内 3 像素的错分通常是分辨率问题,降采样训练或者加边界损失能缓解;整栋漏检多半是标注遗漏或遮挡,要回查标签;大面积误检往往来自阴影和地砖纹理,加负样本或者引入近红外波段最有效。
5. 建筑物识别成果的工程化:从概率图到可用 GIS 图层
5.1 跨区域泛化的低成本做法
同一模型换到另一个城市,IoU 掉 10 个点很常见,主要是屋顶材质和建筑密度的域差异。低成本做法有两个:训练时做强度类增强,随机亮度、对比度、伽马,模拟不同传感器的成像差异;以及推理前做一次直方图匹配,把新影像的亮度和色调整到与训练集接近,这一步不改模型、不加标注,往往能捡回三五个点。
5.2 导出 GeoJSON 与大图接缝处理
二值掩膜转矢量用 rasterio.features.shapes,转出来是多边形集合,属性里带上置信度均值,方便下游按阈值筛。
| 导出项 | 建议做法 | 注意 |
|---|---|---|
| 坐标系 | 与原始影像 CRS 一致 | 别在中途转经纬度,会引入二次误差 |
| 简化容差 | 1~2 像素对应地面距离 | 容差过大,小楼直接消失 |
| 属性字段 | area、mean_prob | 下游可据此过滤碎斑 |
| 拓扑修复 | 用 make_valid 修自相交 | 自相交多边形在部分 GIS 里打不开 |
整景拼接的接缝除了靠汉宁窗加权,还可以在大图推理时把瓦片边界向两侧各扩 64 像素的缓冲区,预测后再裁掉缓冲区,接缝处的上下文更完整,长条形厂房的断裂会少很多。真正上线前,把模型输出按行政区分块统计建筑面积,和人手核对过的参考值比一比,量级对不上就说明阈值或者后处理某一步偏了,这比单看 IoU 更能暴露系统性问题。
本文还有配套的精品资源,点击获取