☰
高分辨率无人机城市图像语义分割:8类标注与270张图的训练实战
2026/10/5 14:02:37 网站建设 项目流程

简介:该数据集面向城市遥感与计算机视觉研究者,聚焦无人机视角下的高分辨率城市地物语义分割任务,提供8个类别的像素级标注,可用于训练与评估分割模型。包内共543个文件,以541张PNG格式的原始影像与对应掩膜为主,另含一个类别说明txt文件与一个Python可视化脚本,整体压缩后约263.73MB。数据按训练集与验证集划分,分别约200张和70张,便于直接开展模型训练与验证;附带脚本可随机抽取样本,将原始图、GT标签及叠加蒙版效果保存为图片,方便快速检查标注质量。目前已有387人学习下载,适合刚入门语义分割的学生或需要无人机城市数据集进行实验的研究者,也适合搭配博主专栏中的UNet、SwinUNet等网络实现对比测试。

1. 高分辨率无人机城市图像语义分割数据集:270张图能做什么?

如果项目组刚拿到一套高分辨率无人机城市图像语义分割数据集,第一反应通常是兴奋:8类像素级标签、原图和标注一一对应,拿起来就能开训。但我建议先冷静下来——大约270张图的规模恰好卡在“能跑通实验”和“能支撑落地”的中间地带。这类数据的典型构成是RGB无人机航拍影像加同尺寸PNG标签,类别包括建筑、道路、车辆、植被等常见城市地物,很适合用来做语义分割算法验证、数据增强对比和遥感图像语义分割入门。

适合谁用呢?如果你是研究生或算法工程师,手上正好有无人机采集的城市场景,需要快速验证一个分割模型能不能用、标注规范有没有问题,这套数据就是你调参前最好的“磨刀石”。它能帮你把数据读取、标签校验、训练闭环、指标评估这一整条链路走通,但别指望不加任何处理就直接得到一个能上线的分割服务。不过话说回来,正因为数量小,它反而逼着你把每一个细节都看清楚,而看清楚小数据集的每一个坑,比盲目堆大数据有用得多。

2. 拆解数据集的8类标注体系:类别定义决定模型上限

拿到数据集先别急着写训练脚本,我一般会花半天时间把标签体系彻底盘一遍。无人机俯拍视角和自然图像分割最大的区别在于,地物的尺度、遮挡和边界表现完全不同,类别定义稍有偏差,后面所有指标都会被带偏。

2.1 无人机俯拍视角下,8个类别怎么划分才合理

常见的8类划分是围绕城市地表覆盖展开的:建筑、道路、人行道、车辆、植被、裸土、水体、其他。这套划分的逻辑很直观,城市管理、违建识别、绿地占比统计这些需求都能覆盖。建筑在俯拍影像里通常是屋顶面,形态规整;道路是连续带状区域;车辆尺度小但分布密集;植被在不同季节颜色差异极大。类别定义颗粒度直接决定模型的表达上限,如果数据里把建筑的阴影区域标成“裸土”,模型就永远学不会区分阴影下的建筑边缘到底该归哪一类。

在检查标签的时候,我最关心的往往是边界类别的归属约定。比如道路和人行道之间以路缘石为界,还是以车道线为界;车辆被树冠部分遮挡时,露出半个车顶算车辆还是算植被;水体里的桥面要不要单独拆出来。这类边界情况在约270张图里不会有太多样本,但一旦标注不一致,模型会在这些位置输出大片的碎片化预测。另一个容易被忽视的点是“其他”类。如果这一类里混入了阴影、围挡、集装箱、施工机械等各种杂项,它就变成了一个没有语义内聚性的垃圾筐类别,训练时特别容易抢占其他类别的像素。

对高分辨率无人机图像来说,一个合理的类别定义还要考虑目标在地面的实际尺寸。无人机飞高一点,一辆轿车在图像里可能只有三四十个像素,这时候“车辆”单列一类问题不大;如果飞行高度很高,车辆连轮廓都看不清,就应该把它并入“其他”或者干脆不设这类。所以拿到这份8类分割数据后,第一件事是统计每张图中每个类别的像素占比,而不是先看某张图看起来有多清楚。

2.2 标签文件的存储形态:灰度PNG、索引色与ignore_index

图像分割数据集的标签存储常见有两种方式。第一种是单通道灰度PNG,像素值直接用类别ID,0代表建筑、1代表道路,以此类推,读取最简单;第二种是索引色PNG,也叫调色板PNG,文件里存放的是调色板索引,再用一张颜色表把索引映射成显示颜色。很多可视化工具打开第二种标签时能看到非常漂亮的彩色分割图,但直接读像素值就会得到一串完全对不上类别表的数字。用Python读取时,np.array(Image.open(p))对灰度图和P模式索引图都会返回二维索引数组,这点通常没问题;真正容易翻车的是有人先convert('RGB')再读,拿到三个通道的颜色值,之后所有统计都会乱掉。

除了存储方式,还要搞清楚数据里有没有ignore_index,也就是“不参与训练”的像素。常见做法是标注规范规定某些区域不参与训练,比如图像边缘的拼接黑边、未标注区域、模糊区域,会将这部分像素值设为255或特定的忽略值。如果数据里没有这类像素,那所有像素值都应该落在0到7之间;如果存在255,训练时要在损失函数里显式跳过。

对这套约270张的高分辨率数据,我还建议确认一下标签是否和原图严格对齐,尺寸是否一致,文件名是否一一匹配。这几个看起来没什么技术含量的问题,往往是项目里浪费时间最多的源头。原图是JPG、标签是PNG是常见组合,但如果原图被某次预处理脚本旋转过、压缩过,标签没有同步处理,后续模型怎么调都学不出来。

2.3 给270张标签做一次像素级体检:一行脚本找出隐患

在开始训练之前,我会先跑一个像素级检查脚本,确认每个标签文件的像素值范围、类别分布和文件匹配情况。这个脚本简单但非常有效,几乎所有标注质量问题都能在几分钟内暴露出来。

import glob import numpy as np from PIL import Image mask_dir = "annotations" img_dir = "images" mask_paths = sorted(glob.glob(f"{mask_dir}/*.png")) img_paths = sorted(glob.glob(f"{img_dir}/*.jpg")) print(f"标签文件数量: {len(mask_paths)}") print(f"原图文件数量: {len(img_paths)}") # 类别像素占比统计 cnt = np.zeros(8, dtype=np.int64) for p in mask_paths: mask = np.array(Image.open(p)) # 只统计0~7的像素,255视为ignore区域 valid = mask[(mask >= 0) & (mask <= 7)] for cls in range(8): cnt[cls] += (valid == cls).sum() total = cnt.sum() for cls in range(8): print(f"类别 {cls}: {cnt[cls] / total:.2%}")

这个脚本的核心逻辑是把每张标签读成二维数组,然后只关注0到7之间的像素值。如果某张图里出现了8以上的数值,就说明标签里混入了非约定像素,需要单独排查;如果某类占比为0,说明整个数据集里可能根本没有这个类别的样本,训练时就要考虑删掉这个类别,或者补数据。

我还会在这个脚本后面加一段文件名匹配检查,判断图片和标签是否一一对应。具体做法是把两张列表去掉扩展名后求差集,多出来的文件逐一查看。这类问题虽然不是在像素层面的“脏”,但对训练流程的破坏程度一点不低:如果数据集里有几张图没有标签,DataLoader可能随机报错,也可能把错位的图像和标签配成一对,训练出来的模型在对应区域表现会非常诡异。

检查完后,顺手生成一张类别可视化图也是好习惯。把原图、标签叠加图并排保存,快速翻一遍就能发现有没有标反、漏标、错位严重的地块。270张图翻起来很快,这个时间花得相当值。

3. 用高分辨率无人机数据跑通最小语义分割流程:环境、预处理与训练循环

确认标签没问题之后,就可以进入训练流程了。很多人拿到数据集第一反应是找现成的训练仓库,但我建议先在本地把最小闭环跑通,再套用更大的项目框架,否则出了问题根本不知道是数据的问题还是框架的问题。

3.1 环境配置:显存、PyTorch版本与依赖怎么选

这套高分辨率无人机图像分割数据集的训练依赖主要有三块:深度学习框架、GPU显存、数据增强库。PyTorch在当前语义分割领域是事实标准,生态最全,配套的torchvision、albumentations、segmentation_models_pytorch都能直接上手。如果你更熟悉YOLOv8那套训练自己的数据集流程,也可以做分割,但YOLO系做的是实例分割,评估维度跟语义分割不太一样,用在这类城市地物全覆盖任务上不如专门的分割模型直接。

硬件方面,约270张图的样本量不算大,真正吃显存的是图像分辨率。高分辨率无人机影像直接整图送入模型,一张就可能占掉几GB显存,所以8GB到11GB显存的显卡都够用,关键在于控制输入尺寸和batch size。常见做法是先把图像裁剪成512×512或768×768的块,再用batch size 4到8训练一个轻量UNet。如果显卡只有6GB显存,那batch size降到2甚至1都是正常的。

依赖安装我推荐用albumentations做数据增强,它处理图像和标签的同步变换很成熟,能避免手工实现时最容易犯的错位问题。模型部分如果不想自己写UNet,直接用segmentation_models_pytorch一行就能加载带预训练权重的UNet、DeepLabV3等结构。不过用预训练权重时要注意,ImageNet预训练是针对自然图像的,对无人机俯拍影像有帮助但没有想象中那么大,后面第5部分会细讲怎么判断该不该用。

3.2 把270张图变成训练样本:裁剪策略、划分与数据增强

高分辨率无人机图像很少直接整图训练,原因有两点:一是显存放不下,二是分割模型普遍有固定输入尺寸,直接把一张几千乘几千的图压缩到512×512,车辆、行人这类小目标会缩成几个像素,等于把最珍贵的细节抹掉了。常见做法是从每张原图中裁剪出若干子图,每个子图覆盖原图的一个局部区域,这样既能保留小目标信息,又能把数据量从270张扩展到几千个patch。

裁剪方式有两种:随机裁剪和滑动窗口裁剪。随机裁剪适合训练阶段,每次从原图随机取一块512×512区域,配合数据增强让模型看到更多变化;滑动窗口更适合推理阶段,把整张图按固定步长切块预测,再把结果拼回去。对于270张图来说,我一般每张图随机抽4到8块,再配合翻转和旋转增强,训练样本能到一两千个patch,足够一个轻量UNet收敛了。

训练集和验证集的划分要格外注意。虽然只有270张图,但绝不能把同一张图裁剪出来的patch同时放进训练集和验证集,否则模型会在“记答案”,验证指标会虚高到离谱。常见做法是按原图划分,比如200张训练、70张验证,然后确保每个类别的图像在训练和验证中都有分布。如果某个类别只出现在少数几张图里,可以先把该类别的图像单独分出来,再按比例分配。

数据增强方面,我对这类高分辨率场景常用的组合是随机翻转、随机旋转90度、随机亮度对比度扰动、随机高斯模糊。要避免使用随机缩放或随机裁剪时让标签错位,albumentations的Compose可以同时作用于图像和标签,是对的做法。增强强度要适可而止,无人机图像本身视角稳定,过度形变会让模型学到不存在的几何畸变。

3.3 最小训练循环:一个可以直接改着用的PyTorch骨架

下面这个训练骨架按“最小可复现”的原则组织,省略了TensorBoard、模型保存策略等外围功能,只保留数据加载、损失计算、反向传播和验证评估四件事。你只需要把路径换成自己的目录,然后根据显卡调整输入尺寸和batch size。

import os import glob import numpy as np import torch from torch.utils.data import Dataset, DataLoader from torchvision.transforms import functional as F from PIL import Image class DroneSegDataset(Dataset): def __init__(self, img_dir, mask_dir, crop_size=512, augment=False): self.img_paths = sorted(glob.glob(os.path.join(img_dir, "*.jpg"))) self.mask_paths = sorted(glob.glob(os.path.join(mask_dir, "*.png"))) self.crop_size = crop_size self.augment = augment def __len__(self): return len(self.img_paths) def __getitem__(self, idx): image = Image.open(self.img_paths[idx]).convert("RGB") mask = Image.open(self.mask_paths[idx]) # 裁剪到相同尺寸 w, h = image.size if self.augment: crop_x = np.random.randint(0, w - self.crop_size + 1) crop_y = np.random.randint(0, h - self.crop_size + 1) else: crop_x = (w - self.crop_size) // 2 crop_y = (h - self.crop_size) // 2 image = image.crop((crop_x, crop_y, crop_x + self.crop_size, crop_y + self.crop_size)) mask = mask.crop((crop_x, crop_y, crop_x + self.crop_size, crop_y + self.crop_size)) # 转Tensor image = F.to_tensor(image) mask = torch.from_numpy(np.array(mask)).long() return image, mask

这个Dataset类做了两件关键的事:一是用随机裁剪把高分辨率图切成固定尺寸的训练块,二是保证image和mask使用完全相同的裁剪坐标,这是数据加载阶段最不能错的地方,错位的话模型从第一轮就学不到正确映射。mask直接读成long类型,是为了后面交叉熵损失时能作为整数标签使用。如果标签中包含255的忽略像素,需要在损失函数里指定ignore_index=255。

训练循环本身很简单,用UNet作为示例模型,每次迭代算交叉熵损失,反向传播更新参数。

import torch.nn as nn class UNet(nn.Module): # 这里省略结构定义,用 segmentation_models_pytorch 加载即可 pass model = UNet() criterion = nn.CrossEntropyLoss(ignore_index=255) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=60) for epoch in range(60): model.train() for images, masks in DataLoader(train_ds, batch_size=4, shuffle=True): preds = model(images) loss = criterion(preds, masks) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step() # 每个epoch结束后在验证集上算mIoU,记录最优模型

这里的ignore_index=255只对含忽略像素的数据集有意义,如果你的标签严格在0到7之间,也可以不加。学习率1e-4是AdamW在城市语义分割里经验上比较稳的起点,显存允许就batch size开4,不允许就降到2并用梯度累积补足。60个epoch对270张图裁剪出来的patch量来说已经能看出收敛趋势,通常训练loss会在前10个epoch快速下降,之后进入缓慢优化阶段。

3.4 训练过程中怎么判断有没有在收敛

很多人只盯着训练loss降没降,这是不够的。小数据集上训练loss会降得很快,但验证指标可能纹丝不动甚至倒退。我会同时看三件事:训练loss曲线、验证集上的mIoU、以及抽样预测图。如果训练loss下降但验证mIoU长期不涨,大概率是过拟合,这时需要增强正则化,比如增加weight decay、加大数据增强强度、缩小模型规模。如果训练loss和验证mIoU都不动,可能是学习率太低,或者类别像素占比严重失衡导致梯度被多数类主导。

抽样预测图是最直观的手段:每个epoch结束,从验证集里挑几张图,把预测结果和标签并排保存。肉眼扫一遍就能看出模型是在认真学边界,还是只学会了把图片整体涂成背景色。这类高分辨率数据里,建筑和道路这类大目标通常最早被学会,车辆和裸土这类小目标或稀疏目标要等到后半程才慢慢成型,训练时间不够就会造成类别指标两极化。

4. 小样本高分辨率语义分割训练的避坑指南:5条落坑记录

我在多次用这类小规模无人机数据做分割实验时踩过不少坑,下面五条是最典型、也最容易复现的。每条都按“现象、原因、解决”来写,方便你对着检查。

4.1 模型学成“多数类傀儡”,训练mIoU看起来还行但小目标全丢

现象:训练结束时总体mIoU能到0.8以上,但把预测图放大一看,车辆、人行道这类小目标几乎没被预测出来,混淆矩阵里它们的IoU只有0.1甚至0。

原因:约270张图里,建筑和道路的像素占绝对优势,交叉熵损失被这几个大类主导。模型发现只要把整张图预测成建筑和道路,损失就已经很低,没必要再去学习少数类。

解决:先给每个类别算像素占比,然后根据占比反比设置类别权重,比如torch.nn.CrossEntropyLoss(weight=class_weight)。或者更简单实用:在裁剪阶段强制让每张训练patch里包含车辆或裸土等类别,也就是按类别做区域采样。另一种有效做法是使用带class_weight的损失函数,让少数样本类别的梯度贡献更大。

4.2 标签文件里出现0到7之外的像素值,训练直接报shape错误或loss异常

现象:训练到中途突然报错,提示target里出现了不该出现的类别编号;或者loss值莫名其妙变成nan。

原因:有些标签在编辑保存时被图像处理软件二次压缩,PNG边缘出现了抗锯齿过渡色,这些像素值落在0到7之外;还有的标签图用convert('RGB')后按RGB值当类别ID,整个标签数组都错乱了。

解决:在训练前用第2.3节的体检脚本逐张扫描,发现非法像素值后修正。如果是边缘抗锯齿产生的杂点,常见做法是直接把这些杂点像素设为0或255;如果是格式读取问题,用np.array(Image.open(p))而不是Image.open(p).convert('RGB')来读标签。修复脚本每次训练前跑一遍,顺便把标签格式规范化。

4.3 随机裁剪时图像和标签错位,模型学到的是“图像在左、标签在右”

现象:训练loss能下降,但验证结果差到不可理喻,预测结果看起来像是把图像平移了一段距离后分割的结果。

原因:手动实现随机裁剪时分别对图像和标签各调用了一次随机数,两次crop坐标不一致,导致标签和图像错位。这类问题在自然图像数据集上也常见,但无人机高分辨率图上由于纹理重复度高,更不容易察觉。

解决:确保图像和标签使用同一个随机种子、同一个裁剪坐标。最简单的方式是像3.3节代码那样,先算crop_x和crop_y,再用同样的坐标对image和mask执行crop(),不要分别调用RandomCrop。用albumentations的RandomCrop同时传给image和mask也可以,它内部保证了同步变换。

4.4 高分辨率图像直接整图送入模型,显存溢出后换了小batch还是崩

现象:CUDA out of memory,试着把batch size降到1,仍然在模型forward阶段爆显存。

原因:无人机原图可能高达4000×3000像素,直接送入模型时中间特征图的尺寸也很大,显存占用随空间尺寸线性增长。batch size只是其中一个因素,输入分辨率才是主要矛盾。

解决:把训练改为裁剪patch输入,比如512×512或768×768。显存允许时可以用中等分辨率加快训练,但不要用超过模型设计能力的输入。如果推理阶段需要整图预测,使用滑窗推理:把大图切成小块逐块预测,再把结果拼回原图尺寸。滑窗重叠区域出现不一致的预测时,取平均值或按距离加权融合都可以。

4.5 验证集只有几十张图,mIoU每轮波动几个点,选模型像在抽奖

现象:每个epoch在验证集上算出的mIoU忽高忽低,有时一个epoch之间就差3到5个百分点,最后保存的“最优”模型很可能是运气最好的那个epoch而不是真正泛化最好的。

原因:约270张图里分出70张验证集,再切成固定patch后也只有几百个验证样本,空间自相关性又强,相邻patch预测结果高度相似,导致评估指标方差大。

解决:一是验证时采用中心裁剪而非随机裁剪,保证每次评估同一块区域;二是连续评估多个epoch取平均值再比较;三是如果数据量实在太少,就用K折交叉验证。对270张图来说,5折交叉验证是更可靠的方案,虽然训练时间变成5倍,但得到的指标置信度高得多。

5. 把这套数据用得更深:逐类IoU、混淆矩阵与增量标签策略

训练出来的模型指标只是第一步,真正决定这个数据集能不能继续发挥价值的是你对评估结果的解读能力,以及能不能在270张图的基础上有效扩展。

5.1 从mIoU一个数字到逐类IoU:一眼定位短板

整体mIoU会掩盖很多问题,真正到了业务侧,你需要回答的是“哪个类别最不可用”。下面是一段逐类IoU的计算代码,每次验证后跑一遍,把每类的IoU打印出来。

def compute_iou(pred, mask, num_classes=8): ious = [] for cls in range(num_classes): inter = ((pred == cls) & (mask == cls)).sum() union = ((pred == cls) | (mask == cls)).sum() ious.append(inter / float(union) if union > 0 else 0.0) return ious # 验证时使用 pred = torch.argmax(model(images), dim=1).cpu().numpy() ious = compute_iou(pred, masks.numpy())

一旦打出来,你会发现建筑、道路的IoU可能都在0.85以上,而车辆IoU只有0.4。下一步不是盲目加数据,而是针对车辆失效的具体原因做判断:是车辆样本数太少,还是车辆类别在标注里被植被遮挡严重,或是裁剪patch太小把车辆截成碎片了。逐类指标加上混淆矩阵是定位这类问题的基本方法。

5.2 在270张图之外还能做什么:再补一个模型而不是只靠调参

这个规模的数据再怎么调,模型的泛化上限都在那里。我常用的做法是把训练好的模型当“初筛器”,去无人机采集的新视频里跑一遍,把置信度高的预测结果结合原图人工修正后补进数据集。这本质上是半监督学习的伪标签做法,能把标注成本压得很低,同时持续扩充数据。另一个更稳的路线是换用更大数据集的预训练backbone,比如在ImageNet或更大分割数据集上预训练过的模型,但要把backbone冻结前几层,只微调后面几层,让模型保留一些通用纹理特征,而不是一头扎进270张图的过拟合循环。

这类无人机城市图像分割任务的完整落地路径其实就是这样:先彻底搞清标签规范,用最小的流程验证模型能收敛,再用逐类指标定位短板,最后用伪标签或补充标注逐步扩大数据池。我自己的习惯是不管数据多小,都固定随机种子、用同样的划分跑三遍,取指标中位数作为结论,这样就不会被单次运行的运气带着走。希望帮到你。

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

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

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

立即咨询