ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

高分辨率无人机图像分割数据集实战与避坑指南

高分辨率无人机图像分割数据集实战与避坑指南 简介面向语义分割训练与验证的高分辨率无人机城市图像数据集包含约270张原始影像及对应的像素级标签图标注覆盖建筑、公路等8类典型地物适用于城市遥感场景理解与地物提取。数据已划分为训练集和验证集其中训练集约200张、验证集约70张每部分均提供原图目录与掩膜标签目录可直接用于U-Net等主流分割模型的训练、验证与精度对比。压缩包共543个文件绝大多数为PNG格式的原始图片和标签图片另附1个类别说明文本文档和1个Python可视化脚本整体约263.73MB。附带脚本可随机抽取样本将原始影像、真实标签图以及标签叠加蒙版一并保存到当前目录便于快速核查标注质量并直观观察预测效果。已有387人学习适合计算机视觉初学者与遥感、无人机方向的算法工程师快速上手实践。1. 高分辨率无人机图像分割270张图与8类标签的真实使用门槛无人机航拍图像的语义分割和普通街景分割完全是两码事。高分辨率带来的直接后果就是目标尺度差异极大一辆车在4000×3000的原图里可能只占几十个像素而一片建筑群却能铺满大半张图。这份资源给的正是这个场景下的真实数据8个类别约270张图像加对应的mask标签分成训练集和验证集另附一个可视化脚本能直接把原图、GT和叠加蒙版图三张图并排输出方便第一时间确认标签质量。适合正在做遥感图像语义分割、用UNet或SwinUNet跑实验、以及需要一份能快速验证分割流程的数据集的人。它的价值不在数据量大而在“高分辨率无人机视角”这个标签很多通用数据集学不到的尺度问题在这里一跑就暴露。2. 数据集结构与类别映射train/val目录、classes.txt与mask格式2.1 目录结构与文件命名规律这份数据集的物理结构按常规分割项目组织训练集下分images和masks两个目录验证集同样如此。images里放原始航拍图masks里放对应的标签图文件名一一对应比如seq3_000200.png对应seq3_000200_mask.pngmask目录内可能有单独的mask后缀具体看压缩包内实际文件。从文件名看seq前缀代表拍摄序列后面的数字是帧号所以同一seq前缀下的帧大概率来自同一条飞行航线这是一个后续处理时必须记住的信息后面训练集验证集划分时它会成为隐患。文件命名本身没有复杂的编码规则seq是航线ID数字是采集时间顺序。两个不同seq下的图片内容通常没有重叠但同一个seq下的相邻帧可能有极高的视觉相似度。处理时不要只看文件名长短先把seq前缀提取出来做个分组统计能避免不少后续麻烦。2.2 classes.txt与8类标签的真实顺序类别信息记录在classes.txt里8个类别大致包括建筑、公路、树木、车辆、草地等。注意一点classes.txt里每一行的顺序就是mask图像里像素值的顺序第一行对应像素值0第二行对应像素值1以此类推。如果你自己的训练代码是从0开始编号类别直接用即可如果框架要求背景类单独占一个ID就需要手动做一次映射。我这里建议你拿到数据后的第一件事不是训练而是先跑一段脚本统计mask里实际出现了哪些像素值和classes.txt的类别数对不对得上。常见做法是用PIL或OpenCV读一张mask用numpy.unique直接去看。这个检查成本很低但能挡住后续80%的“训练loss正常但mIoU几乎为0”的莫名其妙问题。import numpy as np from PIL import Image mask_path val/masks/seq3_000200.png mask np.array(Image.open(mask_path).convert(L)) vals, counts np.unique(mask, return_countsTrue) for v, c in zip(vals, counts): print(f像素值 {v}: {c} 像素)这段代码用灰度模式读入mask并统计每个像素值的出现次数。为什么要手动加convert(L)因为很多标签图虽然是PNG但可能以RGB格式存储直接np.array读出来是三维数组unique统计的结果会完全变样。加上convert(L)强制转单通道得到的就是真正的类别ID分布。如果发现像素值最大值是7且8个值均匀分布说明标签类和classes.txt一一对应如果只出现几个值说明某些类别在训练集里样本极少训练时需要特别注意类别不平衡。2.3 mask是单通道灰度图不是彩色图这是这个数据集最容易让人踩坑的地方。很多做自然图像分割的人习惯了三通道彩色标签但无人机遥感数据集的mask几乎都是单通道灰度图像素值直接代表类别ID。训练时网络输出是8通道的概率图和mask做交叉熵时mask必须保持单通道H, W不能是H, W, 3。如果你的数据加载器里用了cv2.imread读mask默认会得到三通道BGR这会直接导致维度不匹配跑起来就报错。解决办法是读mask时加参数或者像我上面那样用PIL转灰度。还有人会用cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)这也能得到单通道结果。关键是全家桶里所有用到mask的地方都要统一这个读法不要一张图用PIL一张用cv2后面颜色顺序不一致时排查起来很费时间。3. 先用自带可视化脚本跑通一张图脚本逻辑与输出说明3.1 脚本是怎么工作的数据集的压缩包里带了一个图像分割可视化脚本作用是随机抽取一张图把原始图片、GT掩码、GT叠加在原图上的蒙版图并排展示并保存到当前目录。这个脚本的价值不只是“看个效果”它是你验证数据加载器是否正确的最快路径如果脚本输出的三张图能准确对应说明数据文件的文件名匹配、路径结构、mask取值范围都没问题如果脚本输出错位或者蒙版是花的说明读取环节已经埋了雷。脚本核心逻辑大致是遍历images目录收集所有图片路径用random.choice选一张然后用对应的mask文件名拼接出完整路径读原图、读mask最后做一个半透明叠加把mask里非背景区域在原图上覆盖一层颜色。因为mask是单通道ID叠加时通常会把每个类别ID映射到一个彩色值再转换回RGB图。import random import glob import numpy as np import cv2 import os image_paths sorted(glob.glob(train/images/*.png)) chosen random.choice(image_paths) mask_path chosen.replace(/images/, /masks/).replace(.png, _mask.png) img cv2.imread(chosen) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) mask cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) color_map { 0: (0, 0, 0), # 背景 1: (255, 0, 0), # 建筑 2: (0, 255, 0), # 公路 3: (0, 0, 255), # 树木 4: (255, 255, 0), # 车辆 } overlay img.copy() for cid, color in color_map.items(): overlay[mask cid] color combined np.hstack([img, cv2.cvtColor(mask, cv2.COLOR_GRAY2RGB), overlay]) cv2.imwrite(visual_check.png, cv2.cvtColor(combined, cv2.COLOR_RGB2BGR)) print(f已保存 visual_check.png来源: {chosen})这段代码模仿的是自带脚本的核心逻辑。filename替换那行有个细节如果mask文件名不是简单地在原图名后面加_mask这行就会匹配失败所以第一遍跑的时候建议先打印一下拼接后的路径确认文件真实存在。color_map里的颜色映射只是示例实际颜色值以数据集自带脚本为准但映射方式就是“像素值 → RGB颜色”这样一一对应。3.2 可视化结果怎么看跑完脚本会生成一张横向拼接的图左边是原图中间是mask转成的RGB图右边是蒙版叠加图。重点看右边如果建筑的边缘和原图对得上、道路线条连续说明这个数据集的标注精度不错。如果看到某些区域mask是黑色但原图里明显是建筑要么是类别ID没映射完整要么是标注本身漏标了。这一个脚本的用途远不止“看一眼”后续你改数据增强、做归一化、调整输入尺寸都应该用这个脚本复查一遍输出确认改动没有破坏标签对齐。我一般会把脚本改造成可以指定文件名而不是随机抽取这样查具体某几张问题图时能精确复现。改动很小就是把random.choice换成从命令行参数读路径。另外要注意保存路径脚本默认存当前目录如果你在notebook里跑记得先os.chdir到数据集根目录否则保存位置不对会找不到输出文件。还有cv2.imwrite不能保存中文路径如果数据集解压到了一个带中文的目录下保存时直接报错这是OpenCV的老毛病把工作目录改纯英文即可。4. 常见问题与避坑拿到数据集先检查这4个地方4.1 classes.txt顺序与mask像素值错位现象拿到的数据说好是8类但训练时loss下降很快mIoU却上不去可视化叠加图里建筑物的颜色和GT完全对不上。原因类别顺序错位。比如classes.txt第一行是背景但mask里像素值0实际是建筑。你按0当背景去算负样本占了绝大多数模型很快就学会全部预测背景loss照降指标全崩。解决先跑一遍2.2里的统计脚本把mask里的像素值分布打出来然后逐行对照classes.txt。如果发现像素值0在mask里占比不是最高一般说明0不是背景或标注时另有约定。手动做一张映射表把mask里的像素值映射成训练框架从0开始的连续ID。不要嫌这一步麻烦数据集的类别顺序在文档里写得再清楚都不如自己跑一遍统计可靠。4.2 用cv2.imread读mask导致通道数不匹配现象DataLoader在训练时抛错说label的shape是(512, 512, 3)但期望的是(512, 512)或者是loss计算时报维度不一致。原因cv2.imread读PNG默认读成三通道BGR而mask实际是单通道灰度图两者混在一起送进网络交叉熵直接拒收。解决统一读mask的姿势推荐用cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)或者PIL的convert(L)。关键是所有读取入口都用同一套代码不要有的地方转灰度、有的地方不转。如果你用了albumentations做增强它要求mask是(H, W)单通道读进来之后顺手assert一下这个shape能挡住大部分问题。4.3 同一seq的帧被同时分进train和val造成验证集泄漏现象训练mIoU有80%以上验证集却一直上不去两者差距悬殊。原因这个数据集的图片按seq序列组织同一段航线拍出来的连续帧内容高度相似。如果划分时直接random split很可能某个seq的前几帧进了训练集后几帧进了验证集验证集里全是训练集见过的场景这叫数据泄漏。解决按seq前缀分组同一个seq的所有帧要么全进训练集要么全进验证集。做法是把文件名按seq字段分组然后以seq为单位做划分而不是以单张图为单位。如果原文已经按seq划分好了train和val目录你也应该先检查两个目录下有没有重复的seq前缀有的话说明划分有问题需要手工重排。4.4 高分辨率原图直接resize导致小目标消失现象训练时loss正常但预测结果里车辆和行人小目标几乎全部丢失大面积的建筑和道路分得还行。原因原图是几千像素的高分辨率图常见做法是直接resize到512×512或者640×640一辆在原图里占20×20像素的车resize之后只剩两三个像素基本被抹掉了。解决不要整图resize改成随机裁剪训练时从原图里crop一块固定尺寸的区域比如512×512配合随机翻转和色彩抖动。验证时用滑窗推理再拼回去这个方案在遥感分割里是通行做法。如果显存够大也可以尝试先把图缩小到1600左右再crop效果比直接512要好。这个数据集的价值恰恰在这高分辨率下的小目标是它的特色不要用resize把这部分信息浪费掉。5. 训练前的一轮数据体检类别平衡、标签质量与重复帧检查5.1 类别像素占比统计找出哪些类别是“稀有类”训练分割模型类别不平衡是个绕不开的坎。270张图里建筑和道路通常占据大部分像素车辆、行人和树木可能只占极小比例。如果你的loss函数用普通交叉熵网络会倾向于把所有像素预测成高频类别mIoU里稀有类得分惨淡。所以训练前先跑一次全数据集的像素统计看清每个类别大概占多少比例。import numpy as np from PIL import Image import glob mask_files glob.glob(train/masks/*.png) class_count {} for mf in mask_files: mask np.array(Image.open(mf).convert(L)) vals, counts np.unique(mask, return_countsTrue) for v, c in zip(vals, counts): class_count[v] class_count.get(v, 0) c total sum(class_count.values()) for cid in sorted(class_count.keys()): print(f类别 {cid}: {class_count[cid]} 像素, 占比 {class_count[cid] / total:.4f})这段脚本汇总所有训练mask的像素分布。如果发现类别0占90%以上说明背景占比过高建议训练时把背景类设成ignore_index不参与loss计算。如果某个类别占比低于1%这个类别基本属于难度极高的稀有类要单独处理。常见做法是对稀有类做重采样裁剪时强制让裁剪窗口覆盖到稀有类区域或者用带类别权重的Dice Loss替代普通交叉熵。5.2 标签质量检查mask边缘毛刺和漏标区域高分辨率无人机图像的标注最怕边缘羽化和漏标。mask里建筑边缘如果有一圈渐变而不是锐利边界训练出来的模型边缘预测也会发虚漏标则表现为原图里有明显的建筑但mask对应区域是背景。这两类问题靠肉眼很难全部揪出来但可以用一个间接指标——按类别统计连通域数量。import numpy as np from PIL import Image from scipy import ndimage image_paths sorted(glob.glob(train/masks/*.png)) for mf in image_paths[:5]: mask np.array(Image.open(mf).convert(L)) for cid in np.unique(mask): component_map, num ndimage.label((mask cid).astype(int)) print(f{mf} 类别 {cid}: {num} 个连通域)这里的逻辑是正常标注下一个建筑即使被树挡了一半也应该是少数几个大连通域。如果某个类别在一张图里出现几百个连通域而且都是几个像素的小碎块说明标签里有椒盐噪声或者漏标边界。这类噪声会让模型在推理时产生很多小面积误检视觉上就是“预测图很花”。发现这种情况建议对mask做一次形态学开闭运算去掉小连通域比如用cv2.morphologyEx但操作时要小心别把真的小目标车辆、行人一起抹掉。5.3 训练集与验证集的重复帧检查无人机数据集的另一个特有坑是帧与帧之间的视觉重叠。同一段航线无人机在悬停或缓慢前进时相邻帧有大量重叠区域如果这些帧跨在train和val两个集合里验证集的数字会虚假偏高。检验方法很简单把训练集和验证集的图像各取一个小尺寸缩略图算一下感知哈希或者直接用像素均方差做相似度比对。import cv2 import glob import numpy as np train_imgs sorted(glob.glob(train/images/*.png)) val_imgs sorted(glob.glob(val/images/*.png)) train_small [cv2.resize(cv2.imread(p), (64, 64)) for p in train_imgs[:30]] val_small [cv2.resize(cv2.imread(p), (64, 64)) for p in val_imgs[:30]] for ti in train_small: for vi in val_small: diff np.mean(np.abs(ti.astype(float) - vi.astype(float))) if diff 3.0: print(发现疑似重复帧MSE , diff)这段代码做了简化版查重用缩小图计算平均像素差阈值3.0是经验值光照一致的同一场景通常能到2以下。如果查出来train和val里有重复帧需要回到5.3的地图重新按seq切割。如果查不出来那说明这个数据集的划分质量不错可以用。注意这里只对比了前30张完整检查需要全量比对数据量大时可以先用感知哈希粗筛再精排。6. 进阶从训练参数到滑窗推理验证一个能直接抄的流程6.1 训练参数建议用这组数据跑UNet和SwinUNet时我建议输入尺寸用640×640或512×512的随机裁剪batch size设812G显存以内优化器用AdamW初始学习率1e-4配合Cosine Annealing。损失函数用CrossEntropy和Dice的加权组合权重建议0.6的Dice加0.4的CE对稀有类友好。主干用SwinUNet时输入可以直接用整图缩放到768×768因为Swin的窗口注意力对尺度没那么敏感但UNet建议老老实实crop。6.2 滑窗推理验证技巧训练完成后验证不要在验证集上整图resize后直接预测而是滑窗推理再拼接这样能看到模型在高分辨率下的真实表现。窗口选512步长设256重叠区域取平均概率。下面这个流程我每次换数据集都会强制走一遍先跑统计脚本确认类别ID再跑可视化脚本看标签对齐然后按seq检查train/val重叠这三步过了才允许自己碰训练代码。这套检查在别的无人机数据集上也救过我两次希望帮到你。本文还有配套的精品资源点击获取
返回列表