☰
遥感影像道路分割数据集处理:切片、划分与训练避坑指南
2026/9/28 5:37:53 网站建设 项目流程

简介:遥感影像道路分割数据集,基于DeepGlobe Road Dataset整理,已划分好训练集与测试集,适合深度学习图像分割方向的算法验证、模型调参与基准测试。训练集含4981张图片及对应mask,测试集含1245张图片及对应mask,前景像素标注质量较高,可直接用于分割网络训练与评估,也能作为大分辨率遥感场景下的泛化能力测试数据。包体共2000个文件,以jpeg格式保存原始影像与标签图,目录按images和masks分列,结构清晰;另附一个Python可视化脚本,可随机抽取样本并叠加展示原图、GT与蒙版效果,便于快速检查标注质量。资源整体约336.79MB,7z压缩包形式。目前已有977人学习使用。对于需要高分辨率遥感道路数据、希望省去数据收集与划分工作的研究者和开发者,这份资源能提供规范的数据组织方式和现成的可视化工具,显著缩短数据准备周期,帮助快速开展分割实验与效果对比。

1. 大分辨率遥感影像道路分割:数据集的用法决定模型上限

拿到一份已经划分好训练集和测试集的遥感道路图像分割数据集,不代表可以直接开训。大分辨率遥感影像与自然图像数据集有本质差异:一幅图动辄上万像素,道路在整幅图中往往只占不到 10% 的像素;训练集和测试集如果不是按场景划分而是按切片随机切,最后得出的指标基本是自欺欺人。很多人把模型效果差归咎于网络结构,真正卡住进度的往往是数据加载、切片策略、划分逻辑和评估方式这几件事。下面这些内容会围绕这套数据集的读取、切片、划分、训练和评估走一遍完整流程,适合正在做遥感图像分割,或者刚拿到这类数据集准备入门的从业者。

2. 拆解数据集的底层构成:格式、标签语义与目录组织

先说结论:拿到数据集的第一天不要急着写模型,先花半天把数据读进内存、可视化几张图、统计标签分布。这个习惯能帮你省掉后面几周的调试时间。遥感影像数据集的坑,有一大半在读图和标签阶段就埋下了,等训练跑起来再回头查格式问题,代价要高得多。

2.1 文件格式与读取工具:先分清 TIFF、PNG 和位深

遥感影像的常见格式是 GeoTIFF,也就是带地理坐标的 TIFF。单幅影像可能是几千乘几千像素,3 个波段或 4 个波段(RGB 加近红外)。普通图像分割数据集里常见的是 PNG 和 JPG,而遥感道路数据集大多用 TIFF 存原图、PNG 存标签,因为 PNG 是无损压缩,标签经不起 JPEG 那种有损压缩造成的边缘污染。

import rasterio from PIL import Image import numpy as np image_path = "dataset/train/images/scene_001.tif" mask_path = "dataset/train/masks/scene_001.png" with rasterio.open(image_path) as src: img = src.read() # 形状 (C, H, W) print("波段数:", src.count, "尺寸:", src.height, "x", src.width) print("数据类型:", src.dtypes, "CRS:", src.crs) mask = np.array(Image.open(mask_path)) print("标签值域:", np.unique(mask))

这里用 rasterio 读 GeoTIFF 是标准做法,它对大图支持窗口读取,不会一次性把整幅载入内存,这也是大分辨率影像和普通图像在工程实现上最大的区别。print 的 src.dtypes 会暴露常见的第一个坑:很多遥感原图是 uint16 存储,按 uint8 直接显示会变成一片黑。标签用 PIL 读没问题,但遇到 16bit PNG 时返回的 dtype 是 uint16 而不是 uint8,后续计算要统一转换。

参数说明:src.count 代表波段数,3 就按 RGB 处理;如果是 4,近红外波段对道路分割很有价值,因为道路和裸土在近红外波段上差异更明显,但网络输入通道要相应改成 4。src.crs 存在说明影像有地理坐标,后续按区域划分训练集和测试集时可以用上。

2.2 标签语义:0、1、255 分别是什么

道路分割数据集的标签语义看似简单,不同发布者的定义不完全一样。最常见的是 0 背景、1 道路;有些数据集在道路边缘标注了不确定区域,用 255 表示忽略;极少数会把背景写 255、道路写 0,或者背景 0、道路 255。

import numpy as np mask = np.array(Image.open("dataset/train/masks/scene_001.png")) unique, counts = np.unique(mask, return_counts=True) for v, c in zip(unique, counts): print(f"像素值 {v}: 占比 {c / mask.size * 100:.2f}%")

这一步必须做。如果发现 255,不要强行归成背景或道路,正确做法是在损失函数里设 ignore_index=255,让这些像素不参与梯度计算。我曾见过有人把所有非 0 像素都当成道路,结果模型在边缘不确定区域反复震荡,训练 loss 一直下不去。道路标注的宽度通常只有几个到十几个像素,边缘稍微错位几个像素,IoU 就会掉好几个点,所以边缘语义必须较真。

补充一点:这类数据集很多是从公开渠道下载的原始影像自己标注或半自动生成的,不同来源的标签规范差异很大。拿到手先统计值域,再决定归一化方式,比对着 README 猜要可靠。

2.3 目录组织与命名规则:先画一张数据地图

常见组织方式是 train/images、train/masks、test/images、test/masks,文件名一一对应。但大分辨率数据集有时会在 images 下按场景分子目录,mask 放在另一个平铺目录里,比如 train/images/scene_001_00.tif 对应 train/masks/scene_001_00.png。先用脚本把全部文件名拉出来核对一遍。

from pathlib import Path root = Path("dataset") for split in ["train", "test"]: img_files = sorted((root / split / "images").glob("*.tif")) mask_files = sorted((root / split / "masks").glob("*.png")) img_stems = {f.stem for f in img_files} mask_stems = {f.stem for f in mask_files} print(split, "影像数:", len(img_files), "标签数:", len(mask_files)) print("缺失标签的影像:", img_stems - mask_stems) print("多余标签:", mask_stems - img_stems)

这一步会暴露两类问题:一是文件名不匹配,二是部分影像没有对应标签。出现不匹配时先核对是不是子目录结构造成的 stem 不一致,比如影像叫 scene_001_00.tif、标签叫 scene001_00.png,看起来是同一个场景却不是同一个名字。遇到这种情况直接重命名对齐,别等 DataLoader 运行到一半才报 FileNotFoundError。

目录核对完之后,再统计一遍所有影像的尺寸分布。如果训练集里混着 1024×1024 和 4096×4096 两种尺寸,后面切片步长、归一化参数都要跟着调整;最怕的是测试集尺寸和训练集不一致,预处理逻辑会在评估时静默出错。

from collections import Counter size_counter = Counter() for split in ["train", "test"]: for img_file in (root / split / "images").glob("*.tif"): with rasterio.open(img_file) as src: size_counter[(src.height, src.width)] += 1 for size, cnt in size_counter.most_common(5): print(size, cnt)

2.4 可视化验证:原图与标签叠加,一眼看出坐标是否对齐

格式、命名、值域都检查完之后,还需要做一次可视化确认。把原图、标签、叠加图并排打印出来,这一步能同时发现位深异常、标签值异义和坐标偏移三类问题。

import matplotlib.pyplot as plt import numpy as np from PIL import Image import rasterio def visualize_pair(image_path, mask_path): with rasterio.open(image_path) as src: img = src.read([1, 2, 3]).transpose(1, 2, 0) if img.dtype == np.uint16: img = (img / 257).astype(np.uint8) # 16bit 转 8bit mask = np.array(Image.open(mask_path)) if set(np.unique(mask)) == {0, 255}: mask = (mask == 255).astype(np.uint8) plt.figure(figsize=(14, 4)) plt.subplot(1, 3, 1) plt.imshow(img) plt.title("image") plt.subplot(1, 3, 2) plt.imshow(mask, cmap="gray") plt.title("mask") plt.subplot(1, 3, 3) plt.imshow(img) plt.imshow(mask, cmap="Reds", alpha=0.4) plt.title("overlay") plt.show()

叠加之后如果道路没有沿着像素边缘对应,而是整体偏移了几个像素甚至几十个像素,说明影像和 mask 之间存在坐标偏移。这在人工标注的数据集里并不少见,必须在预处理阶段校正,不能拖到训练时让网络自己学。

3. 把大图切成训练样本:滑窗切片与数据增强的落地代码

遥感影像的单幅图太大,没法像自然图像那样整张喂进网络。公开数据集里常见的做法是先把大图切成固定大小的切片,再进入训练流程。切片这一步做得好不好,直接影响模型能不能看到完整的道路结构和上下文。

3.1 滑窗切片:窗口大小与步长的选择逻辑

窗口大小最常见的是 512×512 和 256×256。512 更适合 U-Net、DeepLabV3 这类常见分割网络的输入尺寸,显存占用和感受野比较均衡;256 对细窄道路更友好,因为道路在窗口里的像素占比更高,但窗口之外的上下文信息丢失较多。道路是细长结构,横跨窗口边界是常态,我一般用 512 做主训练尺寸。

步长决定了切片总数和数据冗余。stride 等于窗口大小代表无重叠,数据量最小;stride 取窗口一半(比如 512 窗口配 256 步长)代表 50% 重叠,切片数量约为无重叠的 4 倍。遥感道路分割场景下我通常选 50% 重叠,因为道路跨越窗口边界时,无重叠切片会直接把路切断,模型看到的都是半截路,推理时边界断裂问题会非常明显。

窗口大小步长重叠率4096×4096 影像切片数适用场景
5125120%64快速试跑、资源紧张
51225650%225推荐,兼顾效果与数据量
51212875%841追求边界连续性,时间充裕

除了窗口和步长,还有一个必须处理的细节:遥感影像四周经常有黑色边框、云层遮挡或纯背景区域。这些区域切成块后没有道路信息,大量喂给网络会让模型偏向预测背景,拖慢收敛。3.2 节的脚本会一并处理。

3.2 影像与标签同步裁剪:一段可以直接用的切片脚本

切片脚本的核心要求有两个:影像和标签必须按同一个窗口裁剪,不能各切各的;大图不能整体读进内存,要用窗口读取。

import rasterio from rasterio.windows import Window import numpy as np import cv2 from pathlib import Path def crop_scene(image_path, mask_path, out_root, win_size=512, stride=256): """把一幅大图和对应标签切成带重叠的小块。 win_size: 切片边长,遥感分割常用 512 stride: 滑窗步长,256 表示 50% 重叠 """ out_root = Path(out_root) (out_root / "images").mkdir(parents=True, exist_ok=True) (out_root / "masks").mkdir(parents=True, exist_ok=True) name = Path(image_path).stem count = 0 with rasterio.open(image_path) as src_img, rasterio.open(mask_path) as src_mask: h, w = src_img.height, src_img.width for top in range(0, h - win_size + 1, stride): for left in range(0, w - win_size + 1, stride): window = Window(left, top, win_size, win_size) img = src_img.read(window=window) # (C, H, W) msk = src_mask.read(window=window) # (1, H, W) img = np.transpose(img, (1, 2, 0)) # 转 HWC msk = np.squeeze(msk, axis=0) # 转 HW # 过滤纯背景块:没有道路像素的块没有训练价值 if msk.max() == 0: continue # 过滤黑边块:无效值占比过高会扰乱归一化 if np.mean(np.all(img == 0, axis=-1)) > 0.3: continue if img.dtype == np.uint16: img = (img / 257).astype(np.uint8) cv2.imwrite(str(out_root / "images" / f"{name}_{count:05d}.png"), img) cv2.imwrite(str(out_root / "masks" / f"{name}_{count:05d}.png"), msk) count += 1 print(f"{name}: 生成 {count} 个切片") return count

代码逻辑说明:rasterio 的 Window(left, top, win_size, win_size) 参数是列号、行号,第一个参数是列起点,第二个是行起点,和 OpenCV 里先 x 后 y 的习惯是反的,写错了就直接切错位置。read 出来的数组形状是 (C, H, W),先转成 HWC 才能让 cv2.imwrite 直接写。两个过滤条件分别针对纯背景块和黑色无效区域,具体阈值可以按数据集的实际情况微调。

参数说明:win_size=512 在 8GB 显存的卡上配 U-Net 能跑 batch_size=4 左右,win_size=256 可以翻倍,但窗口内道路上下文变少。stride=256 让切片数量约为无重叠的 4 倍,如果训练时间吃紧可以调成 384。16bit 转 8bit 用 257 而不是 256,是因为 65535 / 257 = 255,这样能保留完整的亮度范围,直接除以 256 整体会偏暗。

还有一个小提醒:保存的 mask 如果值是 0 和 1,用看图软件打开会显示接近全黑,这是正常的,不代表数据丢了。要人工检查时先乘 255 再显示。

3.3 数据增强的边界:哪些操作对遥感影像安全

遥感道路分割的数据增强比自然图像要保守。安全的是几何类:左右翻转、上下翻转、旋转 90 度、180 度、270 度。道路没有固定的"上下"方向,这些变换不会改变语义。

要小心的是亮度、对比度、色彩抖动。同一场景不同时相的光照差异真实存在,做适量扰动确实能增强泛化,但扰动过强会把沥青路面的灰色调偏到植被的绿色上,模型会把路认成树。不建议做的是弹性形变和随机裁切。弹性形变会扭曲道路形状,让细长道路被打断;随机裁切等价于更小的窗口,把本来就只有几个像素宽的道路切得更碎,这里我吃过亏。

import random import numpy as np def sync_augment(img, msk): """图像与标签用同一个随机状态做几何变换,保证像素级对齐。""" if random.random() < 0.5: img = np.fliplr(img) msk = np.fliplr(msk) if random.random() < 0.5: img = np.flipud(img) msk = np.flipud(msk) k = random.choice([0, 1, 2, 3]) img = np.rot90(img, k) msk = np.rot90(msk, k) return img, msk

这段代码最容易翻车的点是变换不同步。如果分别用 torchvision 的 RandomHorizontalFlip 处理 image 和 mask,两次随机调用不一定得到同一个随机结果,图像翻转了标签没翻转,训练时 loss 会一直震荡。把两个数组放进同一个 if 分支,用同一个随机状态做变换,才能保证对齐。

4. 训练集与测试集划分的正确姿势:避免同源数据泄漏

标题里强调"划分了训练集和测试集",这正是遥感数据集和普通数据集最不一样的地方。划分的粒度错了,后面的评估全部失真。

4.1 随机划分在大分辨率遥感影像上为什么会翻车

很多入门教程教的是把所有图片路径放进一个列表,然后 train_test_split 按比例随机切。这个做法在自然图像数据集上凑合能用,在大分辨率遥感影像上是灾难。

遥感影像的单幅图往往是一个场景,一景卫星影像覆盖几平方公里。滑窗切片之后,同一场景切出的几百个切片在光照条件、传感器型号、地物类型上高度相关。如果随机把这些切片分到训练和测试,测试切片等于在和它高度相似的训练切片"裸考"。模型并没有见过新的地理环境,指标却虚高,换个城市立刻崩。

正确做法是场景级划分:把一张完整大图整体划入训练集或测试集,无论它之后会被切成多少块。如果一张图地理跨度极大,比如同时包含城区、郊区、农田,最好先按地理瓦片拆开再划分,确保同一瓦片下的切片不会跨集合。

4.2 按场景划分训练集和测试集的脚本

我一般拿到原始大图后,先按场景统计信息,再做场景级划分。

import random from pathlib import Path import json rng = random.Random(42) raw_images = sorted(Path("raw/images").glob("*.tif")) scene_names = [p.stem for p in raw_images] # 场景级随机划分:完整一张图只进一个集合 rng.shuffle(scene_names) test_ratio = 0.2 test_count = int(len(scene_names) * test_ratio) test_scenes = set(scene_names[:test_count]) train_scenes = set(scene_names[test_count:]) with open("split.json", "w") as f: json.dump({ "train": sorted(train_scenes), "test": sorted(test_scenes) }, f, indent=2)

这里的核心是 shuffle 发生在 scene_names 上,而不是切片文件列表上。如果数据集作者已经切好片,文件名保留了场景前缀(比如 scene_001_0000.png),就先按前缀聚合,再对聚合后的场景名做划分。如果文件名前缀被抹掉,只能靠文件名特征或影像特征去反推场景归属,工作量会成倍增加,这也是我反复强调先检查命名规则的原因。

如果影像带地理坐标,可以更进一步:把场景的经纬度范围画出来,把空间相邻的场景分到同一侧,避免地理位置泄漏。实际操作可以按经纬度网格分桶,再把桶划分到训练和测试。这个在大规模遥感工程里属于常规操作。

4.3 划分后必须做的独立性与分布一致性检查

当数据集作者已经划分好训练集和测试集时,我不直接信任,先跑一遍验证,重点看四件事:

  1. 文件名是否有交叉。
  2. 训练集和测试集的影像尺寸分布是否一致。
  3. 两边的道路像素占比是否接近。
  4. 把训练和测试的缩略图拼在一起肉眼对比,确认没有同一区域重复出现。
import numpy as np from PIL import Image from pathlib import Path def road_ratio(mask_path): m = np.array(Image.open(mask_path)) return float((m == 1).sum() / m.size) for split in ["train", "test"]: ratios = [road_ratio(p) for p in (Path("dataset") / split / "masks").glob("*.png")] print(split, "道路占比均值: {:.2f}%, 最小: {:.2f}%, 最大: {:.2f}%".format( np.mean(ratios) * 100, np.min(ratios) * 100, np.max(ratios) * 100))

road_ratio 函数假设标签是 0/1,如果数据集用的是 0/255,先归一化再统计。这个统计非常关键:如果训练集道路占比 5%、测试集 15%,模型在测试集上指标一定会偏低,因为训练时偏向少预测道路,而测试集道路又多,精召之间怎么调都不对。这种情况要在切片划分时做分层抽样,按道路占比分布去切。

检查文件级泄漏的脚本更简单:

train_masks = set(p.stem for p in (Path("dataset") / "train" / "masks").glob("*.png")) test_masks = set(p.stem for p in (Path("dataset") / "test" / "masks").glob("*.png")) overlap = train_masks & test_masks print("同名文件重叠数:", len(overlap)) train_prefixes = {s.rsplit("_", 1)[0] for s in train_masks} test_prefixes = {s.rsplit("_", 1)[0] for s in test_masks} print("跨集合场景数:", len(train_prefixes & test_prefixes))

同名文件重叠是最简单的泄漏,但更隐蔽的是不同名的相邻切片跨集合。比较文件名前缀就能发现大部分问题。如果前缀也大量重合,说明数据集作者做的确实是切片级随机划分,这份数据集的评测结论在严格意义上不可信。

5. 避坑:大分辨率遥感道路分割数据集使用中的常见问题排查

这一节列的是我在处理大分辨率遥感道路数据时踩过、或帮别人排查过的高频问题。每一条都按现象、原因、解决三段来写,可以直接对照排查。

5.1 训练 Loss 正常下降,验证指标却剧烈震荡

现象:训练 loss 平滑下降,验证 IoU 每个 epoch 上下跳 10 个百分点以上。

原因:最常见的是验证集切片的空间相关性太强。验证集的几百张切片全部来自同一幅大图,且恰好包含一个密集路网区域。loss 统计的是像素级平均,IoU 是按类别统计,两者对样本分布的敏感度完全不同。另一个常见原因是验证集本身太小,几十张困难切片就会把指标拉低。

解决:验证集至少准备几百张切片;按场景采样,不要按切片采样;固定随机种子,让每个 epoch 评估同一组数据。如果验证集足够大但指标仍然震荡,去检查验证集和训练集的道路占比分布差异,大概率是两边差异太大。

5.2 GPU 显存不足,Batch Size 调到 1 还是爆

现象:把 batch_size 设成 1,关闭了所有中间缓存,训练时仍然 OOM。

原因:显存爆掉的常见原因不是 batch_size 太大,而是输入尺寸太大。把整幅大图直接喂进网络是典型错误。另外如果模型带 BatchNorm,batch_size=1 时 BN 的统计量非常不稳定,可能导致损失震荡甚至不收敛。

解决:确认 DataLoader 输出的是切片而不是整图。512×512 的切片在常见 U-Net 上 batch_size=2 到 4 可以跑;想跑更大 batch 就用梯度累积。batch 特别小时,把 BN 换成 GroupNorm 或 InstanceNorm,遥感道路分割对小 batch 并不敏感,小 batch 下 GroupNorm 明显更稳定。显存优化这一项,做语义分割和做图像分类的侧重点完全不同,不能照搬。

5.3 预测结果里道路边缘呈锯齿状、有拼接缝

现象:测试时用滑窗逐块预测,再把结果拼回大图,拼接处道路断裂,出现一条条明显的方块接缝。

原因:预测时步长等于窗口大小,相邻窗口没有重叠,窗口边缘的信息被模型忽略了;拼接时如果直接取 argmax,两个窗口对边界像素的预测不一致,就会产生接缝。

解决:预测时把步长减小到窗口的一半甚至四分之一,重叠区域对每个像素的概率取平均再取 argmax。训练时也使用重叠切片,让模型适应"道路被窗口截断"的情况,推理时边界连续性会好很多。如果训练时无重叠切片,推理时突然加重叠,效果提升有限,这点要注意。

5.4 测试集 IoU 很高,但目视检查漏检严重

现象:测试集 IoU 达到 0.8 以上,把整幅预测图叠到原图上,发现一条主干道漏掉了一半,窄一点的巷路几乎全断。

原因:IoU 对类别不均衡非常不敏感。背景占 95%、道路占 5% 时,模型只要少预测道路、做保守预测,IoU 不会掉太多。细窄道路的线性退化对像素级 IoU 影响也不大,所以像素指标高不代表道路拓扑完整。

解决:评估时分开看 Precision、Recall 和 F1,特别是道路类的 Recall;把预测结果按道路连通域拆分,统计断裂段数量。如果测试集中的道路明显比训练集细或宽,先查数据增强里有没有尺度扰动,遥感影像分辨率不一致时这个问题很常见。

5.5 标签图读出来一片黑或一片白

现象:训练加载时打印标签值全是 0 或全是 255,可视化出来不是纯黑就是纯白。

原因:像素值 0/255 可能表示的是未标注区域而不是背景;或者标签是 16bit PNG,PIL 读取后是 uint16,没归一化到 uint8 显示就会异常。还有一种情况是 mask 保存成了 JPEG,有损压缩后道路边缘已经糊掉,读出来的值域也乱了。

解决:读标签后先 np.unique 统计值域,把标签规范化到 0/1,有未标注区域则保留 255 并设 ignore_index。确认 PNG 位深后统一转 uint8。这类转换放在预处理脚本里做,不要每次训练循环里重复做,否则 IO 开销会拖慢整体速度,这是最容易被忽视的性能瓶颈。

6. 基线验证与进阶:用滑窗重叠预测和连通域评估确认数据集可用性

6.1 滑窗重叠预测:把大图拼回来还能保持道路连续

拿到划分好的数据集,第一轮实验不是追 SOTA,而是用一个小网络快速跑一个基线,把评估链路打通。我建议用 U-Net 这类常见的卷积模型,输入 512×512 切片,先训练约 100 个 epoch,然后做整幅预测。

推理时的滑窗和训练时的切片不同:训练时切片是独立样本,推理时要把所有切片拼成一幅完整大图。直接拼接会在窗口边界出现接缝,解决办法是让相邻窗口重叠,把重叠区域的预测概率取平均。

import numpy as np def predict_full_image(model, image, win_size=512, stride=128): """重叠滑窗推理,返回整幅图的道路概率图。""" h, w = image.shape[:2] prob_sum = np.zeros((h, w), dtype=np.float32) weight = np.zeros((h, w), dtype=np.float32) for top in range(0, h - win_size + 1, stride): for left in range(0, w - win_size + 1, stride): crop = image[top:top + win_size, left:left + win_size] prob = model.predict(crop) # 你的模型推理函数,返回 (H, W) 概率 prob_sum[top:top + win_size, left:left + win_size] += prob weight[top:top + win_size, left:left + win_size] += 1 prob_map = prob_sum / np.maximum(weight, 1) return prob_map

参数说明:stride=128 表示重叠 75%,推理时间约为无重叠的 16 倍。实际使用根据时间预算放在 128 到 256 之间,128 的平滑效果已经足够好。weight 记录每个像素被预测的次数,用它做平均;加 1 的保护是为了防止大图边缘出现未覆盖区域。真正用 PyTorch 实现时记得 model.eval() 和 torch.no_grad(),把 crop 转成 tensor 放到对应设备。

6.2 指标怎么读:IoU 不是终点,道路拓扑完整性才是

对道路分割来说,IoU、F1 之外还要看拓扑。一条路被截成三段,IoU 可能只下降零点几个点,但对下游应用是致命问题。进阶做法是训练后对预测图做连通域分析:

from scipy import ndimage import numpy as np def count_road_breaks(pred_mask, min_length=10): """统计预测道路的连通段数,段数越多说明断裂越严重。""" labeled, n_component = ndimage.label(pred_mask) sizes = ndimage.sum(pred_mask, labeled, range(1, n_component + 1)) long_segments = (sizes > min_length).sum() return n_component, long_segments

n_component 越大,道路碎片化越严重;long_segments 是像素数超过阈值的连通段数。一个理想结果里,n_component 应该接近真实道路拓扑中独立道路的数量。如果想更严格,可以先对预测图做骨架化提取道路中心线,再统计断裂次数,这是工程上更接近真实需求的指标。

这个指标的用途有两个:一是对比不同实验,判断模型是在变细还是变断;二是作为训练监控,如果 IoU 在涨但 long_segments 在涨,说明模型在走保守路线,需要调整损失权重或增强策略。类的权重我一般按道路像素占比来定,道路占比 5% 时,把 Dice Loss 和交叉熵按 0.3 比 0.7 混合,比单纯用加权交叉熵更稳。

我已经养成一个习惯:每次拿到新数据集,第一周不写模型,先把读图、切片、划分、评估四件事跑通并写进项目文档。数据链路是通的,后面换模型、调参都是在同一个可信地基上迭代;数据链路没打通,训练脚本再漂亮也只是在黑盒上撞运气。希望这套思路能帮你在遥感道路分割这个方向少踩几个坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询