简介:这份资源面向遥感图像处理与计算机视觉方向的开发者、学生及研究人员,聚焦卫星图像分割这一核心任务,帮助读者理解从图像预处理、特征提取到分割算法选择与后处理的完整流程。包内共3个文件,均为Python脚本,压缩包约3KB,涵盖主程序入口、屋顶检测示例与数据准备模块,便于快速搭建实验环境并对照代码理解分割逻辑。内容涉及GDAL/OGR、OpenCV、Scikit-image及TensorFlow、PyTorch等常用库的典型用法,并延伸至U-Net等深度学习分割模型在遥感场景中的应用思路。已有252人学习下载,适合希望以轻量脚本入门卫星图像分割、掌握地物分类与土地覆盖分析基础实现的中级学习者参考。
1. 卫星图像分割:从遥感大图里把每一类地物抠出来
拿到一份标注好的遥感影像数据集,想把建筑、水体、植被、道路逐像素分开,这件事比想象中麻烦。卫星图幅面动辄几千乘几千像素,直接塞进网络显存扛不住;地物边界模糊,建筑和道路灰度接近;标注样本还少。这个项目就是冲着这些痛点来的,用 Python 搭一套完整的卫星图像分割流程,从数据读取、切图增强,到模型训练、推理拼接,最后输出可视化的分割掩膜。适合做遥感方向的学生、想入门语义分割的开发者,以及需要把分割能力集成到自己系统里的工程同学。读完你能跑通全流程,也能按自己的数据改类别和网络结构。
2. 项目结构与技术选型:为什么是这套组合
2.1 目录结构一览
项目按数据、模型、训练、推理、工具五个维度切分,避免所有代码堆在一个文件里。常见做法是这样组织:
satellite-seg/ ├── configs/ # 配置集中管理 │ ├── default.yaml # 默认超参、路径 │ └── unet_r50.yaml # 具体实验配置 ├── data/ # 数据相关 │ ├── dataset.py # Dataset 与 DataLoader │ ├── transforms.py # 增强与归一化 │ └── split.py # 训练/验证集划分 ├── models/ │ ├── unet.py # UNet 主干 │ ├── deeplab.py # DeepLabV3+ 备选 │ └── losses.py # Dice + BCE 组合损失 ├── engine/ │ ├── trainer.py # 训练循环 │ ├── validator.py # 验证与指标 │ └── inference.py # 滑窗推理与拼接 ├── utils/ │ ├── metrics.py # IoU、F1、混淆矩阵 │ ├── visualize.py # 掩膜叠加可视化 │ └── logger.py # 日志与 TensorBoard ├── scripts/ │ ├── train.sh │ └── predict.sh └── requirements.txt这个结构的好处是配置和代码分离,换数据集只改configs,换模型只动models,训练逻辑不用碰。新手容易把所有东西写进一个train.py,改一个参数要翻半天,后期维护成本极高。
2.2 技术栈与依赖
核心依赖如下表,版本按项目实际锁定,避免环境漂移:
| 依赖 | 用途 | 备注 |
|---|---|---|
| Python 3.8+ | 运行环境 | 建议 3.8/3.9 |
| PyTorch 1.10+ | 深度学习框架 | 支持 CUDA 11.x |
| torchvision | 预训练主干 | ResNet 权重 |
| OpenCV | 图像读写与几何变换 | 处理大图切片 |
| NumPy | 数组运算 | 掩膜拼接 |
| albumentations | 数据增强 | 比 torchvision 更丰富 |
| rasterio | 遥感影像读取 | 支持 GeoTIFF |
| matplotlib | 可视化 | 结果叠加 |
| PyYAML | 配置解析 | 管理超参 |
选 PyTorch 而不是 TensorFlow,主要因为动态图调试方便,自定义损失和滑窗推理写起来直观。遥感影像常用 GeoTIFF 格式,rasterio能保留地理坐标和波段信息,比cv2.imread更合适。增强用albumentations,它支持同步变换图像和掩膜,避免旋转后标签错位。
2.3 数据流与训练流程
整个流程分四步:读取大图并切片、增强、送入网络、计算损失反向传播。验证阶段用滑窗推理拼回原图尺寸,再算 IoU。推理阶段加载权重,对单张大图做重叠滑窗,最后用加权平均消除拼接缝。
提示:切片尺寸和重叠比例直接影响显存占用与边界效果,配置里要显式写清楚。
3. 核心模块拆解:数据管道与模型实现
3.1 数据集与切片策略
卫星图太大,必须切成小块训练。切片时保留重叠,避免边界地物被切断。下面是一个简化的 Dataset 实现:
import cv2 import numpy as np import torch from torch.utils.data import Dataset class SatelliteDataset(Dataset): def __init__(self, image_paths, mask_paths, transform=None, patch_size=512, stride=384): self.image_paths = image_paths self.mask_paths = mask_paths self.transform = transform self.patch_size = patch_size self.stride = stride self.patches = [] # 预计算所有切片坐标,训练时按索引取 for img_path, msk_path in zip(image_paths, mask_paths): img = cv2.imread(img_path, cv2.IMREAD_COLOR) h, w = img.shape[:2] for y in range(0, h - patch_size + 1, stride): for x in range(0, w - patch_size + 1, stride): self.patches.append((img_path, msk_path, x, y)) def __len__(self): return len(self.patches) def __getitem__(self, idx): img_path, msk_path, x, y = self.patches[idx] img = cv2.imread(img_path, cv2.IMREAD_COLOR) msk = cv2.imread(msk_path, cv2.IMREAD_GRAYSCALE) img = img[y:y+self.patch_size, x:x+self.patch_size] msk = msk[y:y+self.patch_size, x:x+self.patch_size] img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if self.transform: augmented = self.transform(image=img, mask=msk) img, msk = augmented['image'], augmented['mask'] img = img.transpose(2, 0, 1).astype(np.float32) / 255.0 msk = (msk > 0).astype(np.float32) # 二分类示例 return torch.from_numpy(img), torch.from_numpy(msk).unsqueeze(0)patch_size控制单块大小,512 是常见起点,显存不够降到 256。stride小于patch_size就产生重叠,重叠区域在推理时加权融合。预计算切片坐标而不是每次读整图,能大幅减少 IO。注意掩膜要转成 0/1 浮点,多类别时改成类别索引并配合 CrossEntropyLoss。
3.2 UNet 模型定义
UNet 的编码器-解码器加跳跃连接,适合分割任务。这里用 ResNet 做编码器,解码器逐级上采样:
import torch import torch.nn as nn import torchvision class DecoderBlock(nn.Module): def __init__(self, in_ch, skip_ch, out_ch): super().__init__() self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True) self.conv = nn.Sequential( nn.Conv2d(in_ch + skip_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), nn.Conv2d(out_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), ) def forward(self, x, skip): x = self.up(x) x = torch.cat([x, skip], dim=1) return self.conv(x) class UNetResNet(nn.Module): def __init__(self, num_classes=1, pretrained=True): super().__init__() backbone = torchvision.models.resnet34(pretrained=pretrained) self.encoder0 = nn.Sequential(backbone.conv1, backbone.bn1, backbone.relu) # 1/2 self.encoder1 = nn.Sequential(backbone.maxpool, backbone.layer1) # 1/4 self.encoder2 = backbone.layer2 # 1/8 self.encoder3 = backbone.layer3 # 1/16 self.encoder4 = backbone.layer4 # 1/32 self.decoder4 = DecoderBlock(512, 256, 256) self.decoder3 = DecoderBlock(256, 128, 128) self.decoder2 = DecoderBlock(128, 64, 64) self.decoder1 = DecoderBlock(64, 64, 64) self.final = nn.Conv2d(64, num_classes, 1) def forward(self, x): e0 = self.encoder0(x) e1 = self.encoder1(e0) e2 = self.encoder2(e1) e3 = self.encoder3(e2) e4 = self.encoder4(e3) d4 = self.decoder4(e4, e3) d3 = self.decoder3(d4, e2) d2 = self.decoder2(d3, e1) d1 = self.decoder1(d2, e0) return self.final(d1)编码器用 ResNet34 预训练权重,收敛更快。DecoderBlock先上采样再拼接对应层特征,最后两层卷积融合。num_classes二分类设 1,多分类设类别数。输出分辨率是输入的 1/2,因为encoder0后没有下采样,实际使用时可在final前再加一次上采样,或调整编码器结构对齐。
3.3 损失函数与指标
分割任务常用 Dice + BCE 组合,前者缓解类别不平衡,后者稳定梯度:
import torch import torch.nn as nn class DiceBCELoss(nn.Module): def __init__(self, weight=0.5): super().__init__() self.weight = weight self.bce = nn.BCEWithLogitsLoss() def forward(self, logits, targets): bce_loss = self.bce(logits, targets) probs = torch.sigmoid(logits) intersection = (probs * targets).sum() dice_loss = 1 - (2 * intersection + 1e-6) / (probs.sum() + targets.sum() + 1e-6) return self.weight * bce_loss + (1 - self.weight) * dice_lossweight调节两者比例,0.5 是常用起点。Dice 对前景少的情况更敏感,BCE 提供逐像素监督。训练时记录 IoU:
def compute_iou(pred, target, threshold=0.5): pred = (torch.sigmoid(pred) > threshold).float() intersection = (pred * target).sum() union = pred.sum() + target.sum() - intersection return (intersection + 1e-6) / (union + 1e-6)阈值 0.5 是默认,实际可按验证集调。IoU 比准确率更能反映分割质量,尤其背景占多数时。
4. 训练与推理:从配置到出图
4.1 配置文件与训练入口
配置用 YAML 管理,训练脚本读取后组装数据、模型、优化器:
# configs/unet_r50.yaml data: train_dir: data/train/images val_dir: data/val/images patch_size: 512 stride: 384 batch_size: 4 num_workers: 4 model: name: unet_resnet34 num_classes: 1 pretrained: true train: epochs: 50 lr: 0.0001 weight_decay: 0.0001 save_dir: checkpoints训练循环核心:
import yaml import torch from torch.utils.data import DataLoader from models.unet import UNetResNet from data.dataset import SatelliteDataset from models.losses import DiceBCELoss with open('configs/unet_r50.yaml') as f: cfg = yaml.safe_load(f) train_ds = SatelliteDataset(...) train_loader = DataLoader(train_ds, batch_size=cfg['data']['batch_size'], shuffle=True, num_workers=cfg['data']['num_workers']) model = UNetResNet(num_classes=cfg['model']['num_classes']).cuda() optimizer = torch.optim.Adam(model.parameters(), lr=cfg['train']['lr'], weight_decay=cfg['train']['weight_decay']) criterion = DiceBCELoss() for epoch in range(cfg['train']['epochs']): model.train() for imgs, masks in train_loader: imgs, masks = imgs.cuda(), masks.cuda() logits = model(imgs) loss = criterion(logits, masks) optimizer.zero_grad() loss.backward() optimizer.step() # 每个 epoch 后在验证集算 IoU 并保存最优权重batch_size受显存限制,512 切片下 4 是常见值。lr用 1e-4 配合 Adam,太大容易震荡。num_workers按 CPU 核数调,IO 瓶颈时加大。
4.2 滑窗推理与拼接
大图推理不能直接整图送入,要滑窗后拼回。重叠区域用高斯权重融合,消除拼接缝:
import numpy as np import torch def sliding_window_predict(model, image, patch_size=512, stride=384, num_classes=1): model.eval() h, w = image.shape[:2] prob_map = np.zeros((num_classes, h, w), dtype=np.float32) count_map = np.zeros((h, w), dtype=np.float32) # 高斯权重,中心高边缘低 weight = np.outer(np.hanning(patch_size), np.hanning(patch_size)).astype(np.float32) for y in range(0, h - patch_size + 1, stride): for x in range(0, w - patch_size + 1, stride): patch = image[y:y+patch_size, x:x+patch_size] tensor = torch.from_numpy(patch.transpose(2,0,1)).float().unsqueeze(0).cuda() / 255.0 with torch.no_grad(): out = torch.sigmoid(model(tensor)).cpu().numpy()[0] prob_map[:, y:y+patch_size, x:x+patch_size] += out * weight count_map[y:y+patch_size, x:x+patch_size] += weight prob_map /= np.maximum(count_map, 1e-6) return prob_mapstride越小重叠越多,融合越平滑但耗时增加。weight用汉宁窗,中心贡献大,边缘贡献小,避免边界突变。多类别时num_classes改成类别数,取 argmax 得到最终掩膜。
4.3 结果可视化
把预测掩膜叠加到原图,方便检查:
import cv2 import numpy as np def overlay_mask(image, mask, color=(0, 255, 0), alpha=0.5): mask_bin = (mask > 0.5).astype(np.uint8) colored = np.zeros_like(image) colored[mask_bin == 1] = color return cv2.addWeighted(image, 1 - alpha, colored, alpha, 0)alpha控制透明度,0.5 能同时看到原图和掩膜。颜色按类别区分,多类别时循环赋值。
5. 避坑与常见问题排查
5.1 显存溢出
现象:训练几个 batch 后报 CUDA out of memory。原因:切片太大或 batch_size 过高。解决:把patch_size从 512 降到 256,batch_size降到 2,或开启混合精度torch.cuda.amp。
5.2 掩膜与图像错位
现象:增强后标签和图像对不上。原因:用了不同步的变换库。解决:统一用albumentations,它保证 image 和 mask 同步旋转、翻转。
5.3 验证 IoU 远低于训练
现象:训练 loss 降但验证 IoU 上不去。原因:过拟合或数据分布不一致。解决:加数据增强、早停、检查训练验证集是否来自同一区域。
5.4 拼接缝明显
现象:推理结果有网格状接缝。原因:重叠区域直接平均或没有重叠。解决:用高斯权重融合,stride设为patch_size的 0.75 倍左右。
5.5 类别不平衡导致全预测背景
现象:模型输出几乎全为背景。原因:前景像素太少,BCE 被背景主导。解决:换 Dice + BCE 组合损失,或对前景采样加权。
6. 二次开发:换数据、换模型、加类别
拿到这套代码后,最常改的是数据接口和类别数。换数据集时,把SatelliteDataset里的读取逻辑换成你的格式,GeoTIFF 用rasterio读,普通图片用cv2。类别数从 1 改成 N,损失换成CrossEntropyLoss或Dice + CE,输出层num_classes=N,可视化按类别配色。
换模型更简单,在models/下加新文件,实现forward返回[B, C, H, W]的 logits,配置里改model.name即可。想试 DeepLabV3+,直接调torchvision.models.segmentation.deeplabv3_resnet50,改输出层通道数。
验证改造是否有效,我一般固定三件事:先在少量样本上过拟合,确认模型能学会;再跑完整验证集看 IoU 曲线;最后拿几张没见过的图做滑窗推理,肉眼检查边界。这三步走完,基本能判断改动是正向还是翻车。
有个习惯我每次都强制走一遍:改完数据管道后,先可视化一个 batch 的原图和掩膜,确认对齐、归一化、类别映射都没问题,再开训练。这个后悔药成本极低,能省下几小时白跑。希望帮到你。
本文还有配套的精品资源,点击获取