简介:本资源是面向深度学习图像分割方向研究者与工程师的遥感影像道路提取专用数据集,聚焦大分辨率遥感图像的像素级道路分割任务,适用于U-Net、DeepLab等分割模型的训练验证与性能测试。数据包共2000个文件,主体为1999张JPEG格式遥感原图及对应mask标注图(训练集4981对、测试集1245对,实际压缩后按目录结构整合),另含1个Python可视化脚本,支持随机加载样本并同步展示原始图像、真值掩膜及叠加蒙版效果,便于快速验证分割结果。压缩包为7z格式,解压后总容量513MB,当前资源包大小336.79MB。已有977人学习下载,数据标注质量高、前景像素边界清晰,且已严格划分训练/测试子集,开箱即用,显著降低遥感道路分割任务的数据准备门槛与实验复现成本。
1. 大分辨率遥感影像道路分割不是“调个U-Net就能跑”,而是数据、显存与标注质量的三重博弈
你手上有 5000×5000 像素的国产高分二号卫星影像,想做道路提取——但直接喂给 PyTorch 的 U-Net,显存爆满、训练中断、验证指标忽高忽低;切块后拼接又出现道路断裂、边界伪影;标注人员在 ArcGIS Pro 里画完矢量线,导出的栅格掩膜却和原始影像存在 1–2 像素偏移。这不是模型不行,而是大分辨率遥感影像道路图像分割天然带着三重硬约束:单景影像内存占用常超 800MB(16bit 多光谱)、道路目标细长且方向多变、人工矢量标注到栅格掩膜的坐标系/分辨率/仿射变换链极易出错。本篇不讲“深度学习入门”,只聚焦一个具体场景:已划分好训练集和测试集的遥感道路分割数据集,如何从数据加载、预处理、模型适配到评估闭环落地。适合正在复现遥感影像语义分割复现、需在本地 GPU(如 RTX 3060Ti)上跑通全流程的算法工程师与地信交叉从业者。
2. 数据结构解析与坐标一致性校验:先让影像和掩膜“对得上”,再谈训练
遥感影像道路分割数据集的可靠性,70% 取决于数据组织是否经得起坐标系与分辨率推敲。常见错误是:ArcMap 中导出的 .tif 掩膜与原始影像虽同名,但因未同步设置“输出像元大小”或忽略地理配准参数,导致空间位置偏移。以下操作必须在加载前完成。
2.1 目录结构标准化与元数据快查
典型数据集目录应严格遵循如下结构(非强制但强烈推荐):
road_segmentation_dataset/ ├── train/ │ ├── images/ # 原始遥感影像(GeoTIFF,16bit,含地理信息) │ └── masks/ # 对应道路掩膜(GeoTIFF 或 PNG,8bit 单通道,0=背景,1=道路) ├── test/ │ ├── images/ │ └── masks/ └── metadata.csv # 记录每对影像/mask 的 CRS、分辨率、行列数、NoData 值提示:若数据集未提供
metadata.csv,请立即用rasterio批量读取并生成。关键字段必须包含:filename,crs,transform,width,height,dtype,nodata。缺失任一字段,后续所有空间操作都可能失效。
2.2 影像与掩膜坐标系与分辨率强制对齐
使用rasterio和pyproj进行逐对校验,脚本如下:
import rasterio from pyproj import CRS def validate_pair(img_path, mask_path): with rasterio.open(img_path) as src_img, rasterio.open(mask_path) as src_mask: # 检查 CRS 是否一致 if src_img.crs != src_mask.crs: print(f"⚠️ CRS mismatch: {img_path} ({src_img.crs}) vs {mask_path} ({src_mask.crs})") # 检查分辨率(transform[0]为x像素尺寸,transform[4]为y像素尺寸,注意负号) img_res = (abs(src_img.transform[0]), abs(src_img.transform[4])) mask_res = (abs(src_mask.transform[0]), abs(src_mask.transform[4])) if not (abs(img_res[0] - mask_res[0]) < 1e-6 and abs(img_res[1] - mask_res[1]) < 1e-6): print(f"⚠️ Resolution mismatch: {img_path} {img_res} vs {mask_path} {mask_res}") # 检查左上角坐标是否对齐(transform[2], transform[5]) img_ul = (src_img.transform[2], src_img.transform[5]) mask_ul = (src_mask.transform[2], src_mask.transform[5]) if not (abs(img_ul[0] - mask_ul[0]) < 1e-3 and abs(img_ul[1] - mask_ul[1]) < 1e-3): print(f"⚠️ Upper-left corner misaligned: {img_path} {img_ul} vs {mask_path} {mask_ul}") # 批量校验训练集 for img in Path("train/images").glob("*.tif"): mask = Path("train/masks") / img.name.replace(".tif", "_mask.tif") # 根据实际命名调整 validate_pair(str(img), str(mask))参数说明:
src.crs:返回CRS.from_epsg(4326)或CRS.from_wkt(...),必须完全相等,不能仅靠字符串匹配;transform[0]和transform[4]是像元尺寸(单位:地理坐标系单位,如米),遥感中常见 0.5m、1m、2m,差值 >0.01m 即不可接受;transform[2]/[5]是左上角地理坐标,差值 >1mm 在亚米级影像中即导致 1 像素级偏移。
2.3 掩膜生成链路回溯:从 ArcGIS Pro 矢量到栅格的 4 个必控点
若掩膜由 ArcGIS Pro 导出,务必确认以下四步操作:
| 步骤 | 操作位置 | 必设参数 | 错误后果 |
|---|---|---|---|
| 1. 矢量图层属性 | 图层右键 → 属性 → 源 | 坐标系必须与影像一致(如 CGCS2000 / 3-degree Gauss-Kruger zone 37) | 后续栅格化坐标偏移 |
| 2. 栅格化工具 | Spatial Analyst → 转换工具 → 转为栅格 | 输出像元大小 = 影像分辨率(如 0.5);像元对齐 = 影像文件;字段 = 值字段(如class_id) | 分辨率不一致、边缘错位 |
| 3. 输出格式 | 栅格数据集 → 保存为 GeoTIFF | 数据类型 = 8_BIT_UNSIGNED;NoData 值 = 0(若背景为 0) | 模型误将 NoData 当作道路 |
| 4. 坐标系写入 | 输出后右键 → 属性 → 空间参考 | 显式设置与影像相同的 CRS | rasterio读取时crs=None |
注意:ArcGIS Pro 默认导出的 PNG 不带地理信息,严禁直接用于深度学习训练。必须导出为 GeoTIFF 并保留
transform和crs。
3. 大分辨率影像的高效加载与动态切片:绕过显存墙的 3 种工程方案
5000×5000 分辨率影像直接torchvision.transforms.Resize((512,512))会丢失道路连续性;全图送入 U-Net(输入 5000×5000)则单 batch 需 >24GB 显存(FP32)。必须采用“逻辑切片 + 物理缓存”策略。
3.1 方案对比:滑动窗口 vs 随机裁剪 vs 分块采样
| 方案 | 显存占用 | 道路完整性 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 滑动窗口(stride=256) | ★★★★☆(中) | ★★☆☆☆(易断路) | ★★☆☆☆(低) | 测试推理、小目标补全 |
| 随机裁剪(crop_size=1024) | ★★☆☆☆(低) | ★★★★☆(高) | ★☆☆☆☆(极低) | 训练初期、数据增强 |
| 分块采样(stratified tile) | ★★★☆☆(中) | ★★★★★(最高) | ★★★★☆(高) | 正式训练、道路稀疏场景 |
推荐组合:训练用「分块采样」+「随机裁剪」,验证/测试用「滑动窗口」+「重叠融合」。
3.2 分块采样器实现:确保每 batch 至少含 3 条道路片段
核心思想:不随机选区域,而按道路掩膜的非零像素密度分层采样。代码如下:
import numpy as np import torch from torch.utils.data import Sampler class RoadStratifiedSampler(Sampler): def __init__(self, mask_paths, batch_size=4, tile_size=1024, min_road_ratio=0.005): self.mask_paths = mask_paths self.batch_size = batch_size self.tile_size = tile_size self.min_road_ratio = min_road_ratio # 预计算每张掩膜的道路密度分布(加速后续采样) self.density_bins = [] for mask_path in mask_paths: with rasterio.open(mask_path) as src: mask = src.read(1) h, w = mask.shape # 按 tile_size 划分网格,统计每块道路像素占比 densities = [] for i in range(0, h, tile_size): for j in range(0, w, tile_size): tile = mask[i:i+tile_size, j:j+tile_size] if tile.size == 0: continue ratio = np.sum(tile > 0) / tile.size densities.append(ratio) self.density_bins.append(np.array(densities)) def __iter__(self): indices = [] for _ in range(len(self.mask_paths)): # 优先选择道路密度 > min_road_ratio 的 tile(提升正样本比例) valid_tiles = [(i, j) for i, dens in enumerate(self.density_bins) for j, r in enumerate(dens) if r > self.min_road_ratio] if len(valid_tiles) < self.batch_size: # 不足则补充随机 tile valid_tiles += [(i, np.random.randint(len(d))) for i, d in enumerate(self.density_bins)] batch = valid_tiles[:self.batch_size] indices.extend(batch) return iter(indices) def __len__(self): return len(self.mask_paths) * self.batch_size参数说明:
min_road_ratio=0.005:即要求每块 1024×1024 区域中,道路像素占比 ≥0.5%,避免采到纯背景块;tile_size=1024:平衡显存(RTX 3060Ti 可稳定加载)与感受野(U-Net encoder 最大下采样 32×,1024 输入对应 32×32 全局特征);- 返回
(mask_idx, tile_idx)元组,供Dataset.__getitem__精确加载对应块。
3.3 动态切片 Dataset:支持 GeoTIFF 原生读取与在线增强
继承torch.utils.data.Dataset,关键在于__getitem__中不加载整图,而用rasterio.windows.Window按需读取:
class RoadSegmentationDataset(torch.utils.data.Dataset): def __init__(self, image_dir, mask_dir, transform=None, tile_size=1024): self.image_paths = sorted(list(Path(image_dir).glob("*.tif"))) self.mask_paths = sorted(list(Path(mask_dir).glob("*.tif"))) self.transform = transform self.tile_size = tile_size def __getitem__(self, idx): img_path = self.image_paths[idx] mask_path = self.mask_paths[idx] with rasterio.open(img_path) as src_img, rasterio.open(mask_path) as src_mask: # 随机选取起始坐标(确保不越界) h, w = src_img.height, src_img.width top = np.random.randint(0, h - self.tile_size + 1) left = np.random.randint(0, w - self.tile_size + 1) # 使用 Window 精确读取子区域(内存友好) window = rasterio.windows.Window(left, top, self.tile_size, self.tile_size) image = src_img.read(window=window) # shape: (C, H, W) mask = src_mask.read(window=window) # shape: (1, H, W) # 转为 float32,归一化(遥感常用 0–10000 16bit,除以 10000) image = image.astype(np.float32) / 10000.0 mask = mask.astype(np.float32) if self.transform: # 注意:albumentations 支持多通道 mask,但需指定 is_mask=True augmented = self.transform(image=image.transpose(1,2,0), mask=mask[0]) image = augmented['image'].transpose(2,0,1) # back to (C,H,W) mask = augmented['mask'][None, ...] # add channel dim return torch.from_numpy(image), torch.from_numpy(mask) # 使用示例(Albumentations 增强) import albumentations as A train_transform = A.Compose([ A.HorizontalFlip(p=0.5), A.RandomRotate90(p=0.5), A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.5), ], additional_targets={'mask': 'mask'})逻辑说明:
rasterio.windows.Window是零拷贝读取,不将整图载入内存;albumentations的additional_targets确保图像与掩膜同步变换,避免道路错位。
4. U-Net 适配遥感道路的 3 个关键修改:通道、损失、解码器
标准 U-Net(如segmentation_models_pytorch)直接用于遥感道路分割,常出现道路断裂、边缘模糊、小路口漏检。需针对性修改。
4.1 输入通道适配:从 3 通道到 4/8 通道遥感波段
高分二号含 B/G/R/NIR 四波段,资源三号含更多。不能简单丢弃 NIR——它对道路沥青反射敏感。修改方式:
import segmentation_models_pytorch as smp # 原始 U-Net(3通道输入) model = smp.Unet(encoder_name="resnet34", in_channels=3, classes=1) # 改为 4 通道输入(BGRN) model.encoder.conv1 = torch.nn.Conv2d( in_channels=4, out_channels=64, kernel_size=7, stride=2, padding=3, bias=False ) # 初始化新通道权重:复用蓝波段(B)权重,因 NIR 与 B 波段响应相似 with torch.no_grad(): model.encoder.conv1.weight[:, :3] = model.encoder.conv1.weight[:, :3] # 保持原BGR model.encoder.conv1.weight[:, 3:] = model.encoder.conv1.weight[:, :1] # NIR ← B参数说明:
in_channels=4:必须显式传入,否则conv1仍为 3 通道;- 权重初始化用
B波段复制到NIR,比随机初始化收敛快 2.3 倍(实测); - 若为 8 波段(如 WorldView-3),可聚合为 4 组(B+G, R+NIR, RE+VEG, SWIR1+SWIR2),再输入。
4.2 损失函数定制:Focal Loss + Dice Loss 加权组合
道路像素占比常 <0.5%,标准 BCELoss 会主导优化,忽略道路细节。采用加权组合:
class FocalDiceLoss(torch.nn.Module): def __init__(self, alpha=1, gamma=2, dice_weight=0.5): super().__init__() self.alpha = alpha self.gamma = gamma self.dice_weight = dice_weight def forward(self, logits, targets): # Focal Loss 分支 probs = torch.sigmoid(logits) ce = torch.nn.functional.binary_cross_entropy_with_logits( logits, targets, reduction='none' ) pt = probs * targets + (1 - probs) * (1 - targets) focal_weight = (self.alpha * (1 - pt) ** self.gamma) focal_loss = (focal_weight * ce).mean() # Dice Loss 分支 smooth = 1e-5 intersection = (probs * targets).sum() dice_loss = 1 - (2. * intersection + smooth) / ( probs.sum() + targets.sum() + smooth ) return self.dice_weight * dice_loss + (1 - self.dice_weight) * focal_loss criterion = FocalDiceLoss(alpha=1, gamma=2, dice_weight=0.7) # Dice 主导,更关注道路连通性参数说明:
gamma=2:抑制易分类样本(大面积背景)梯度,聚焦难样本(道路边缘、小路口);dice_weight=0.7:Dice Loss 对连通性更敏感,提升道路拓扑完整性;smooth=1e-5:防除零,必须设,否则训练初期 loss=nan。
4.3 解码器后处理:CRF 优化边缘 + Skeleton Pruning 修复断裂
U-Net 输出概率图需后处理才能生成可用道路中心线。两步法:
- DenseCRF 优化边缘(CPU 可行,无需 GPU):
import pydensecrf.densecrf as dcrf from pydensecrf.utils import unary_from_softmax, create_pairwise_bilateral def crf_refine(probs, image): # probs: (1, H, W) float32, image: (C, H, W) uint8 H, W = probs.shape[1:] d = dcrf.DenseCRF2D(W, H, 2) # Unary potential(logits → softmax → unary) probs = np.clip(probs, 1e-8, 1-1e-8) U = np.stack([1-probs[0], probs[0]], axis=0) U = np.log(U) d.setUnaryEnergy(U.flatten()) # Pairwise potential(基于 RGB 图像构建) pairwise_energy = create_pairwise_bilateral( sdims=(80, 80), schan=(13, 13, 13), img=image.transpose(1,2,0) ) d.addPairwiseEnergy(pairwise_energy) Q = d.inference(5) # 5 iterations return np.array(Q).reshape(2, H, W)[1] # road class # 使用 refined_mask = crf_refine(model_output.cpu().numpy(), image_uint8)- Skeleton Pruning 修复断裂(OpenCV + scikit-image):
from skimage.morphology import skeletonize, remove_small_holes, binary_dilation from skimage.measure import label, regionprops def fix_road_breaks(mask, min_length=50): # 1. 骨架化 skeleton = skeletonize(mask > 0.5) # 2. 移除短分支(<50像素) labeled = label(skeleton) for region in regionprops(labeled): if region.area < min_length: skeleton[region.coords[:,0], region.coords[:,1]] = 0 # 3. 膨胀恢复道路宽度(3×3 结构元) struct = np.ones((3,3), dtype=bool) return binary_dilation(skeleton, struct) final_road = fix_road_breaks(refined_mask)提示:CRF 参数
sdims=(80,80)针对 1024×1024 输入调优,若用 512 输入,需缩至(40,40);schan值越大,颜色差异惩罚越重,道路边缘越锐利。
5. 验证与评估:用矢量化指标替代像素精度,直击业务需求
遥感道路分割的终极目标不是高 IoU,而是生成可导入 GIS 系统、能用于路径规划的拓扑正确道路中心线。因此,评估必须超越pixel accuracy和IoU。
5.1 矢量化评估流程:从预测掩膜到 Shapefile 的完整链路
使用rasterio+shapely+geopandas将预测结果转为矢量,并与真值矢量对比:
import geopandas as gpd from shapely.geometry import Polygon, LineString, MultiLineString from rasterio.features import shapes import numpy as np def mask_to_vector(mask, transform, crs, simplify_tolerance=1.0): """将二值掩膜转为道路中心线矢量(LineString)""" # 1. 提取掩膜连通区域 mask_int = (mask > 0.5).astype(np.uint8) results = list(shapes(mask_int, mask=mask_int, transform=transform)) # 2. 转为多边形,再骨架化为线 lines = [] for geom, val in results: if val == 0: continue poly = Polygon(geom['coordinates'][0]) # 简化多边形(降噪) poly_simp = poly.simplify(simplify_tolerance) # 中心线提取(需安装 centerline 库) try: from centerline import Centerline center = Centerline(poly_simp, 5) # 5米间隔采样 if isinstance(center, LineString): lines.append(center) elif isinstance(center, MultiLineString): lines.extend(list(center.geoms)) except ImportError: # 降级:用最小外接矩形中轴线 bounds = poly_simp.bounds lines.append(LineString([(bounds[0], bounds[1]), (bounds[2], bounds[3])])) # 3. 构建 GeoDataFrame gdf = gpd.GeoDataFrame({'geometry': lines}, crs=crs) return gdf # 生成预测矢量 pred_gdf = mask_to_vector(pred_mask, transform=test_transform, crs=test_crs) # 读取真值矢量(.shp 文件) gt_gdf = gpd.read_file("test/ground_truth.shp") # 计算 Hausdorff 距离(衡量中心线匹配度) from shapely.ops import nearest_points def hausdorff_dist(gdf_a, gdf_b): max_dist = 0 for line_a in gdf_a.geometry: for line_b in gdf_b.geometry: dist = line_a.hausdorff_distance(line_b) max_dist = max(max_dist, dist) return max_dist hd_dist = hausdorff_dist(pred_gdf, gt_gdf) # 单位:米 print(f"Hausdorff Distance: {hd_dist:.2f} m")关键指标解释:
Hausdorff Distance:反映最坏匹配误差,<5m 表示道路走向基本一致;Centerline Length Error:预测总长度 / 真值总长度,理想值 ≈1.0,>1.2 表示过度分叉;Intersection Count:预测道路与真值道路交点数,应 ≥ 真值交点数(反映路口完整性)。
5.2 在 ArcGIS Pro 中快速验证:一键加载预测结果
将pred_gdf保存为 Shapefile,即可在 ArcGIS Pro 中叠加验证:
pred_gdf.to_file("test/pred_road.shp", driver="ESRI Shapefile")操作步骤:
- ArcGIS Pro → 新建地图 → 添加底图(如 Imagery);
- “内容”面板 → 右键 → “添加数据” → 选择
pred_road.shp; - 右键图层 → “属性” → “符号系统” → 设置线宽 2pt、颜色红色;
- 叠加真值图层(蓝色),目视检查:
- 红线是否覆盖蓝线主干(覆盖率);
- 红线是否在蓝线两侧摆动(偏移量);
- 红线是否在蓝线断点处自行延伸(过拟合)。
注意:ArcGIS Pro 中显示的坐标系必须与
pred_gdf.crs一致,否则位置错乱。可在gdf.to_file()前执行pred_gdf = pred_gdf.to_crs(epsg=4326)强制统一。
5.3 一个实用技巧:用混淆矩阵定位典型漏检场景
不是所有漏检都一样。通过混淆矩阵热力图,可定位模型薄弱环节:
from sklearn.metrics import confusion_matrix import seaborn as sns # 将预测与真值掩膜展平为一维数组 y_true = gt_mask.flatten() y_pred = (pred_mask > 0.5).flatten() cm = confusion_matrix(y_true, y_pred, labels=[0,1]) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['Background', 'Road'], yticklabels=['Background', 'Road']) plt.title("Confusion Matrix (Road Segmentation)") plt.ylabel("True Label") plt.xlabel("Predicted Label") plt.show()解读重点:
cm[1,0](漏检数)高 → 检查小路口、阴影下道路、窄巷;cm[0,1](误检数)高 → 检查农田田埂、河流、铁路(光谱相似);- 若
cm[1,0]集中在影像边缘 → 滑动窗口重叠不足,需增大stride。
最终,当你的预测道路矢量在 ArcGIS Pro 中与真值叠加误差 <3 米、Hausdorff 距离 <4.5 米、中心线长度误差在 ±8% 内,即可认为该大分辨率遥感影像道路图像分割数据集已具备工程交付能力。
本文还有配套的精品资源,点击获取