Python遥感实战4 | CBAM-ASPP ResU-Net遥感建筑提取的完整技术栈——以银川市 30m 分辨率建成区提取为例
图 1 银川市全域建成区综合制图图注:银川市 Landsat 8 多光谱影像与建成区提取综合图。(a) 真彩色合成 (RGB:B4/B3/B2);(b) 近红外假彩色 (NIR-R-G:B5/B4/B3);(c) 建筑预测概率热力图;(d) 建成区二值叠加结果,全域建筑覆盖率 16.62%。数据源:2021 年 LC81290332021228 OLI 影像,分辨率 30m。
图 2 银川典型建成区细节对比图注:四类典型区域提取细节。上排:原始真彩色影像;下排:预测概率半透明叠加。涵盖老城密集建筑、城市新区、城郊零散村镇、裸地混合地貌,模型可区分裸土与不透水面,漏检、虚警现象控制良好。
图 3 光谱指数空间分布图 4 建筑空间分布一、定量实验核心结果(成果前置・学术标准)
1.1 模型分割精度指标
表格
| | |
|---|
| | 优于传统 NDBI 阈值法 (0.512)、标准 U-Net (0.641) |
| | |
| | |
| | |
| | |
| | |
1.2 银川市空间统计结果
研究区行政范围总面积:53163.24 km²
自动提取不透水面(建成区)总面积:8834.71 km²
城镇建成区空间覆盖率:16.62%
有效观测像元总量:59072146 个
建筑类有效像元:9754321 个
1.3 核心技术产出
标准化 Landsat8 辐射 / 大气预处理流水线,输出带地理坐标 GeoTIFF 与 NPY 数组;
CBAM-ASPP ResU-Net 多光谱专用分割网络,轻 / 中 / 大三档适配不同算力;
标签生成、瓦片采样、断点训练、大图滑窗推理、行政裁剪、SCI 绘图完整工程化代码;
全套可复现代码,支持多时相城市扩张、大范围国土不透水面普查。
二、研究背景与方法创新
2.1 现有方法局限性
光谱指数阈值法
:裸土、荒漠、河滩易与建筑混淆,单时相鲁棒性差,无法全自动批量处理;
基础 U-Net
:感受野单一,无注意力机制,云 / 裸土噪声干扰大,不匹配多光谱遥感特征;
现有开源工具链断层:大多只提供模型,缺失遥感预处理、栅格后处理、行政区裁剪完整链路。
2.2 四大核心创新
标准化 L8 辐射 - 大气预处理
:基于 MTL 元数据辐射定标 + DOS 暗目标校正,BQA 波段剔除云、冰雪无效像元;
残差 + CBAM 注意力编码器
:双注意力强化建筑光谱特征,抑制植被、水体干扰;
ASPP 空洞金字塔瓶颈
:多扩张率卷积捕捉主城区、乡镇、零散宅基地多尺度建成区;
注意力门控跳跃连接
:过滤浅层裸土、云层噪声,提升建成区边界分割精度。
三、完整技术工作流 + 核心代码分段展示
模块 1 preprocess_l8.py Landsat8 预处理核心代码
功能:元数据解析、DN 转 TOA、DOS 大气校正、BQA 云掩膜import rasterioimport numpy as npdefparse_mtl(mtl_path):"""解析Landsat8 MTL元数据,提取定标系数、太阳高度角""" meta = {}withopen(mtl_path, 'r') as f:for line in f: line = line.strip()if'='in line andnot line.startswith('#'): k, v = line.split('=', 1) k = k.strip() v = v.strip().strip('"')try: v = float(v) if'.'in v or'e'in v elseint(v)except: pass meta[k] = vreturn metadefdn_to_toa_reflectance(dn, band_num, meta):"""DN值转换为TOA表观反射率""" mult = meta[f'REFLECTANCE_MULT_BAND_{band_num}'] add = meta[f'REFLECTANCE_ADD_BAND_{band_num}'] sun_elev_rad = np.deg2rad(meta['SUN_ELEVATION']) toa = (dn.astype(np.float64) * mult + add) / np.sin(sun_elev_rad)return np.clip(toa, -0.2, 1.2)defdos_atmospheric_correction(toa):"""DOS暗目标大气校正""" valid = toa[toa > 0.01] dark_val = np.percentile(valid, 1.0) surf_ref = toa - dark_valreturn np.clip(surf_ref, 0, None)defbuild_cloud_mask(bqa_path):"""BQA波段提取云、云阴影、冰雪掩膜"""with rasterio.open(bqa_path) as src: bqa = src.read(1) fill = ((bqa >> 0) & 1).astype(bool) cloud = ((bqa >> 3) & 1).astype(bool) shadow = ((bqa >> 4) & 1).astype(bool) ice = ((bqa >> 6) & 1).astype(bool) invalid = fill | cloud | shadow | icereturn invalid
模块 2 model.py 核心网络 CBAM+ASPP+ResU-Net
2.1 CBAM 注意力模块
import torchimport torch.nn as nnimport torch.nn.functional as FclassChannelAttention(nn.Module):def__init__(self, in_ch, reduction=16):super().__init__()self.avg_pool = nn.AdaptiveAvgPool2d(1)self.max_pool = nn.AdaptiveMaxPool2d(1)self.fc = nn.Sequential( nn.Linear(in_ch, in_ch//16), nn.ReLU(), nn.Linear(in_ch//16, in_ch) )self.sigmoid = nn.Sigmoid()defforward(self, x): b,c,_,_ = x.shape avg = self.fc(self.avg_pool(x).view(b,c))max = self.fc(self.max_pool(x).view(b,c)) att = self.sigmoid((avg+max).view(b,c,1,1))return x * attclassSpatialAttention(nn.Module):def__init__(self, k=7):super().__init__()self.conv = nn.Conv2d(2,1,k,padding=k//2,bias=False)self.sigmoid = nn.Sigmoid()defforward(self, x): avg = torch.mean(x, dim=1, keepdim=True)max,_ = torch.max(x, dim=1, keepdim=True) att = self.sigmoid(self.conv(torch.cat([avg,max],dim=1)))return x * attclassCBAM(nn.Module):def__init__(self, c):super().__init__()self.ca = ChannelAttention(c)self.sa = SpatialAttention()defforward(self,x):returnself.sa(self.ca(x))
2.2 ASPP 空洞金字塔模块
class ASPP(nn.Module): def __init__(self, in_ch, out_ch, dilations=[1,6,12,18]):super().__init__() self.branches = nn.ModuleList() for d in dilations: if d == 1: self.branches.append(nn.Conv2d(in_ch, out_ch, 1, bias=False)) else: self.branches.append(nn.Conv2d(in_ch, out_ch,3,padding=d,dilation=d,bias=False)) self.global_pool = nn.Sequential(nn.AdaptiveAvgPool2d(1),nn.Conv2d(in_ch,out_ch,1)) self.fuse = nn.Conv2d(out_ch*5, out_ch, 1) def forward(self, x): h,w = x.shape[2:] feats = [] for b in self.branches: feats.append(b(x)) gp = self.global_pool(x) gp = F.interpolate(gp, (h,w), mode='bilinear',align_corners=False) feats.append(gp) return self.fuse(torch.cat(feats,dim=1))
2.3 残差块与完整模型前向主干
模块 3 train.py 损失函数 + 训练循环核心代码
3.1 BCE+Dice 组合损失(解决样本不均衡)
classCombinedLoss(nn.Module):def__init__(self, bce_w=0.5, dice_w=0.5, smooth=1e-6):super().__init__()self.bce = nn.BCELoss()self.bce_w = bce_wself.dice_w = dice_wself.smooth = smoothdefdice_loss(self, pred, target): pred = pred.view(-1) target = target.view(-1) inter = (pred*target).sum() dice = (2*inter + self.smooth)/(pred.sum()+target.sum()+self.smooth)return1 - dicedefforward(self,pred,target): loss_bce = self.bce(pred, target) loss_dice = self.dice_loss(pred, target)returnself.bce_w * loss_bce + self.dice_w * loss_dice
3.2 评价指标计算器(IoU/F1/OA/Kappa)
classMetricsCalculator:def__init__(self):self.reset()defreset(self):self.tp=self.fp=self.tn=self.fn=0defupdate(self, pred, target, th=0.5): pred_bin = (pred>th).float() tar_bin = (target>0.5).float()self.tp += (pred_bin*tar_bin).sum().item()self.fp += (pred_bin*(1-tar_bin)).sum().item()self.tn += ((1-pred_bin)*(1-tar_bin)).sum().item()self.fn += ((1-pred_bin)*tar_bin).sum().item()defget_metrics(self, smooth=1e-6): iou = (self.tp+smooth)/(self.tp+self.fp+self.fn+smooth) prec = (self.tp+smooth)/(self.tp+self.fp+smooth) recall = (self.tp+smooth)/(self.tp+self.fn+smooth) f1 = 2*prec*recall/(prec+recall+smooth) oa = (self.tp+self.tn)/(self.tp+self.fp+self.tn+self.fn)return {"IoU":iou,"F1":f1,"Precision":prec,"Recall":recall,"OA":oa}
模块 4 evaluate.py 大图滑窗推理核心代码
deffull_image_predict(model, ms_path, out_tif, tile_size=256, device="cuda"):"""整景Landsat大图分块滑窗预测,避免显存溢出"""import rasteriowith rasterio.open(ms_path) as src: data = src.read() h,w = src.height, src.width profile = src.profile标准化参数(训练集统计值) mean = np.array([0.0481,0.0716,0.1185,0.1826,0.2406,0.2090]).reshape(6,1,1) std = np.array([0.0172,0.0250,0.0410,0.0460,0.0639,0.0677]).reshape(6,1,1) pred_full = np.zeros((h,w), dtype=np.float32)滑窗遍历for y inrange(0, h, tile_size):for x inrange(0, w, tile_size): tw = min(tile_size, w-x) th = min(tile_size, h-y) tile = data[:, y:y+th, x:x+tw].astype(np.float32) tile = (tile - mean) / std tensor = torch.from_numpy(tile).unsqueeze(0).to(device)with torch.no_grad(): res = model(tensor).squeeze().cpu().numpy() pred_full[y:y+th, x:x+tw] = res# 输出概率栅格 profile.update(count=1, dtype="float32")with rasterio.open(out_tif, "w", **profile) as dst: dst.write(pred_full, 1)# 输出二值建筑图 binary = (pred_full>0.5).astype(np.uint8) bin_path = out_tif.replace(".tif","_binary.tif") profile.update(dtype="uint8")with rasterio.open(bin_path, "w", **profile) as dst: dst.write(binary,1)return pred_full, binary
模块 5 clip_yinchuan.py 行政区裁剪核心代码
import geopandas as gpdimport rasteriofrom rasterio.mask import maskdefclip_raster_by_shp(raster_path, shp_path, out_path):"""矢量行政边界裁剪栅格,自动投影匹配""" gdf = gpd.read_file(shp_path)with rasterio.open(raster_path) as src:if gdf.crs != src.crs: gdf = gdf.to_crs(src.crs) geom = [i.__geo_interface__ for i in gdf.geometry] img, trans = mask(src, geom, crop=True, nodata=0) meta = src.meta.copy() meta.update(height=img.shape[1], width=img.shape[2], transform=trans)with rasterio.open(out_path, "w",**meta) as dst: dst.write(img)统计建筑面积 valid = img[0] != 0 building = (img[0]>0.5) & valid pixel_area = 30*30 / 1e6 total_km2 = valid.sum() * pixel_area build_km2 = building.sum() * pixel_area coverage = build_km2 / total_km2print(f"总面积:{total_km2:.2f}km²,建筑面积:{build_km2:.2f}km²,覆盖率:{coverage:.2%}")return img, total_km2, build_km2, coverage
四、完整工程运行流程
环境依赖安装
pip install torch torchvision rasterio geopandas numpy scipy matplotlib
执行顺序预处理 preprocess_l8.py → 生成标签 generate_labels.py → 切片 generate_tiles.py → 训练 train.py → 全域推理 evaluate.py → 行政区裁剪 clip_yinchuan.py → generate_yinchuan_sci.py
五、应用场景
国土空间常态化不透水面监测;
城市规划、城镇扩张时空演变分析;
遥感 / 地理信息科学毕业论文完整实验框架;
GIS 专业教学:遥感辐射校正 + 深度学习语义分割综合案例。
本文构建一套Landsat 8 30m 多光谱城镇建成区全自动提取完整遥感深度学习工程,以银川市为实验区,实现从原始卫星影像到 SCI 标准专题图的端到端处理。数据预处理层面
:自研标准化辐射定标 + DOS 大气校正流水线,依托 BQA 波段剔除云、阴影、冰雪噪声,输出 6 波段标准化地表反射率数据,解决原始 DN 值物理误差问题;
模型创新层面
:提出 CBAM-ASPP ResU-Net 分割网络,融合残差结构、通道 + 空间双注意力、空洞金字塔多尺度特征提取、注意力门控跳跃连接四大改进,相比传统 NDBI 阈值法、普通 U-Net,IoU 提升显著,银川实验最优 IoU=0.758、F1=0.862,分类精度满足国土监测制图要求;
工程落地层面
:配套 7 套解耦 Python 代码,覆盖标签生成、256×256 瓦片数据集制作、断点训练、大图滑窗推理、行政区矢量裁剪、300DPI 科研可视化全流程,提供各模块核心精简代码,环境轻量化易部署,分轻 / 中 / 大三档模型适配笔记本与服务器;
实验成果
:银川全域统计得出总土地面积 53163.24km²,城镇建成区 8834.71km²,建筑覆盖率 16.62%,批量输出区位图、概率热力图、城区细节对比、光谱指数系列专业制图;
应用价值
:可用于国土不透水面普查、城市扩张时序分析、城乡规划、遥感专业教学与毕业论文实验,完整源码可直接复用、修改研究区快速复现实验