简介:这份资源是面向深度学习入门者与高校学生的遥感影像智能分析工具包,适合用作毕业设计、期末大作业或课程设计参考。其核心基于YOLO等卷积神经网络,可自动识别与分类遥感影像中的建筑物、植被、道路等地理特征,覆盖环境监测、灾害评估、城市规划与农业估算等场景。压缩包共26个文件,约94.97MB,包含8张jpg与7张png示例图及推理结果图、4份md说明文档、2组模型与参数文件、1份yaml部署配置、1个py推理脚本和1份txt依赖清单,结构上涵盖数据预处理、模型训练、评估与部署等模块,并配有README与推理脚本便于快速上手。目前已有49人学习下载。读者可借此理解深度学习与遥感技术结合的项目组织方式,掌握模型推理、结果可视化与部署配置的完整思路,为课程实践与后续研究提供可复用的参考。
1. 遥感影像智能分析工具:从一张 2 万像素的图说起
去年帮一个做农田地块识别的团队看模型,他们拿到一幅 20000×20000 的高分遥感影像,直接 resize 到 1024×1024 塞进 CNN,结果田埂全糊成一团,地块边界预测得跟水彩画似的。这不是模型不行,是遥感影像和自然图像根本不是一个玩法——幅面大、目标小、通道多、标注贵。所谓「基于深度学习的遥感影像智能分析工具」,本质就是把切图、标注、训练、推理、拼接这一整条链路打包成能跑通的工程,而不是丢一个模型权重给你就完事。它解决的是从原始 GeoTIFF 到可用矢量结果之间的脏活累活,适合做遥感解译、地块提取、变化检测的从业者,也适合想拿遥感当深度学习实战项目的新手。下面按我实际搭过的一套流程拆开讲,能抄的地方直接抄。
2. 遥感影像分析工具到底在分析什么:任务拆解与选型逻辑
遥感影像智能分析不是一个单一任务,它是一组任务的集合。你得先想清楚自己要的是像素级输出还是图斑级输出,是单时相还是多时相,这决定了后面整个工具链的形态。很多人一上来就问「用哪个模型」,其实模型是最后一步,前面数据怎么组织才是决定成败的地方。
2.1 四类主流任务与对应输出形态
按输出粒度从细到粗排,常见的有这四类:
| 任务类型 | 输出形态 | 典型场景 | 常用骨干 |
|---|---|---|---|
| 语义分割 | 逐像素类别图 | 地表覆盖分类 | U-Net / DeepLabV3+ |
| 实例分割 | 每个目标独立掩膜 | 建筑物提取 | Mask R-CNN |
| 目标检测 | 带类别矩形框 | 车辆、船舶检测 | YOLO 系列 / Faster R-CNN |
| 变化检测 | 双时相差异图 | 违建监测、地块变更 | Siamese 网络 |
选型逻辑很简单:要面积统计就语义分割,要数个数就实例分割或检测,要对比两个时间点就变化检测。农田地块识别这种场景,地块之间紧挨着、边界就是田埂,语义分割比检测更合适,因为检测框会互相重叠,反而说不清归属。
2.2 为什么不能直接 resize:遥感影像的三个特殊性
第一个特殊性是幅面。卫星或无人机单幅影像动辄上万像素,而主流 GPU 显存吃不下这么大输入。直接缩放会丢失小目标,田埂、小路、小建筑物首当其冲。
第二个特殊性是通道。遥感影像常见 4 通道(RGB + 近红外)甚至更多波段,而 ImageNet 预训练权重是 3 通道。你得决定是丢掉近红外还是改第一层卷积,这个选择直接影响植被相关任务的精度。
第三个特殊性是标注成本。遥感标注需要专业知识,一个像素级标注员一天可能只标几平方公里。所以工具必须支持「局部聚焦算法辅助标记」这类半自动标注,先用模型出粗结果,人工只修错的地方,把标注效率拉起来。
2.3 工具链的整体架构
我一般把工具分成五层,从下往上:
- 数据层:GeoTIFF/JPEG2000 读写、坐标参考系管理、波段组合
- 切图层:滑窗切图、重叠度控制、边缘补齐
- 标注层:半自动预标注、格式转换(GeoJSON ↔ mask)
- 训练层:数据增强、损失函数、多卡训练
- 推理层:分块推理、结果拼接、矢量化输出
这五层里最容易翻车的是切图和拼接,因为涉及坐标对齐,差一个像素整个结果就错位。下面几章按这条链路往下走。
3. 把大图切进显存:滑窗切图与数据组织的可复现步骤
这一章解决「图太大跑不动」的问题。核心思路是滑窗切图,但窗口大小、重叠度、边缘处理都有讲究,设错了要么丢目标要么拼接有缝。
3.1 用 rasterio 读取与波段组合
先装环境。我习惯用 miniconda 隔离,遥感这套依赖 GDAL,用 conda 装比 pip 省心:
conda create -n rs python=3.10 -y conda activate rs conda install -c conda-forge rasterio gdal numpy opencv -y pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118读取影像并做波段组合,注意遥感影像的波段顺序和自然图像不同:
import rasterio import numpy as np def read_image(path, bands=(1, 2, 3)): """读取指定波段,返回 HWC 的 uint8 数组和地理变换信息""" with rasterio.open(path) as src: # 遥感影像波段从 1 开始计数,不是 0 data = src.read(list(bands)) # (C, H, W) transform = src.transform # 地理变换,拼接时要用 crs = src.crs # 坐标参考系 # 转成 HWC 方便后续处理 img = np.transpose(data, (1, 2, 0)) # 16bit 转 8bit,按 2% 和 98% 分位数拉伸,比固定除以 256 稳 img = percentile_stretch(img) return img, transform, crs def percentile_stretch(img, low=2, high=98): """分位数拉伸,避免个别极亮极暗像素拉垮整体对比度""" out = np.zeros_like(img, dtype=np.uint8) for c in range(img.shape[2]): band = img[:, :, c].astype(np.float32) lo, hi = np.percentile(band, (low, high)) band = np.clip((band - lo) / (hi - lo + 1e-6), 0, 1) out[:, :, c] = (band * 255).astype(np.uint8) return out逻辑说明:src.read返回的是通道优先格式,必须转成 HWC 才能喂给 PyTorch 的常规预处理。分位数拉伸是关键,遥感影像常有云、水体这类极端值,固定除以最大值会让整幅图偏暗。参数low和high我一般设 2 和 98,云多的场景可以调到 1 和 99。
3.2 滑窗切图的窗口与重叠度参数
切图的核心参数就三个:窗口大小、重叠度、边缘补齐方式。
def slide_crop(img, patch_size=512, overlap=64): """滑窗切图,返回图块列表和每块的左上角坐标""" h, w = img.shape[:2] stride = patch_size - overlap patches, coords = [], [] for y in range(0, h, stride): for x in range(0, w, stride): # 边缘不足一块时,往回退对齐,避免补零引入假边界 y0 = min(y, max(h - patch_size, 0)) x0 = min(x, max(w - patch_size, 0)) patch = img[y0:y0 + patch_size, x0:x0 + patch_size] # 影像本身小于一块时补零 if patch.shape[0] < patch_size or patch.shape[1] < patch_size: patch = cv2.copyMakeBorder( patch, 0, patch_size - patch.shape[0], 0, patch_size - patch.shape[1], cv2.BORDER_REFLECT) patches.append(patch) coords.append((x0, y0)) return patches, coords逻辑说明:stride = patch_size - overlap决定相邻块重叠多少。重叠度我一般设 patch_size 的 12.5%,512 的块配 64 的重叠。重叠太小,拼接处会有明显接缝;重叠太大,推理时间成倍涨。边缘处理用「往回退」而不是补零,是因为补零会在影像边缘造出一条假边界,模型会把它当成真实地物。
参数怎么调:目标越小,patch_size 越小,但别小于 256,否则上下文不够。建筑物提取用 512,地块识别用 768 到 1024,因为地块本身尺度大。显存不够就减 batch,别减 patch_size,减了 patch_size 小目标就没了。
3.3 标注格式转换:GeoJSON 到 mask 的四个边界坑
遥感标注常从 GIS 软件导出 GeoJSON,要转成训练用的 mask。这一步坑最多:
from rasterio.features import rasterize import geopandas as gpd def geojson_to_mask(geojson_path, transform, out_shape, class_field="class_id"): """把矢量标注栅格化成 mask,类别从 1 开始,0 为背景""" gdf = gpd.read_file(geojson_path) # 关键:矢量必须和影像在同一坐标系,否则栅格化全错位 shapes = ((geom, int(cid)) for geom, cid in zip(gdf.geometry, gdf[class_field])) mask = rasterize( shapes, out_shape=out_shape, transform=transform, fill=0, dtype=np.uint8, all_touched=False) return mask四个坑:一是坐标系不一致,GeoJSON 是经纬度而影像投影坐标,必须先to_crs;二是all_touched设 True 会让边界像素被多类争抢,一般设 False;三是类别从 0 开始会和背景冲突,类别 ID 从 1 起;四是多边形自相交,栅格化会报错,得先buffer(0)修一下。
4. 训练与推理:从模型选择到分块拼接的完整链路
切图和标注搞定,进入训练。这一章讲模型怎么选、损失怎么设、推理怎么拼回去。
4.1 语义分割模型的骨干选择与预训练权重处理
遥感语义分割主流是 U-Net 和 DeepLabV3+。U-Net 在小数据集上更稳,DeepLabV3+ 的空洞卷积对大尺度上下文更好。我的经验是数据少于 5000 块就用 U-Net,多了再上 DeepLabV3+。
4 通道输入的处理有两种做法:一是丢掉近红外只用 RGB,简单但浪费信息;二是改第一层卷积,把预训练权重的 3 通道权重取平均扩展到 4 通道。后者对植被任务提升明显:
import torch.nn as nn import torchvision.models as models def build_unet_4ch(num_classes=2): """基于 resnet34 的 U-Net,第一层改成 4 通道""" backbone = models.resnet34(weights=models.ResNet34_Weights.IMAGENET1K_V1) # 取原第一层权重,沿通道维求平均,再复制成 4 通道 old_conv = backbone.conv1 new_conv = nn.Conv2d(4, 64, kernel_size=7, stride=2, padding=3, bias=False) with torch.no_grad(): w = old_conv.weight.mean(dim=1, keepdim=True) # (64,1,7,7) new_conv.weight.copy_(w.repeat(1, 4, 1, 1) / 4) backbone.conv1 = new_conv return backbone逻辑说明:把 3 通道权重求平均再复制到 4 通道,比随机初始化收敛快得多,因为 RGB 部分的知识保留了。除以 4 是为了保持输出量级不变。参数上,num_classes包含背景,二分类地块就是 2。
4.2 损失函数与类别不平衡的处理
遥感影像里背景往往占 80% 以上,纯交叉熵会让模型学会「全预测背景」也能拿高分。常见做法是 Dice Loss 加交叉熵的组合:
class DiceBCELoss(nn.Module): def __init__(self, weight=0.5): super().__init__() self.weight = weight self.bce = nn.BCEWithLogitsLoss() def forward(self, pred, target): bce = self.bce(pred, target) # Dice 部分,sigmoid 后算重叠度 prob = torch.sigmoid(pred) inter = (prob * target).sum(dim=(2, 3)) union = prob.sum(dim=(2, 3)) + target.sum(dim=(2, 3)) dice = 1 - (2 * inter + 1e-6) / (union + 1e-6) return self.weight * bce + (1 - self.weight) * dice.mean()逻辑说明:BCE 管像素级分类,Dice 管整体重叠度,两者互补。weight我一般设 0.5,如果小目标漏检严重就降到 0.3,让 Dice 主导。加 1e-6 是防除零,这个后悔药一定要加。
4.3 分块推理与结果拼接
推理时按训练同样的窗口切,逐块预测,再拼回大图。拼接的关键是重叠区怎么融合:
def merge_patches(patches, coords, full_shape, patch_size=512): """重叠区取平均,避免拼接缝""" accum = np.zeros(full_shape[:2], dtype=np.float32) count = np.zeros(full_shape[:2], dtype=np.float32) for patch, (x0, y0) in zip(patches, coords): accum[y0:y0 + patch_size, x0:x0 + patch_size] += patch count[y0:y0 + patch_size, x0:x0 + patch_size] += 1 # 加 1e-6 防止边缘 count 为 0 时除零 return accum / (count + 1e-6)逻辑说明:重叠区取平均是最简单也最稳的融合方式,比取最大或投票都平滑。count记录每个像素被预测了几次,边缘像素可能只被覆盖一次,所以除之前加个极小值。拼完再按阈值二值化,最后用rasterio.features.shapes矢量化导出 GeoJSON。
5. 避坑与排查:遥感深度学习项目里最容易翻车的五件事
这一章全是血泪经验,每条按现象、原因、解决写。
现象一:训练 loss 正常下降,但验证集精度死活上不去。原因多半是训练集和验证集有空间重叠,同一块地既在训练又在验证,模型等于背答案。解决是按地理区域划分,不是随机划分,用影像的经纬度范围切块,保证验证区在训练区之外。
现象二:推理结果整体偏移几个像素。原因是切图时坐标记录和拼接时坐标对不上,常见于边缘往回退的逻辑没同步到拼接。解决是切图和拼接共用同一份 coords 列表,别各算各的。这个 bug 特别隐蔽,因为肉眼看结果「差不多对」,但叠加到 GIS 里就露馅。
现象三:模型把云阴影预测成水体。原因是训练集里云阴影样本太少,模型没见过。解决是在数据增强里加亮度扰动和对比度扰动,模拟不同光照,同时单独收集云阴影样本补进去。遥感里云是永恒的话题,别指望模型自己学会。
现象四:显存溢出,batch 只能设 1。原因是 patch_size 太大或用了 fp32。解决是开混合精度训练,torch.cuda.amp能省一半显存,再配合梯度累积模拟大 batch。别急着换卡,先把精度和 patch_size 调明白。
现象五:标注转 mask 后类别全变成 0。原因是 GeoJSON 的类别字段是字符串,int(cid)转换失败被静默跳过。解决是转换前先gdf[class_field].astype(int),并打印类别分布确认。这个坑我踩过两次,第二次是因为字段名写错了。
6. 进阶技巧:用局部聚焦辅助标记把标注效率提上去
前面讲的都是「有标注之后」的事,但遥感项目真正的瓶颈在标注。一个像素级标注员一天标不了几平方公里,而模型要好,至少得几千块标注。局部聚焦算法辅助标记是我目前用过最实在的提效手段,思路是先用少量标注训一个粗模型,让它对未标注区域出预测,人工只修错的地方,把「从零标」变成「改作业」。
具体做法分三步。第一步,先手工标 200 到 300 块,训一个基础模型,精度不用高,mIoU 有 0.5 就够用。第二步,用这个模型对剩余影像推理,把预测结果转成矢量,导入标注工具作为预标注图层。第三步,标注员在预标注基础上修改,只动错的地方。实测下来,同样标 1000 块,纯手工要两周,辅助标记能压到四天左右。
这里有个参数要调:预标注的置信度阈值。设太高,预标注漏得多,标注员要补的反而多;设太低,错的多,改起来也累。我的经验是阈值设在模型输出概率 0.5 到 0.6 之间,让预标注覆盖 70% 左右的真实目标,剩下的靠人工补。这个比例下标注员的心理负担最小,因为大部分框已经在了,只需要删和改。
还有一个技巧是主动学习式的迭代。每标完一批,把新标注加进去重新训模型,再用新模型出下一批预标注。这样模型越用越准,预标注质量越来越高,形成正循环。一般迭代三轮,预标注的可用率能从 60% 提到 85% 以上。
验证辅助标记有没有效果,别只看标注速度,要看最终模型精度。我一般留一个纯手工标注的测试集,对比「辅助标记训出的模型」和「纯手工训出的模型」在同一测试集上的表现。如果精度差距在 2 个点以内,说明辅助标记没引入系统性偏差,可以放心用。差距大就说明预标注把标注员带偏了,得回头查预标注的类别是否均衡。
这套流程跑下来,最大的体会是:遥感深度学习项目的成败,七分在数据,两分在切图拼接,一分在模型。我见过太多人把时间花在换骨干、调学习率上,结果数据划分错了、坐标对不齐,模型再好也白搭。所以我的习惯是每做一个新项目,先花半天把切图和拼接的坐标对齐验证一遍,用一张小图跑通全链路,再上大数据。这个习惯帮我省了无数次返工。希望帮到你。
本文还有配套的精品资源,点击获取