当前位置:首页>python>Python遥感实战4 | CBAM-ASPP ResU-Net遥感建筑提取的完整技术栈——以银川市 30m 分辨率建成区提取为例

Python遥感实战4 | CBAM-ASPP ResU-Net遥感建筑提取的完整技术栈——以银川市 30m 分辨率建成区提取为例

  • 2026-10-11 06:19:37
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 模型分割精度指标

表格

评价指标
数值
行业说明
IoU(交并比)
0.758
优于传统 NDBI 阈值法 (0.512)、标准 U-Net (0.641)
F1-Score
0.862
建成区像素识别均衡性良好
Precision 精确率
0.874
裸土、滩涂虚警少
Recall 召回率
0.851
小型村镇不透水面漏检少
OA 总体精度
0.928
全域地物整体分类精度
Kappa 系数
0.814
分类一致性优秀,满足国土监测制图要求

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 空洞金字塔瓶颈

:多扩张率卷积捕捉主城区、乡镇、零散宅基地多尺度建成区;

注意力门控跳跃连接

:过滤浅层裸土、云层噪声,提升建成区边界分割精度。

三、完整技术工作流 + 核心代码分段展示

整套工程 7 份脚本,下文放各模块核心精简代码。

模块 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%,批量输出区位图、概率热力图、城区细节对比、光谱指数系列专业制图;

应用价值

:可用于国土不透水面普查、城市扩张时序分析、城乡规划、遥感专业教学与毕业论文实验,完整源码可直接复用、修改研究区快速复现实验

最新文章

随机文章