资讯详情 遥感道路分割数据集实战:大分辨率影像划分与训练避坑指南
📅 2026/10/18 11:54:13
简介面向深度学习和遥感图像分析需求这份资源提供大分辨率遥感影像的道路分割数据集已按标准划分训练集与测试集。训练集包含4981张原始影像及对应Mask标注测试集包含1245对前景标注精细可直接用于U-Net、DeepLabV3等主流分割模型的训练与精度评估。压缩包内共有2000个文件以1999张JPEG图像为主另含1个Python可视化脚本整体压缩包约336.79MB。图像覆盖遥感原图与道路GT掩膜便于监督学习脚本可随机抽取样本一键生成原图、GT及叠加蒙版三合一效果图方便直观核验标注质量和模型输出。目前已有982人学习下载特别适合需要现成高分辨率道路数据的研究者、算法工程师与学生可省去采集、清洗和划分数据的繁琐流程专注网络设计与调优。1. 为什么大分辨率遥感影像道路分割数据集值得单独研究先想清楚它解决什么问题想象你手头的遥感影像分辨率到了 0.5 米级别道路在画面上是一条条清晰的浅色带可模型一测就碎。问题多半不在网络结构而在数据集本身大分辨率影像没有合理的裁剪粒度训练集和测试集划分不干净标注语义混着多种约定这些都是道路分割任务里最常见的翻车点。这篇文章直接用“深度学习之图像分割数据集大分辨率下的遥感影像道路图像分割数据集划分了训练集和测试集”当切入点拆清楚这类数据集该怎么看、怎么做、怎么避坑。没碰过遥感数据的读者可以照着步骤走已经跑过几轮训练的读者也能在划分逻辑和参数边界上找到值得回头检查的地方。2. 这类数据集的数据组织方式标注语义、大分辨率粒度与训练/测试划分的底层逻辑2.1 道路分割到底分什么三种标注约定直接影响模型行为道路分割在遥感任务里通常被建模为二分类语义分割每个像素要么是道路要么是背景。但“道路”这个语义并没有想象中统一。常见标注约定有三种只标路面、标路面加路肩、以及把道路中心线缓冲成面。大多数公开遥感道路数据集的默认约定是“正射视角下可见的路面”不包含被树冠遮挡的部分也不包含建筑出入口延伸到院内的区域。这个约定直接决定了掩码的边界形状换了一套约定指标对比就没有意义。我拿到一个数据集后第一件事就是随手抽几张影像和掩码叠加看确认它用的是哪一种约定。只标路面的掩码边缘和路面灰度变化贴合紧密中心线缓冲出来的掩码会明显把路口转角画成圆角路肩包含进来的掩码往往比可见路面宽出两到三个像素。这个判断并不需要专业知识肉眼对比就能看出来但它决定了后续所有评估结论的口径。另一个容易被忽略的维度是类别不平衡普通城市遥感影像中道路像素通常只占 5% 到 15%郊区和林区甚至会低于 3%。这会直接影响损失函数选择、训练集划分策略以及评估指标的解释方式后面我会专门展开。2.2 大分辨率到底指多大影像尺寸、地面采样距离与裁剪窗口的三角关系“大分辨率”这个词在遥感语境里经常被误解。深度学习里一张 1024×1024 的图已经算大但遥感影像单景常是 12000×12000 起步。真正参与训练时显存和网络结构决定了输入尺寸只能落在 256 到 1024 之间那中间这段差距怎么补靠滑动窗口裁剪而裁剪的粒度由两个因素决定目标网络输入尺寸和目标地面采样距离。地面采样距离GSD是每个像素对应的实际地面距离0.3 米 GSD 的影像里一条乡村土路能占到十几个像素宽2 米 GSD 的影像里同一段路只剩三四个像素。所以裁剪窗口固定时窗口内道路的形态完全由 GSD 决定。这个参数不统一的数据集训练时模型会在“宽路”和“窄路”两种形态之间反复横跳最终收敛到一个谁都不得罪的中间状态。我见过一个数据集把 0.5 米和 1 米 GSD 的影像混在同一目录下训练集里 0.5 米的占八成测试集里 1 米的居多最后的报告显示验证集指标很好部署时全碎。查看数据集时先看元信息里有没有给出统一的 GSD没有的话就自己从影像的地理范围反推。裁剪粒度的经验值我一般这样取训练窗口 512 或 640滑动步长设为窗口的 75% 到 87.5%也就是 overlap 64 到 128 像素。过大窗口会让显存压力陡增过小窗口又会让道路在局部看起来像块状纹理模型很难学到线性连续性。预测阶段可以用更大窗口加 overlap 平均因为不再需要反向传播。2.3 训练集和测试集划分的底层逻辑为什么空间重叠比样本数量更致命“划分了训练集和测试集”这句话看起来普通但遥感影像的划分和普通图像分类划分有本质区别。分类任务追求类别分布一致遥感分割任务的核心约束是空间不重叠。如果原始大图只有两三张常见的省事做法是对大图分块后把所有 patch 随机洗牌再分配训练集和测试集里会出现大量相邻甚至重叠的 patch两张图的差异只有几十个像素的平移。这种划分下测试集指标虚高得离谱模型换到新地形上立刻漏出原形。正确的划分基准是空间范围先划定训练区域和测试区域再在各自区域内裁剪。训练区与测试区之间要有明确的几何边界比如按经纬度多边形切分、按地块编号切分或者按单景影像切分。只有一张原始影像时宁可让训练集小一些也要保证测试区域与训练区域在地理上分离。一个划分合理的数据集通常会随数据提供每个 patch 的起始行列号或者原始影像编号方便使用者回溯空间归属。没有这些元信息的“已划分”数据集可信度要打个问号。这类空间泄漏问题在训练第 4 章第 3 节还会再次出现因为即使你拿到的划分没问题自己二次调整时也可能亲手把泄漏引进来。3. 从原始影像制作划分好的道路分割数据集可复现的完整流程3.1 数据准备统一 GSD、统一像素尺寸、建立索引表假设你手里有几景原始影像可能是带坐标的 GeoTIFF也可能是普通 PNG 加同名掩码。第一步是把所有影像统一到同一个 GSD 和像素尺寸上。这一步常见做法是栅格重采样把不同来源的数据对齐到目标分辨率。下面这段脚本用栅格库完成重采样并保留地理变换信息便于后续做矢量栅格化对齐。import rasterio from rasterio.enums import Resampling src_path area_12.tif dst_path area_12_unified.tif target_gsd 0.5 # 目标地面采样距离单位米/像素 with rasterio.open(src_path) as src: scale_x src.res[0] / target_gsd scale_y src.res[1] / target_gsd new_width int(src.width / scale_x) new_height int(src.height / scale_y) # 用缩放比例更新仿射变换保持影像地理范围不变 transform src.transform * src.transform.scale( (src.width / new_width), (src.height / new_height) ) profile src.profile profile.update(widthnew_width, heightnew_height, transformtransform) with rasterio.open(dst_path, w, **profile) as dst: for band in range(1, src.count 1): data src.read( band, out_shape(new_height, new_width), resamplingResampling.bilinear ) dst.write(data, band)这段代码的核心在于scale和transform的处理它是按目标 GSD 反推缩放系数而不是直接把尺寸硬顶到某个值。这样做的好处是影像的地理覆盖范围不变后续所有 patch 的坐标都能追溯。重采样算法上多光谱影像用双线性足够近红外波段做归一化植被指数时可以考虑三次卷积但掩码保存时务必要用最近邻不然道路边缘会被插值成模糊过渡带二值化以后出现一圈假像素。统一尺寸后还建议顺手做一遍像素值统计不同批次的影像灰度分布差异过大的话需要做直方图匹配或者归一化避免模型把不同传感器的色调差异当成语义差异。最后把这些元信息整理成一张索引表记录影像编号、GSD、行列号、像素值范围这一步能减少后续很多排查时间。3.2 掩码生成与二值化矢量栅格化时最容易踩的对齐问题道路掩码的来源通常有两种已有地理信息矢量栅格化或者人工在影像上逐像素勾勒。人工勾勒质量高但成本大矢量栅格化速度快但坐标对齐问题非常容易让掩码整体偏移。下面是用矢量面状道路数据生成掩码的脚本。import geopandas as gpd from rasterio.features import rasterize # 假设已经准备好了统一后的影像transform 来自该影像 roads gpd.read_file(road.shp) road_geoms [geom for geom in roads.geometry if geom is not None] # all_touched 控制边界像素的判定方式True 表示只要碰到就算 mask rasterize( shapes[(geom, 1) for geom in road_geoms], out_shape(new_height, new_width), transformtransform, fill0, all_touchedTrue, dtypenp.uint8 )栅格化脚本里有三个参数值得反复检查transform必须是最终保存影像的transform哪怕只差一个像素掩码整体就会偏移all_touched设为 True 时道路边界会往外扩一点设为 False 时由像素中心点判定道路会略细这个选择要和模型评估指标的口径统一dtype用uint8即可二值掩码不需要更高的位深。另外很多人会把掩码和影像分别裁剪后再检查对齐这是一种低效做法。正确的验证方式是先把掩码栅格化到整幅大图尺度随后和影像一起按同一窗口裁剪从像素层面保证二者严格对齐。人工标注路径下我习惯把影像放大到 300% 到 400% 再沿着路面边缘描边背景类要顺手把阴影里能看见的路段标出来否则模型会以为“带有阴影纹理的区域不是道路”。3.3 按空间区域切分训练集和测试集一个带防泄漏逻辑的划分脚本下面这个脚本演示的是单张大图内按区域划分后再裁剪。实际工程中如果有多个影像文件最优做法是按图编号划分图与图之间的划分区域天然独立代码逻辑完全一致。import random import numpy as np import rasterio from rasterio.windows import Window from pathlib import Path src_path area_12_unified.tif mask_path area_12_mask.tif patch_size 512 overlap 64 out_dir Path(road_split) random.seed(42) with rasterio.open(src_path) as src, rasterio.open(mask_path) as mask: h, w src.height, src.width # 只演示单张大图上半部分作为训练区域下半部分作为测试区域 train_region (0, h // 2, 0, w) test_region (h // 2, h, 0, w) def crop_region(region, out_folder, prefix): r0, r1, c0, c1 region idx 0 for row in range(r0, r1 - patch_size, patch_size - overlap): for col in range(c0, c1 - patch_size, patch_size - overlap): win Window(col, row, patch_size, patch_size) img src.read([1, 2, 3], windowwin).transpose(1, 2, 0) lab mask.read(1, windowwin) # 低信息量 patch 直接跳过避免训练集被纯背景淹没 if np.sum(lab) 50: continue out_path out_folder / f{prefix}_{idx:05d}.npz np.savez_compressed(out_path, imageimg, labellab) idx 1 crop_region(train_region, out_dir / train, train) crop_region(test_region, out_dir / test, test)这段脚本的防泄漏逻辑体现在划分发生在裁剪之前训练 patch 全部来自上半区域测试 patch 全部来自下半区域。两个区域的 patch 可能在几何边界处仍有视觉连续性但这是因为真实道路本身是跨区域的模型在测试时遇到了和训练区域相邻的地块这正是遥感任务要面对的真实分布。参数上overlap控制相邻 patch 的重叠程度。训练时 overlap 设 64 到 128 就够重叠更多只能增加样本间相关性对多样性有害测试时为了做预测融合可以把 overlap 提升到 128 甚至 256。过滤阈值 50 是针对 512×512 patch 和 0.5 米 GSD 的经验值换算成道路像素占比约 0.02%。如果你做的是 1 米 GSD 的赛区阈值可以提高到 200但测试集永远不要做这种过滤因为真实推理时你不知道哪里会有路。4. 用这套数据训练分割模型数据管线与损失函数的参数细节4.1 Dataset 类里必须对齐的通道顺序、像素范围和标签 dtype拿到划分好的数据集最容易出问题的其实是加载器里的小细节而不是网络结构。遥感影像常见的存储格式是 GeoTIFF里面波段顺序可能是 R、G、B也可能是 R、G、B、NIR还有可能是 BGR。用普通图像库读取 PNG 时还要确认通道顺序是否被隐式翻转。下面是一个通用的 Dataset 实现。import numpy as np import torch from torch.utils.data import Dataset from pathlib import Path class RoadDataset(Dataset): def __init__(self, folder, normalizeTrue, augmentFalse): self.files sorted(Path(folder).glob(*.npz)) self.normalize normalize self.augment augment def __len__(self): return len(self.files) def __getitem__(self, idx): data np.load(self.files[idx]) img data[image].astype(np.float32) label_raw data[label].astype(np.int64) # 遥感影像可能带近红外等扩展波段训练只用前三个波段 if img.ndim 3 and img.shape[2] 4: img img[:, :, :3] # 标准化到 0-1 区间 if self.normalize: img / 255.0 img img.transpose(2, 0, 1) # HWC - CHW # 标签归一化很多数据集把道路像素标成 255这里统一成 1 label (label_raw 0).to(torch.int64) return torch.from_numpy(img), label这个加载器的关键点有三个第一label_raw先转成int64再做二值化避免在.npz里存了uint8掩码时被CrossEntropyLoss拒绝第二标签先把大于 0 的像素统一归为道路类这样无论数据集用的是 1 还是 255 都不会出错第三transpose必须在转 tensor 之前完成如果先转 tensor 再转置要么报错要么白转。注意如果你手头的数据是 PNG 文件对而不是 npz加载时要在__init__里通过文件名把影像和掩码配对并提前校验缺失文件。避免训练到一半因为某个文件读不出来而中断。4.2 数据增强在遥感道路上的取舍翻转、旋转、亮度扰动要克制遥感影像没有绝对的“上下”方向因为传感器是从头顶拍摄的这为数据增强提供了很大空间。但不同增强操作的收益差异不小。随机翻转和 90 度倍数旋转基本无损能直接扩大数据多样性任意角度旋转会产生边缘空白区处理不好会引入黑边伪影亮度对比度扰动要谨慎幅度过大会让沥青和水泥地的区分度变得不可靠。增强操作推荐配置注意点水平/垂直翻转概率 0.5道路方向无关翻转安全90 度旋转k 取 1/2/3概率 0.3配合翻转等价于 8 方向增广任意角度旋转避免或做边缘填充黑边会被模型当成背景特征亮度扰动±0.1归一化后过大容易混淆道路材质随机裁剪保证道路占比下限裁剪需先检查 mask 非空下面是一个推荐增强函数旋转只用 90 度倍数亮度扰动幅度控制在 ±0.1。import random def augment(img, label): if random.random() 0.5: img np.flip(img, axis0) label np.flip(label, axis0) if random.random() 0.5: img np.flip(img, axis1) label np.flip(label, axis1) if random.random() 0.3: k random.choice([1, 2, 3]) img np.rot90(img, k, axes(0, 1)) label np.rot90(label, k, axes(0, 1)) if random.random() 0.5: img random.uniform(-0.1, 0.1) img np.clip(img, 0.0, 1.0) return img, label这里容易踩的坑是np.rot90的旋转方向和 k 的含义。它每次只转 90 度k 参数决定转几次使用时要注意图像和标签必须用同一个 k否则训练标签和图像错位之后指标再高也是自欺欺人。亮度扰动我限制在 ±0.1因为遥感道路分割里道路和旁边裸露土地的灰度差异本来就小扰动过大模型会把颜色相近的裸地学成道路。4.3 训练时让损失函数关注道路边界和类别不平衡道路像素占比低普通交叉熵收敛后模型容易输出全背景。常见做法是交叉熵加上 Dice 系数作为辅助损失。几乎所有人第一次跑遥感分割都会遇到这个问题不加 Dice 时损失下降得很快但验证集的 IoU 卡在 40% 上不去加了 Dice 以后训练曲线变得曲折但最终 IoU 能明显改善。class DiceLoss(torch.nn.Module): def __init__(self, smooth1e-5): super().__init__() self.smooth smooth def forward(self, pred, target): # pred 为 softmax 后概率图 [B, C, H, W] # target 为 long tensor [B, H, W]道路像素为 1 pred pred.permute(0, 2, 3, 1).contiguous().view(-1, 2) target target.view(-1) pred_road pred[:, 1] target_bin (target 1).float() inter (pred_road * target_bin).sum() union pred_road.sum() target_bin.sum() dice (2.0 * inter self.smooth) / (union self.smooth) return 1.0 - diceDiceLoss 实现本身简单但有两个参数会直接影响训练稳定性。smooth设为 1e-5 在单卡 float32 下足够避免除零设大了会让 dice 值失真。另一个是概率阈值训练时我们默认把 softmax 输出中大于 0.5 的类别判为道路但在最终推理时遥感道路分割往往需要调低阈值比如 0.4 甚至 0.35因为漏检一条路的代价比多预测一小块空地高业务上通常希望召回优先。交叉熵和 Dice 的权重比我常用的起点是 0.7 : 0.3。如果数据里道路占比特别低可以把 Dice 权重提到 0.5但要盯着训练集精度曲线权重过高模型会过度放大道路类背景误检率上升。训练时的一个小技巧是先用普通交叉熵跑 10 个 epoch再加 DiceLoss 联合训这样可以避免模型一开始被类别不平衡带偏Dice 部分像是一个精调的辅助把手。5. 大分辨率遥感道路分割避坑清单五个高频翻车现场与排查路径5.1 现象验证集 IoU 很高换到新影像上全碎这类情况在遥感道路分割里占据新手踩坑的头号位置。表现是训练日志里验证集 IoU 已经到 0.75可是把模型放在一张全新区域的影像上道路预测要么整段消失要么把大片农田当成了路。原因基本就两个一是数据划分存在空间泄漏测试 patch 和训练 patch 来自同一片区域模型看到的“新样本”其实在训练时见过长相几乎一致的邻居二是训练集地表覆盖类型太单一比如只有城市建成区没有林地、农田、裸地这些背景。排查路径很清楚把预测图和真实掩码叠加显示如果错误集中在边界处那多半是标注噪声或数据泄漏如果整段道路消失或大片误检那是分布覆盖问题。前者要回溯划分脚本后者要补充数据或做地域层面的增广。不要急着换网络结构和调 loss数据分布问题在模型层面怎么调都是隔靴搔痒。5.2 现象测试集上类别正确但道路断裂严重道路断裂表现为本来连续的公路被预测成一段一段的虚线。根本原因是模型的感受野不足以在 patch 内建立长距离连接。512×512 窗口在 0.5 米 GSD 下只覆盖 256 米见方一条主干道可能横跨整个窗口模型看到了路面的局部纹理却看不到整条路的走向断掉就成了必然。这类问题有一个不算玄学的解决手段推理时用重叠滑窗加平均预测。滑窗步长设为 128每个像素会被多个窗口覆盖取道路类概率的平均值再阈值化连续性会明显改善。另外可以试试把预测输入放大一级比如下采样整张大图得到低分辨率全局视图再和局部窗口预测融合。低分辨率视图提供全局走向高分辨率窗口提供路面细节两者互补的效果在这类任务上立竿见影。5.3 现象显存不够直接 OOM训练中断这种场景很常见输入尺寸是 512batch size 调到 4 都放不下。我的建议是不要一味缩小 batch size小 batch 会让 BatchNorm 统计量波动很大训练稳定性变差。排查顺序是先确认是不是模型中间特征图过大可以通过逐步减少输入尺寸来定位再用混合精度训练显存压力通常能降一半。如果这些都试过还不够就把训练窗口和推理窗口解耦。训练用 384 或 448推理用 512 加重叠滑窗。这样做会牺牲一点训练时的感受野但遥感道路分割对高分辨率细节的依赖高于对大感受野的依赖退一步往往比强行迭代大模型更划算。要特别注意改小输入后要相应调低学习率或做 warmup否则模型会更容易过拟合。5.4 现象训练曲线正常但掩码可视化出现系统性错位预测的道路边界整体比真实道路偏移几个像素而且偏移方向一致这是典型的坐标系错位问题。根源通常在数据制作阶段原始影像和标注矢量用了不同的投影基准或者栅格化时transform拿错了。这类错位用数据增强、调损失函数都解决不了模型看到的是“路在真实位置的旁边”怎么学都对不齐。排查方式非常直接把影像和标签以 0.5 的透明度叠加显示放大到 200%。如果道路边缘的两层轮廓完全平行但不重合那就是坐标层的问题。解决办法是回到 3.1 和 3.2 的步骤检查transform来源和重采样方式重新生成掩码。这类问题一旦发生训练时间越长浪费越大所以在划分数据之前做一次叠加检查比训练完再回头看要省得多。5.5 现象划分脚本执行完训练集和测试集的 patch 高度相似如果你拿到的“已划分”数据集是用随机洗牌方式生成的那测试集里很可能混着训练集的近亲。识别方法可以做一个抽样检查从测试集随机取一个 patch和训练集所有 patch 做像素差如果最小差值几乎到零说明至少存在重复或重叠区域。避免这个问题的唯一可靠方案就是按空间区域划分。只有一张影像时把上半部分和下半部分分开是最粗粒度的切法也可以按照道路网、河流、行政区边界做更复杂的多边形切分但核心原则只有一个任何两个属于不同集合的 patch它们对应的地面区域不能有交集。如果你的业务场景连一张影像也没有只有若干小块数据那宁可加大测试集覆盖范围也别为了凑训练样本把同一区域拆进两个集合。空间连续性远比样本数量更重要。6. 用半小时验证一个陌生道路分割数据集动手前的快速体检6.1 统计道路像素占比检查数据集是否“虚胖”很多数据集表面上有几万张 patch实际道路像素少得可怜。拿到数据后先跑一个统计脚本把每个 patch 的道路像素占比算出来看分布。import numpy as np from pathlib import Path folder Path(road_split/train) ratios [] for f in folder.glob(*.npz): d np.load(f) label d[label] ratios.append(np.sum(label 0) / label.size) ratios np.array(ratios) print(min :, ratios.min()) print(median :, np.median(ratios)) print(max :, ratios.max()) print(道路为 0 的 patch 数量:, np.sum(ratios 0))如果大量 patch 的道路占比为 0训练时这些样本只能提供背景语义对道路特征学习没有贡献。正常情况下一个划分合理的道路分割数据集非空 patch 的比例应该超过 80%训练集的道路像素占比中位数不应该低于 2%。低于这个值说明要么裁剪策略太粗暴要么数据本身标注稀疏训练时就要靠增强和损失权重去补。6.2 用一个小网络快速验证数据有没有训练潜力我的习惯是先不加载完整训练集而是从测试集取 200 个 patch再从训练集取 200 个 patch用一个轻量分割网络跑 30 个 epoch。如果验证集 IoU 在前 10 个 epoch 内能超过 0.3说明数据标注和划分没有大问题如果始终在 0.1 附近徘徊先别怀疑网络回头查标注和划分。这 30 个 epoch 的代价极小但能过滤掉大部分数据质量陷阱比直接开完整训练再发现数据有问题划算得多。这个方法也适合验证“已划分数据集”是否真的可以放心使用。我之前拿到一份来源模糊的数据训练集指标一直正常用 200 张快速子集验证时发现测试集里有三张 patch 和训练集几乎一模一样。重新按空间区域划分后模型最终指标掉了十几个点但换到新区域上的表现反而明显变好。数据划分的干净程度决定了你的算法评测是自欺欺人还是真实可靠。这是我在数据集准备上最重视的一条经验希望帮到你。本文还有配套的精品资源点击获取