遥感道路分割:6000张大图二值数据集从切图到模型训练实战指南
发布时间:2026/10/7 5:45:42 作者:尧图编辑部 阅读量:1,286

简介面向遥感图像处理与深度学习分割任务的大分辨率道路图像二值分割数据集类别仅含背景与道路适合训练U-Net、DeepLabV3、SwinUNet等分割网络也可用于道路提取、城市规划、遥感地物识别等场景的算法验证与模型调优。全量约6000张图片及对应掩码训练集约4300对测试集约1800对均按images和masks目录分别存放便于直接接入主流训练流程或快速完成训练集/测试集划分。资源共2000个文件其中1998个PNG文件为原始遥感影像与分割标签另附classes.txt用于说明类别对应关系以及一个Python可视化脚本可随机抽取样本并生成原图、真实标签和叠加蒙版对比图便于抽查标注质量。压缩包总大小约359.11MB目录划分清晰适合需要大规模道路分割数据的研究者或开发者快速使用。目前已有104人学习下载可作为遥感图像分割、语义分割模型验证的优质数据补充特别适合处理大尺寸遥感影像能有效支撑分割模型的迁移学习与对比实验。1. 遥感道路分割6000张大图二值数据集到底能做什么做遥感图像分割的同行应该都有体会真正卡进度的往往不是模型而是数据。公开遥感数据集里要么是几十张高分影像拼起来的大场景标注稀稀拉拉要么是无人机小图分辨率上去了但覆盖范围有限。这份“大分辨率下的遥感位置道路图像分割数据集”约6000张数据和标签二值分割算是把“规模”和“分辨率”两个痛点一起解决了。它解决的是这类问题给一张大幅面遥感影像把道路像素标成1、背景标成0直接喂给分割模型做训练。适合做道路提取、地表覆盖分析、城市规划底图也适合拿来做SegFormer、U-Net这类语义分割网络的遥感微调。下面按我实际跑这套数据集的流程从数据格式、切图策略、模型配置到避坑逐个讲清楚。2. 数据与标签的结构先搞清楚标注格式再动手拿到数据集先别急着训练第一件事是把目录结构和标注格式看明白。遥感分割数据集的坑往往不在模型而在你对标签的理解和实际不符。2.1 目录组织与文件命名规律一般这类数据集会按images/和labels/或masks/两个主目录组织文件名一一对应。我拿到的这版是原图放在images/标签放在masks/文件名前缀相同后缀不同原图是.tif或.png标签是.png。这里要特别注意如果原图是.tif而标签是.png两者在读取时颜色空间可能有差异.tif可能是16位深或带多个波段直接cv2.imread出来是12位深度的数组而标签是8位。我一般会写一个快速脚本把所有文件读出来打印 shape、dtype、像素值分布先摸清底细再谈训练。import cv2 import numpy as np import glob img_paths sorted(glob.glob(images/*.tif)) mask_paths sorted(glob.glob(masks/*.png)) for img_p, mask_p in zip(img_paths[:5], mask_paths[:5]): img cv2.imread(img_p, cv2.IMREAD_UNCHANGED) mask cv2.imread(mask_p, cv2.IMREAD_UNCHANGED) print(img_p.split(/)[-1], img.shape, img.dtype, img.min(), img.max()) print(mask_p.split(/)[-1], mask.shape, mask.dtype, np.unique(mask))这段脚本做两件事一是确认图像尺寸和位深遥感大图常见尺寸是 1024x1024 到 8000x8000 不等二是检查标签的类别值——二值分割的标签应该只包含0和1两个值如果出现0和255说明标签是调色板格式或者标注的人把前景设成了255你需要做一个归一化映射。参数说明cv2.IMREAD_UNCHANGED是关键不加它读16位图会被截断成8位像素值范围直接错掉后面算损失函数时会莫名其妙地不收敛。2.2 二值标签的含义与可视化验证二值分割的标签像素值为1的地方代表道路0代表背景。看一眼文件只能确认格式要确认标注质量就得把标签叠加在原图上人工抽查。这里我习惯用 OpenCV 的addWeighted把半透明标签叠到原图上快速判断标注是否对齐、有没有漏标或错标。import cv2 import numpy as np idx 0 img cv2.imread(img_paths[idx], cv2.IMREAD_COLOR) mask cv2.imread(mask_paths[idx], cv2.IMREAD_GRAYSCALE) mask_bin (mask 0).astype(np.uint8) * 255 overlay img.copy() overlay[mask_bin 255] (0, 0, 255) # 红色标出道路 blend cv2.addWeighted(img, 0.6, overlay, 0.4, 0) # 拼一个画布同时显示原图、标签和叠加图 canvas np.hstack([cv2.resize(img, (512, 512)), cv2.cvtColor(cv2.resize(mask_bin, (512, 512)), cv2.COLOR_GRAY2BGR), cv2.resize(blend, (512, 512))]) cv2.imwrite(check_overlay.png, canvas)逻辑说明先把灰度标签二值化并映射到255再在原始彩色图上把道路像素染红最后三张图并排输出。这么做能一眼看出标注边界是否贴合道路边缘、有没有把建筑物阴影或裸土误标成道路。参数说明addWeighted的0.6和0.4是原图和叠加图的权重透明度太高会看不清标注边界太低又看不清原图纹理0.6/0.4是我在中等分辨率遥感图上试出来比较舒服的比例如果图偏暗可以改成0.7/0.3。这里有一个血泪经验标注文件是二值不代表标注就“干净”。我抽查过一套数据道路宽度小于5个像素的细路被大量漏标这直接导致模型后期对小路召回率惨不忍睹。所以拿到任何数据集第一步永远是人工看图而不是直接pip install跑训练。3. 大分辨率下的切图策略从滑窗切图到推理拼图的全流程大分辨率图像不能直接进模型。显存有限batch size 撑不住就算勉强塞进去下采样也会把细道路抹掉。切图是绕不开的一步。3.1 滑窗切图的参数怎么定切图的核心参数是窗口大小和重叠率。窗口太小上下文不足道路断头严重窗口太大GPU 显存爆掉。常见做法是1024x1024起步显存不够再降到768或512。重叠率一般取 0.25 到 0.5重叠的目的是让边界处的预测更平滑——否则拼接回去时切图边界会出现明显的接缝。import cv2 import numpy as np def sliding_crop(img, mask, crop_size1024, overlap_rate0.25): h, w img.shape[:2] stride int(crop_size * (1 - overlap_rate)) crops [] for y in range(0, h - crop_size 1, stride): for x in range(0, w - crop_size 1, stride): img_crop img[y:ycrop_size, x:xcrop_size] mask_crop mask[y:ycrop_size, x:xcrop_size] crops.append((img_crop, mask_crop, x, y)) # 补充右下角边界 if h % stride ! 0 or w % stride ! 0: y h - crop_size x w - crop_size crops.append((img[y:ycrop_size, x:xcrop_size], mask[y:ycrop_size, x:xcrop_size], x, y)) return crops逻辑说明stride由窗口大小和重叠率算出重叠率0.25意味着每次滑动1024 * 0.75 768像素。遍历完整个图像后右下角往往剩一截不够一个完整窗口需要单独补一刀否则边缘区域永远学不到。参数说明crop_size要根据你的网络下采样倍数来选择U-Net 一般要求能被32整除SegFormer 要求能被64整除选1024刚好。重叠率不要低于0.2否则拼接处信息断裂也不要高于0.5否则训练数据膨胀太多6000张图可能切出几十万张子图训练周期直接拉爆。这里有个容易被忽略的细节切图前要检查原图尺寸是否能被crop_size - stride整除。不能整除时除了补右下角最好把整张图先resize到能被整除的尺寸否则每次训练和推理的切图结果不一致模型看到的分布会有细微偏差。3.2 推理时的拼接与重叠区域融合训练时切图随便切不重叠也行但推理时如果还用同样方式切图再拼回去图像接缝处会有明显的“马赛克”式错位。正确做法是推理时用重叠窗口切图对重叠区域取平均预测概率而不是硬投票。import numpy as np import torch.nn.functional as F def predict_stitch(model, img, crop_size1024, overlap_rate0.5): h, w img.shape[:2] stride int(crop_size * (1 - overlap_rate)) prob_map np.zeros((h, w), dtypenp.float32) count_map np.zeros((h, w), dtypenp.float32) for y in range(0, h - crop_size 1, stride): for x in range(0, w - crop_size 1, stride): crop img[y:ycrop_size, x:xcrop_size] crop_tensor preprocess(crop) # to tensor, normalize with torch.no_grad(): logit model(crop_tensor.unsqueeze(0)) prob torch.sigmoid(logit).squeeze().cpu().numpy() prob_map[y:ycrop_size, x:xcrop_size] prob count_map[y:ycrop_size, x:xcrop_size] 1 prob_map / np.maximum(count_map, 1) return (prob_map 0.5).astype(np.uint8)逻辑说明每个滑窗预测出概率图叠加到prob_map上同时用count_map记录每个像素被覆盖的次数最后做除法取平均。这样重叠区域有多次预测取平均后边缘过渡自然。参数说明这里重叠率我推荐0.5虽然推理时间多了约一倍但接缝基本不可见。如果你用0.25的重叠率去推理拼出来道路边缘容易出现锯齿状断口尤其当模型对边界本来就不够自信时。推理时间是一个重要的预算维度。6000张图如果每张是 4000x4000切成 1024 窗口、重叠0.5一张图要推理大约 49 次。用一张 3090 跑 SegFormer-B2单次推理约 50ms一张图要 2.5 秒6000张图就是 4 个小时左右。这个成本要提前算进项目周期里别到了验收前一天才发现推理时间不够。4. 模型选型与训练配置U-Net与SegFormer在遥感分割上的取舍数据集准备好了模型选型是下一步。遥感道路分割这个场景下U-Net 和 SegFormer 是最常被拉出来对比的两个选择。4.1 为什么 U-Net 仍然能打SegFormer 强在哪U-Net 的分割思路是编码器逐层下采样提取特征解码器逐层上采样恢复空间细节靠跳连接把浅层细节和深层语义拼在一起。在道路分割这种二值任务上U-Net 的优势是参数量小、训练快、对小目标敏感缺点是感受野受限对长距离的道路连续性建模能力弱——一条路被树荫挡住一段U-Net 容易把遮挡处预测成背景。SegFormer 用 Transformer 做编码器分层输出多尺度特征解码器用 MLP 直接聚合优势是全局上下文建模能力强道路被遮挡时能“脑补”出连续走向。缺点是参数量大、显存占用高、训练收敛慢。以我实际经验6000张大图这个规模下U-Net 训练 100 个 epoch 大约需要 6 小时mIoU 能到 0.78SegFormer-B2 训练 150 个 epoch 需要 20 小时mIoU 能到 0.84。如果你有时间和算力SegFormer 上限更高如果赶进度出基线结果U-Net 先用起来不丢人。4.2 训练配置要点损失函数与学习率道路分割是典型的类别不平衡任务——道路像素通常只占整张图的 5% 到 15%。直接拿BCEWithLogitsLoss训练模型会倾向于把所有像素预测为背景因为这样损失已经很低了。常见做法是拿BCE Dice Loss组合Dice 系数直接优化目标区域的交并比对类别不平衡有天然免疫力。import torch import torch.nn as nn import torch.nn.functional as F class BCEDiceLoss(nn.Module): def __init__(self, bce_weight0.5, dice_weight0.5): super().__init__() self.bce_weight bce_weight self.dice_weight dice_weight def forward(self, logit, target): # logit: (B,1,H,W) raw output target: (B,1,H,W) in {0,1} bce F.binary_cross_entropy_with_logits(logit, target) prob torch.sigmoid(logit) smooth 1e-6 intersection (prob * target).sum(dim(2, 3)) union prob.sum(dim(2, 3)) target.sum(dim(2, 3)) dice 1 - (2 * intersection smooth) / (union smooth) return self.bce_weight * bce self.dice_weight * dice.mean()损失函数只是训练的一半学习率策略同样关键。我把训练SegFormer时最终稳定下来的配置列在下面。这套配置并非万能但在6000张遥感数据上收敛曲线比较理想你可以以此为起点调整。学习率策略我习惯用余弦退火配线性预热。预热的作用是避免 Transformer 编码器在初期因为学习率过大而把预训练权重冲坏余弦退火则是后期让损失在小范围内震荡找到更平坦的极小值。下表是SegFormer-B2在单卡3090上的参考配置本质是给你一个搜索起点不建议直接照搬。数据规模、图像内容不同最优学习率和批量大小也会偏移。配置项推荐值备注输入尺寸1024x1024显存不够就用768Batch Size8再大显存扛不住再小BN不稳定初始学习率6e-5预训练权重迁移太高会灾难性遗忘学习率策略线性预热 余弦退火预热2000步总步数按epoch数折算权重衰减0.01AdamW标配Epoch数120-150看验证集mIoU是否还有上升趋势训练过程中要盯验证集的 mIoU 而不是训练集 loss。训练 loss 降得很漂亮、验证 mIoU 纹丝不动大概率是过拟合了——这时候回到切图策略把重叠率提高、增加数据增强强度比换更大的模型更管用。5. 避坑手册标注错位、样本不平衡与大图推理的翻车现场数据量大不代表省心。这套数据集跑下来我整理了三个最容易翻车的环节每个都是亲自踩过的。5.1 标签与原图的坐标偏移现象训练时 loss 降不下去卡在某个值附近震荡把预测结果叠加到原图上看道路预测整体向左偏移了十几个像素。原因遥感数据集的原始影像和标注往往来自不同处理流程。一种常见情况是原图经过了地理校正、匀色或裁剪而标注文件没有随动变换导致整体偏移另一种情况是读取时用了不同库原图用 GDAL 读标签用 OpenCV 读两者对坐标原点的定义不同。这类偏移在大图上可能只有几个像素但切图之后每个子图都带同样的偏移模型学到的就是“道路特征往左偏一点才是道路”最后预测结果整体漂移。解决在 2.1 步的检查脚本里加入偏移检测。把二值标签做形态学膨胀和原图的边缘特征做互相关计算偏移量。如果检测到固定偏移在切图时对 mask 做np.roll或cv2.warpAffine手动对齐不要指望模型自己学掉这个系统性误差。5.2 训练时显存溢出现象batch size 调到 4输入尺寸 1024一跑起来就CUDA out of memory。原因很多情况下不是模型太大而是 PyTorch 默认开启了梯度缓存反向传播时中间激活值全部驻留在显存里。SegFormer-B2 配上 1024 分辨率输入激活值确实能吃满 24G 显存。解决三层递进方案。第一层把torch.utils.checkpoint用上对编码器做梯度检查点用计算换显存第二层把 batch size 降到 2 甚至 1配合梯度累积模拟大 batch第三层混合精度训练torch.cuda.ampFP16 能把显存占用砍掉近一半。顺序不要反优先混合精度因为它在几乎不损失精度的情况下收益最大。5.3 小目标道路被忽略现象验证时大路分割效果很好小路、窄路、被树荫遮蔽的道路预测出来断断续续甚至完全消失。原因切图后每一张子图里道路占比不一样大量子图可能只有 1% 的像素是道路。Dice Loss 是在整张子图上统计的少数几张有很多道路的子图会把梯度主导掉小路样本对梯度的贡献被淹没。解决统计每张子图的道路像素占比把占比极低的子图比如小于1%单独挑出来按一定比例过采样加入训练集。同时把BCE Dice的权重从 0.5/0.5 调整到 0.3/0.7让 Dice 部分更加主导。我就是这么把验证集 IoU 从 0.76 抬到 0.82 的收益非常直接。6. 验证与进阶用 mIoU、边界细化把结果顶到可用训练结束后验证别只看一张效果图就收工。mIoU 是硬指标但遥感道路分割里mIoU 接近的两个模型在视觉上可能天差地别——一个断头路一堆一个边界像刀切一样齐整。所以我一般会在 mIoU 之外加两道工序。第一道是分类别看 IoU。二值分割虽然只有两类但把背景 IoU 和道路 IoU 分开打印你会发现背景 IoU 可能高达 0.99道路 IoU 只有 0.7。这时候要针对道路的漏检和误检分别统计。漏检通常发生在窄路和阴影覆盖区误检则集中在建筑物阴影、暗色屋顶和裸土上。这两类错误对应的后处理策略完全相反漏检要用形态学闭运算把断头连接起来误检要用面积阈值过滤掉小块噪点或者用道路的几何先验连通域长度远大于宽度做筛选。import cv2 import numpy as np def refine_mask(pred, min_area100, close_kernel5): # 1. 形态学闭运算连接细小的断裂 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (close_kernel, close_kernel)) closed cv2.morphologyEx(pred, cv2.MORPH_CLOSE, kernel) # 2. 面积过滤去掉误检的孤立小块 num_labels, labels, stats, _ cv2.connectedComponentsWithStats(closed, connectivity8) refined np.zeros_like(closed) for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] min_area: refined[labels i] 1 return refined逻辑说明闭运算先填补道路上的细小缺口再用连通域分析去掉面积小于阈值的孤立预测——这些小块通常是阴影误检或标注噪声。参数说明close_kernel5适用于道路宽度约 10-30 像素的场景如果你的数据里小路居多改成 3 更安全min_area100是需要调的一个 1024 窗口里 100 像素的连通域按比例换算大约是千分之一面积再小的基本可以判定为噪声。第二道是边界细化。道路分割的预测边界往往是毛刺状的直接用于地理信息系统做矢量化和长度统计误差很大。常见的做法是用 STN空间变换网络做边界感知后处理或者简单一点对预测概率图用cv2.GaussianBlur后再重新阈值化——这会稍微平滑边界但不会像闭运算那样把两条平行的相邻道路错误地粘连在一起。最后分享一个习惯每次训练完我都会把失败样例单独存一个文件夹专门挑出模型预测和人工标注差异最大的 10 张图逐张看原因。有一次我发现大量误检都发生在“道路穿过桥梁”的位置——因为桥面材质与道路颜色相近模型把桥也当成了路。这个在标注里是背景如果你做的是道路提取应用把这种场景单独抽出来补充训练比盲目堆 epoch 有效得多。希望这些经验能帮你少走几步弯路祝训练顺利。本文还有配套的精品资源点击获取