拓冰建站拓冰建站
首页 / 资讯中心 / 正文

深度学习遥感图像分类全流程实战:语义分割从入门到避坑

“AI 遥感”现在其实有点两极分化一边是遥感数据处理上手门槛高初学者连一个多波段 TIF 怎么读出来、怎么转成模型能用的数据都不清楚另一边是深度学习的教程太通用拿自然图像分类的代码直接套遥感场景结果类别不均衡、尺度差异大、大图推理拼接这些真实问题一个都没解决。这次要聊的这条路线专门解决上面两个痛点从遥感影像的读取与预处理开始到语义分割模型搭建、训练评估、批量推理导出完整走一遍基于深度学习的遥感图像分类流程。方向定在“地物分类”这个最常见也最贴合课程设计、毕业设计需求的任务上。相比单纯跑通一套代码更重要的是建立一套可以迁移到其他遥感任务的工程思路。文章会分成三块来写第一块是原理用最短篇幅把遥感分类为什么需要深度学习、语义分割模型到底在学什么讲清楚第二块是实战给出可以直接改的 PyTorch 工程结构和代码示例覆盖数据裁剪、模型训练、精度评估、批量推理第三块是避坑把显存占用、GDAL/rasterio 安装、类别不均衡、大图拼接这些问题单独拉出来说这些才是实际跑项目时最耗时间的地方。如果你是正在做课程设计、毕业设计或者遥感方向入门研究的同学这篇文章可以直接收藏。内容不只给结论会尽量把每一步的输入输出、判断标准和可能踩的坑都写清楚。1. 核心能力速览项目说明教程主题基于深度学习的遥感图像分类以语义分割实现地物分类为主目标任务土地利用分类、建筑物提取、水体识别、植被覆盖分类等核心模型U-Net、DeepLabV3、SegFormer 等语义分割模型技术栈Python、PyTorch、rasterio/GDAL、OpenCV、NumPy数据来源通用遥感公开数据集如 ISPRS Vaihingen/Potsdam、LoveDA、DeepGlobe Land Cover硬件建议建议 NVIDIA GPU显存 8GB 起步CPU 可运行但不适合完整训练输入数据多光谱或 RGB 遥感影像支持 TIF/GeoTIFF 格式输出结果单波段分类标签图、类别掩膜、统计面积或类别占比批量能力支持滑动窗口批量推理、大图裁剪预测后拼接评测指标IoU、mIoU、F1、总体精度OA适合场景课程设计、毕业设计、遥感影像智能解译入门研究2. 遥感图像分类为什么需要深度学习传统的遥感图像分类思路主要依赖光谱特征和手工特征。早期做法是设定光谱阈值或者计算 NDVI 等指数把植被、水体、裸地分开后来发展到用 SVM、随机森林这类机器学习算法在人工设计的特征上做分类。这些方法在场景简单、类别少、地物光谱差异明显时很有效但一旦遇到城市复杂场景比如建筑物阴影、道路与屋顶光谱接近、不同地物尺度差异大传统方法的精度和泛化能力就会明显不足。深度学习把这件事变成了一个端到端的问题输入影像输出逐像素的类别标签。模型自己从大量标注样本中学习“建筑物长什么样”“水体在多个波段下有什么特征”不再依赖人工设计特征。在遥感图像分类任务里现在主流的做法是语义分割也就是给影像的每一个像素预测一个类别比如草地、树木、建筑物、道路、水体、农田。遥感影像和自然图像最大的区别在于几个方面影像尺寸通常很大一幅 TIF 可能是几万乘几万像素不能直接整图输入模型需要裁剪成 patch。波段数不一定只有 RGB可能是多光谱甚至高光谱输入通道数与自然图像的 3 通道不同。类别不均衡严重比如一幅影像里建筑物只占 5%背景占 95%如果不做处理模型会倾向于把所有像素都预测成背景。地物尺度差异大同一类别可能在不同区域表现出完全不同的尺寸和形态。这些特点决定了遥感深度学习项目不能简单套自然图像分类代码数据预处理、模型选型、训练策略都要单独设计。3. 适用场景与使用边界这类基于深度学习的遥感图像分类方案适合以下场景土地利用与土地覆盖分类区分耕地、林地、草地、水域、建设用地、裸地等。建筑物提取结合公开数据集做建筑物轮廓识别是城市规划和不动产管理的研究热点。水体与植被识别面向生态遥感指数计算、资源调查等应用。变化检测的前处理先在两个时相影像上分别做地物分类再做逐类别变化分析。本科或研究生阶段的课程设计、毕设选题以及遥感智能解译方向的入门研究。同时也要明确边界。深度学习分类效果依赖训练数据质量如果研究区域很特殊、公开数据集中没有类似地物需要自行标注数据标注成本不低。若没有标注经验容易产生标签边界不准确、类别遗漏等问题。另外深度学习模型的可解释性仍然偏弱如果应用场景对分类依据有硬性合规或审计要求只靠一个黑盒模型是不够的。合规方面需要特别强调遥感数据存在版权和授权限制实验优先使用公开数据集或已获得授权的影像不应对涉及敏感区域、敏感设施的高分辨率影像进行自动识别和标注论文或商用项目中使用遥感数据时要详细记录数据来源、许可协议和使用范围。标注数据时涉及人脸、车牌、个人身份信息等内容必须按当地法规脱敏处理。4. 环境准备与数据准备4.1 环境准备推荐使用 Linux 环境或 Windows WSL2也可以直接在 Windows 上安装 Python 环境。主要依赖如下Python 3.8 到 3.10PyTorch安装时按官方命令选择对应 CUDA 版本rasterio 用于读取和写入 GeoTIFFGDALrasterio 会依赖OpenCV 用于图像增强和形态学后处理NumPy、Matplotlib、tqdm创建独立环境的参考命令conda create -n rsseg python3.9 conda activate rsseg # 按 PyTorch 官网给出的当前版本安装命令执行 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install rasterio opencv-python numpy matplotlib tqdm如果直接 pip 安装 GDAL 报错可以用 conda 安装conda install -c conda-forge gdal rasterio装完以后可以用下面这段代码确认核心依赖是否可用import rasterio import torch import cv2 print(rasterio:, rasterio.__version__) print(torch:, torch.__version__) print(cuda available:, torch.cuda.is_available())对大多数入门项目来说8GB 显存可以跑 256×256 或 512×512 的语义分割训练backbone 建议先用 ResNet18 或小尺寸版本显存更大的机器可以把批量大小或裁剪尺寸调大。没有 NVIDIA GPU 时可以用 CPU 跑小尺寸训练和推理但完整训练速度会非常慢适合做代码调试不适合正式做实验。4.2 数据准备训练数据一般由遥感影像和对应的标签图构成。标签图中每个像素是类别编号。公开数据集是入门首选ISPRS Vaihingen / Potsdam城市地物分类经典数据集常见类别包括不透水面、建筑物、低矮植被、树木、汽车等。LoveDA面向土地利用分类的多源遥感数据集场景包含城市和乡村。DeepGlobe Land Cover全球土地覆盖分类数据集包含农田、森林、草地、水域等类别。拿到数据后先做统一处理将影像统一到一个尺寸范围常用 256×256 或 512×512。归一化到 0-1 或标准化。标签图转为 long 类型类别编号从 0 开始连续编号背景和其他类别不要混淆。划分训练集、验证集、测试集。5. 核心原理速通遥感图像分类使用的语义分割模型本质上是一个编码器-解码器结构。编码器负责提取影像特征解码器负责把特征恢复到原始分辨率并在每个像素位置输出类别概率。U-Net 是最经典的结构特点是跳跃连接把编码器和解码器对应层的特征拼接起来保留细节信息对小目标和边界有较好效果适合训练数据量不是特别大的情况。DeepLabV3 使用空洞卷积扩大感受野在保持分辨率的同时捕获更大范围的上下文信息对建筑物和道路这类结构性地物效果不错。SegFormer 是 Transformer 类分割模型利用自注意力机制建模长距离依赖。遥感影像中同类地物可能分布在影像不同区域长距离依赖建模对这种场景有优势但训练开销和数据需求通常比 CNN 更大。训练遥感分类模型时损失函数不能只依赖交叉熵。遥感类别不均衡很常见建议组合使用交叉熵损失基础损失函数。Dice Loss对类别不均衡更友好。Focal Loss当背景像素占比过高时让模型更关注难分类的少数类别。评估指标使用 IoU 和 mIoU 是遥感语义分割的常见做法。IoU 表示预测类别区域和真实类别区域的交集除以并集mIoU 是所有类别 IoU 的平均值。只看准确率在遥感分类里很容易被误导因为背景类别占比太高。6. 实战从 TIF 到训练集这一步的目标是把一幅大尺寸 TIF 影像裁剪成模型可以训练的小 patch。先定义一个通用的裁剪函数按指定窗口大小滑动裁剪同时处理影像和标签import numpy as np import rasterio from rasterio.windows import Window def crop_image_label(image_path, label_path, patch_size512, stride512, out_img_dir, out_label_dir): 将一整幅TIF影像和对应标签裁剪为patch。 若stride小于patch_size则生成重叠patch否则为无重叠裁剪。 count 0 with rasterio.open(image_path) as src_img, rasterio.open(label_path) as src_label: width, height src_img.width, src_img.height for y in range(0, height - patch_size 1, stride): for x in range(0, width - patch_size 1, stride): window Window(x, y, patch_size, patch_size) img src_img.read(windowwindow) # shape: (bands, h, w) label src_label.read(1, windowwindow) # shape: (h, w) img img.astype(np.float32) label label.astype(np.int64) np.save(f{out_img_dir}/patch_{count:05d}.npy, img) np.save(f{out_label_dir}/patch_{count:05d}_label.npy, label) count 1 return count裁剪时要注意如果影像本身小于 patch_size可以直接 resize 到 patch_size但会引入形变测试阶段建议保持原始尺寸。标签图中如果包含 255 或无类别区域要统一处理成一个特定编号或在计算损失时忽略这些像素。多光谱影像可以直接把所有波段读入不一定必须转成 RGB。裁剪完成后还需要做归一化和数据增强。遥感影像增强常用方式包括随机翻转、随机旋转、随机亮度对比度调整。增强时注意对影像和标签使用完全相同的变换。再写一个 PyTorch Dataset 类import torch from torch.utils.data import Dataset import numpy as np class RemoteSensingDataset(Dataset): def __init__(self, image_dir, label_dir, transformNone): self.image_paths sorted(glob.glob(f{image_dir}/*.npy)) self.label_dir label_dir self.transform transform def __len__(self): return len(self.image_paths) def __getitem__(self, idx): img np.load(self.image_paths[idx]).astype(np.float32) label_path f{self.label_dir}/patch_{idx:05d}_label.npy label np.load(label_path).astype(np.int64) img torch.from_numpy(img) label torch.from_numpy(label) # 如果做同步增强需要在这里对img和label使用同一随机变换 return img, label7. 模型训练与效果验证下面给一个最简 U-Net 的训练流程示例重点不是复现最高精度而是让你理解整个训练闭环。定义模型结构时可以使用现成库import segmentation_models_pytorch as smp model smp.Unet( encoder_nameresnet18, encoder_weightsimagenet, in_channels3, classesnum_classes, )如果使用的是多光谱影像比如 4 波段或 8 波段in_channels要改成实际波段数。此时 encoder_weights 建议设置为 None因为 ImageNet 预训练权重只适用于 3 通道输入。训练主循环import torch from torch.utils.data import DataLoader from torch.optim import Adam from torch.nn import CrossEntropyLoss device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) optimizer Adam(model.parameters(), lr1e-4) criterion CrossEntropyLoss(ignore_index255) train_loader DataLoader(train_dataset, batch_size8, shuffleTrue, num_workers2) val_loader DataLoader(val_dataset, batch_size4, shuffleFalse, num_workers2) for epoch in range(30): model.train() total_loss 0.0 for img, label in train_loader: img img.to(device) label label.to(device) output model(img) # shape: (batch, num_classes, h, w) loss criterion(output, label) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() avg_loss total_loss / len(train_loader) # 验证 model.eval() total_ious 0.0 with torch.no_grad(): for img, label in val_loader: img img.to(device) label label.to(device) output model(img) pred torch.argmax(output, dim1) # 逐类别计算IoU这里省略细节实际会统计混淆矩阵 # iou compute_iou(pred, label, num_classes) # total_ious iou print(fepoch{epoch} loss{avg_loss:.4f})验证集评估的核心是混淆矩阵。按类别统计 TP、FP、FN然后计算每个类别的 IoU最后求平均得到 mIoU。这里给出一个不依赖第三方库的 IoU 计算思路def compute_iou(pred, label, num_classes): ious [] for cls in range(num_classes): pred_cls (pred cls) label_cls (label cls) intersection (pred_cls label_cls).sum().item() union (pred_cls | label_cls).sum().item() if union 0: ious.append(float(nan)) else: ious.append(intersection / union) return ious训练时重点关注两个信号训练 loss 持续下降但验证 mIoU 不升说明开始过拟合可以增加数据增强、增大训练集或降低模型复杂度。某个类别的 IoU 明显偏低大概率是样本太少针对这个类别可以增加采样权重或使用 Focal Loss。判断训练成功与否不能只看 loss。更稳妥的流程是每轮或每几轮保存一次模型权重在验证集上计算 mIoU以 mIoU 最高的模型作为最终模型。8. 批量推理与结果导出遥感影像推理和自然图像不同测试阶段面对的是完整大图。最常用的方案是滑动窗口推理后再拼接。为了避免窗口边缘出现拼接接缝可以让相邻窗口设置一定重叠重叠区域取多个预测结果的平均概率或者只保留中心区域。下面是一个简单的滑窗推理与拼接示例import numpy as np import torch def sliding_predict(model, image, patch_size512, stride256, batch_size4, devicecuda): image: (bands, height, width) 返回: prob_map (num_classes, height, width) model.eval() bands, height, width image.shape prob_map np.zeros((num_classes, height, width), dtypenp.float32) count_map np.zeros((height, width), dtypenp.float32) patches [] coords [] for y in range(0, height, stride): for x in range(0, width, stride): y_end min(y patch_size, height) x_end min(x patch_size, width) y_pad y_end - y x_pad x_end - x img_patch np.zeros((bands, patch_size, patch_size), dtypenp.float32) img_patch[:, :y_pad, :x_pad] image[:, y:y_end, x:x_end] patches.append(img_patch) coords.append((y, x, y_pad, x_pad)) with torch.no_grad(): for start in range(0, len(patches), batch_size): batch patches[start:start batch_size] batch_tensor torch.from_numpy(np.stack(batch)).to(device) output model(batch_tensor) prob torch.softmax(output, dim1).cpu().numpy() for j, offset in enumerate(range(start, start len(batch))): y, x, y_pad, x_pad coords[offset] prob_map[:, y:y y_pad, x:x x_pad] prob[j][:, :y_pad, :x_pad] count_map[y:y y_pad, x:x x_pad] 1 prob_map prob_map / np.expand_dims(count_map, axis0) return prob_map def save_label(prob_map, output_tif_path, reference_tif_path): label np.argmax(prob_map, axis0).astype(np.uint8) import rasterio with rasterio.open(reference_tif_path) as src: profile src.profile profile.update(count1, dtypeuint8) with rasterio.open(output_tif_path, w, **profile) as dst: dst.write(label, 1)批量推理时建议把所有测试影像放在一个目录按顺序读取逐幅预测输出结果使用与原影像相同的地理参考信息保存。这样得到的分类结果可以直接导入 GIS 软件做面积统计或专题制图。批量任务还要设计日志和失败重试。比如每处理完一幅影像就写一条记录包括影像路径、耗时、是否成功中途失败时自动跳过并记录错误信息最后统一重试失败项。9. 资源占用与性能观察遥感深度学习的资源瓶颈主要在显存。影响显存占用的因素有四个输入 patch 尺寸。256×256 和 512×512 的显存占用差距非常明显如果显存不够优先缩小 patch。批量大小 batch_size。显存溢出时把 batch_size 降到 2 或 1。backbone 的复杂度。ResNet18 和 ResNet101 显存占用差距巨大。通用做法是先跑小模型确认流程正常再换大模型刷精度。是否开启混合精度。PyTorch 的自动混合精度可以明显降低显存占用同时加快训练速度现在已经是标准做法。观察显存占用最简单的方式是 GPU 监控命令训练时另开一个终端执行watch -n 1 nvidia-smi如果显存不足优先尝试以下方式减小 batch_size。缩小裁剪 patch 尺寸。使用 ResNet18 替代 ResNet50。启动梯度累积用多个小 batch 模拟大 batch。开启混合精度训练。CPU 推理是完全可行的但速度会比较慢。以 256×256 的 patch 为例GPU 推理通常可以在秒级或毫秒级完成CPU 可能需要数秒甚至更久。如果只是验证几幅测试影像CPU 足够如果要处理几十幅大影像建议至少准备一张入门级 NVIDIA 显卡。多光谱影像会增加输入通道数但不会像扩大分辨率那样显著拉高显存因为中间特征图的主要开销由空间分辨率决定。10. 常见问题与排查方法问题现象可能原因排查方式解决方案安装 rasterio/GDAL 失败缺少编译依赖或版本冲突查看报错最后几行确认是编译器还是依赖包问题改用 conda 从 conda-forge 安装conda install -c conda-forge gdal rasterio读取 TIF 后显示全黑数据未归一化原始 DN 值范围不是 0-255打印影像数组 min/max按波段做 0-1 归一化或标准化标签图读取出来只有 0 和 255标签图是 RGB 编码或背景值为 255查看标签唯一值将标签重映射为连续类别编号背景通常设为 0 或在损失函数中 ignore_index255模型预测几乎全为背景类别不均衡背景像素占比过高统计训练集各类别像素比例使用 Focal Loss 或 Dice Loss增加少数类别权重或对少数类别做过采样显存不足patch 过大、batch_size 过大、backbone 过重用 nvidia-smi 观察占用调小 batch_size、裁小 patch、换轻量 backbone、开启混合精度验证 mIoU 很低但训练 loss 正常验证集和训练集分布差异大或验证集预处理不一致检查验证集归一化和标签映射是否与训练一致统一预处理流程检查标签是否错位大图推理拼接后有明显接缝滑动窗口没有重叠边缘预测不一致检查推理代码的 stride 设置stride 设为 patch_size 的一半重叠区域取平均概率训练速度很慢未使用 GPU 或 num_workers 太低检查 torch.cuda.is_available()确保安装 CUDA 版 PyTorch调大 num_workers报错 shape mismatch输入影像波段数与模型 in_channels 不一致打印影像 shape修改模型的 in_channels 参数或对影像做波段选择11. 最佳实践与使用建议第一次跑实验不要一上来就用大模型和大 patch。先把 patch_size 设为 256backbone 用 ResNet18batch_size 设为 4 或 8确认整个流程能跑通再逐步增加参数。文件目录建议这样组织便于复现实验project/ ├── data/ │ ├── images/ │ ├── labels/ │ ├── train/ │ └── val/ ├── outputs/ │ ├── checkpoints/ │ ├── preds/ │ └── logs/ ├── src/ │ ├── dataset.py │ ├── model.py │ ├── train.py │ └── inference.py └── config.yaml训练过程中的日志要记录清楚数据集来源、patch 尺寸、batch_size、学习率、backbone、训练轮数、最终 mIoU。这个习惯对写实验报告和复现结果非常有用。对类别不均衡除了换损失函数还可以做标签重映射把相邻且容易混淆的类别合并降低模型学习难度。比如不需要区分“低矮植被”和“树木”时可以合并成“植被”一类。批量推理前先选一幅中等尺寸的测试影像做单图调试确认输出标签图没有错位、类别映射正确再批量跑。批量跑的时候加一条处理日志每完成一幅影像就记录状态避免跑了几小时后才发现某个参数错误。涉及人脸、车牌、个人隐私或受版权保护的数据时必须先确认使用授权。遥感影像同样如此务必检查数据集许可协议论文中注明引用数据集的名称和来源。12. 总结与下一步这套流程最值得先跑通的是“数据裁剪 语义分割训练 滑窗推理拼接”这个完整链路。先把 256×256 的小 patch 训练跑通用公开数据集验证 mIoU 能稳定提升再逐步换成更大的模型和更大 patch。最容易踩的坑是数据预处理。遥感影像的波段顺序、标签编码、归一化方式只要有一处不一致最终精度就会很受打击。遇到问题先检查数据不要一上来就调模型结构。后续可以继续扩展的方向包括引入 SegFormer 或更深的分割模型刷精度加入多时相影像做变化检测对推理结果做后处理去除小噪点或把训练好的模型封装成 HTTP 接口供业务系统调用。建议先把文章中的裁剪代码和训练循环在自己的机器上跑通再对照自己的数据集做替换。真正动手之后很多概念会比单纯看文章清楚得多。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门