十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

从GF-2卫星影像到语义分割数据集:基于Labelme的遥感数据制作全流程

从GF-2卫星影像到语义分割数据集:基于Labelme的遥感数据制作全流程 1. 项目概述从零构建遥感专属数据集的必要性在计算机视觉领域尤其是在遥感图像分析这个细分赛道里一个共识是模型的上限很大程度上由数据决定。我们经常看到各种先进的语义分割模型如U-Net、DeepLabv3甚至是基于Transformer的SegFormer在公开数据集上表现优异。但一旦将这些模型直接套用到自己的项目比如识别自家农田里的作物长势、分析特定工业园区的地物变化或者监测某段海岸线的侵蚀情况时效果往往大打折扣。问题的核心通常不在于模型不够先进而在于数据“水土不服”。公开数据集如Cityscapes、PASCAL VOC乃至一些遥感数据集如ISPRS Vaihingen其地物类别、成像条件、分辨率与我们的特定任务目标存在显著差异。这就是为什么“从原始图像开始制作自己的数据集”成为了从业者特别是遥感应用开发者和研究人员的必备技能。本次我将以国产高分二号GF-2卫星影像为例手把手带你走通从原始多光谱数据到可用于模型训练的标准语义分割数据集的全流程。高分二号具有亚米级全色和4米多光谱的分辨率数据易于获取且应用广泛是实践此过程的理想样本。整个过程不仅涉及数据预处理、标注工具如Labelme的使用更关键的是如何根据遥感影像特点设计标注规范以及如何处理标注后的数据以适应主流深度学习框架如PyTorch, TensorFlow。掌握这套方法意味着你拥有了将任何原始遥感影像转化为“燃料”驱动定制化AI模型的能力。2. 核心思路与方案设计为何选择“原始图像-Labelme-标准格式”路径面对一堆原始的GF-2卫星影像可能是.tiff格式的多波段文件要将其变成模型可读的数据集有几种常见路径。例如使用专业的遥感处理软件如ENVI进行交互式分类并导出或者寻找支持直接读取地理信息的深度学习库。然而对于大多数专注于算法开发和应用的团队来说最通用、最灵活且学习成本相对较低的方案是将遥感图像转换为普通RGB图像利用通用图像标注工具进行像素级标注最后转换为深度学习框架通用的数据集格式。我选择这条路径主要基于以下几点考量工具生态成熟以Labelme为代表的通用标注工具经过多年发展其多边形标注功能稳定、交互友好且拥有庞大的用户社区遇到问题容易找到解决方案。相较于学习一套全新的专业遥感软件Labelme的上手速度更快。流程标准化输出为通用的图像-掩膜对如JPEGPNG使得后续数据增强、模型训练可以完全脱离遥感数据的特殊性复用大量为自然图像设计的成熟代码和工具链降低了工程复杂度。灵活性高你可以自由定义任何你关心的地物类别如“建筑”、“道路”、“水体”、“林地”、“裸土”、“特定作物”而不受限于预定义的分类体系。适用于多源数据这套方法不仅适用于GF-2稍作调整主要是预处理环节便可应用于无人机影像、航空照片、甚至其他卫星如Sentinel-2, Landsat数据具有很好的可扩展性。整个方案的核心链路可以概括为数据准备与预处理 - 样本切片 - 人工标注 - 格式转换与数据集组织。其中预处理和样本切片是针对遥感影像大尺寸、多波段特性的关键适配步骤而标注与转换则是保证数据质量与可用性的核心。3. 数据准备与预处理将原始GF-2数据变为可标注的RGB图像拿到原始的GF-2数据通常是一个包含多个波段的TIFF文件。GF-2多光谱影像包含蓝B、绿G、红R、近红外NIR四个波段。我们的第一步是将其处理成视觉上易于辨认、且符合标注习惯的RGB彩色图像。3.1 波段合成与真彩色转换大多数情况下我们期望看到接近人眼视觉的“真彩色”图像。对于GF-2这意味着需要使用其B、G、R波段分别对应RGB通道。这里我强烈推荐使用Python的rasterio和numpy库进行操作它们能很好地处理地理栅格数据。import rasterio import numpy as np import cv2 # 打开原始多波段TIFF文件 with rasterio.open(GF2_原始数据.tiff) as src: # 读取蓝、绿、红波段注意波段索引通常1,2,3对应B,G,R blue src.read(1) green src.read(2) red src.read(3) # 获取图像的元数据如变换参数、坐标系等后续可能用到 profile src.profile # 将波段数据堆叠成HWC格式的数组 rgb_array np.stack([red, green, blue], axis-1) # 遥感数据通常为16位需要拉伸到8位0-255以便显示和保存 def stretch_to_8bit(band, lower_percent2, upper_percent98): # 去除极端值按百分比截断 lower np.percentile(band[band0], lower_percent) # 忽略0值可能是背景 upper np.percentile(band[band0], upper_percent) band_stretched np.clip(band, lower, upper) # 线性拉伸到0-255 band_8bit ((band_stretched - lower) / (upper - lower) * 255).astype(np.uint8) return band_8bit rgb_8bit np.stack([stretch_to_8bit(red), stretch_to_8bit(green), stretch_to_8bit(blue)], axis-1) # 保存为标准的RGB图像文件如JPEG或PNG cv2.imwrite(GF2_RGB.jpg, cv2.cvtColor(rgb_8bit, cv2.COLOR_RGB2BGR)) # OpenCV使用BGR顺序注意波段拉伸是预处理的关键一步。直接使用原始DN值数字量化值合成的图像可能对比度极低一片灰暗无法进行有效标注。百分比截断拉伸如2%-98%是一种稳健的方法它能有效增强图像视觉效果同时抑制噪声和异常值的影响。3.2 影像切片处理大尺寸图像的实用策略一幅完整的GF-2影像可能超过10000x10000像素直接载入Labelme进行标注会非常卡顿且不利于后续小批量加载训练。因此我们需要将其切割成更小的切片Patches例如512x512或1024x1024。from PIL import Image import os def slice_image(image_path, output_dir, patch_size512, overlap128): 将大图像切割成有重叠的小块。 :param overlap: 重叠像素有助于避免切割边界处的目标被切断后续可做处理。 img Image.open(image_path) img_width, img_height img.size os.makedirs(output_dir, exist_okTrue) patch_id 0 for y in range(0, img_height, patch_size - overlap): for x in range(0, img_width, patch_size - overlap): # 计算当前切片的实际边界防止越界 x_end min(x patch_size, img_width) y_end min(y patch_size, img_height) box (x, y, x_end, y_end) patch img.crop(box) # 保存切片文件名包含位置信息以便追溯 patch_name fpatch_{patch_id:04d}_x{x}_y{y}.jpg patch.save(os.path.join(output_dir, patch_name)) patch_id 1切片时设置一定的重叠overlap非常重要。这能确保那些恰好位于切片边界上的建筑物、道路等目标不会被硬生生切断在训练时可以减少边界效应。在后续将标注转换为掩膜时重叠区域需要特殊处理如只保留中心区域或在后处理中融合。4. 使用Labelme进行像素级语义分割标注获得RGB切片后就进入了人工标注环节。Labelme是一款非常优秀的开源图像标注工具特别适合多边形标注。4.1 Labelme的安装与基本标注流程安装非常简单通过pip即可完成pip install labelme。安装后在命令行输入labelme即可打开图形界面。标注流程的核心步骤如下打开图像文件夹使用Open Dir打开存放RGB切片的文件夹。创建标签在左侧Labels区域点击号添加你定义的地物类别例如building,road,water,forest。建议为每个类别选择一种醒目的颜色便于区分。绘制多边形选择左侧的一个标签如building。使用Create Polygon工具或按快捷键CtrlN沿着目标的轮廓依次点击形成一个闭合多边形。闭合后该区域会被填充为标签对应的颜色。保存标注完成一张图片上所有目标的标注后点击Save或按CtrlS。Labelme会生成一个与图像同名的.json文件其中以几何坐标的形式存储了所有多边形的位置及其对应的标签。4.2 针对遥感影像的标注实践与技巧尺度问题遥感影像中地物尺度差异巨大。一栋房子可能只占几十个像素而一片森林可能覆盖整个切片。标注时需灵活调整视图缩放级别。边界模糊问题某些地物边界如植被到裸土的过渡可能不清晰。此时应遵循一致的判断标准例如以颜色或纹理的显著变化作为边界。可以在团队内部制定简单的标注规范文档。小目标处理对于像素面积很小的独立地物如孤立的树木、小汽车如果其对任务重要应尽量标注如果无关紧要可以忽略以降低标注成本和模型学习难度。利用先验知识标注者最好具备一定的遥感解译知识能区分“水体”可能是深色、形状不规则和“阴影”通常与建筑物或地形相关避免混淆。批量标注与质检这是一个耗时过程。建议进行分批标注并定期进行质量检查。可以随机抽样已标注的图片检查多边形闭合是否准确、标签是否正确、有无遗漏等。5. 标注数据转换从Labelme JSON到标准分割掩膜Labelme生成的.json文件不是深度学习框架直接需要的格式。我们需要将其转换为每张图片对应的语义分割掩膜Mask。掩膜是一张单通道图像其上每个像素的值代表了该像素所属的类别索引例如0代表背景1代表建筑2代表道路...。5.1 转换脚本的核心逻辑以下Python脚本演示了如何批量将Labelme的JSON标注转换为PNG格式的掩膜。import json import os import numpy as np from PIL import Image, ImageDraw import glob # 定义类别映射字典 class_name_to_id { “_background_”: 0, # Labelme会自动包含背景 “building”: 1, “road”: 2, “water”: 3, “forest”: 4, # ... 添加其他类别 } def json_to_mask(json_path, output_mask_dir): # 读取JSON文件 with open(json_path, ‘r’) as f: data json.load(f) image_height data[‘imageHeight’] image_width data[‘imageWidth’] # 创建一个全零的数组作为掩膜画布 mask np.zeros((image_height, image_width), dtypenp.uint8) # 遍历JSON中的每一个形状多边形 for shape in data[‘shapes’]: label_name shape[‘label’] # 忽略未在映射表中的标签或视为背景 if label_name not in class_name_to_id: continue label_id class_name_to_id[label_name] # 提取多边形点坐标 points shape[‘points’] # 将点列表转换为多边形顶点格式 (需要整数坐标) polygon [(int(point[0]), int(point[1])) for point in points] # 使用PIL在掩膜上绘制填充的多边形 mask_pil Image.fromarray(mask) draw ImageDraw.Draw(mask_pil) draw.polygon(polygon, outlinelabel_id, filllabel_id) mask np.array(mask_pil) # 保存掩膜为PNG文件 mask_filename os.path.basename(json_path).replace(‘.json’, ‘_mask.png’) mask_output_path os.path.join(output_mask_dir, mask_filename) Image.fromarray(mask).save(mask_output_path) print(f’Saved mask to {mask_output_path}’) # 批量处理 json_files glob.glob(‘path/to/your/labelme_jsons/*.json’) output_dir ‘path/to/mask_output’ os.makedirs(output_dir, exist_okTrue) for json_file in json_files: json_to_mask(json_file, output_dir)5.2 处理切片重叠区域的掩膜由于我们之前切片时有重叠直接转换得到的掩膜在边缘区域会存在重复标注。常见的处理方式有两种中心区域裁剪法在制作最终数据集时从每个切片及其掩膜的中心裁剪出一个无重叠的区域例如从512x512中裁剪出384x384。这种方法简单但会损失一部分图像数据。重叠区域融合法在训练时将重叠部分视为增强数据。在评估或推理时使用滑动窗口并融合重叠区域的预测结果如取平均值或最大值。这种方法更复杂但能充分利用数据。对于初学者我建议采用第一种方法先确保数据集的干净和一致。可以使用以下代码进行中心裁剪def crop_center_patch(img_path, mask_path, output_size384): img Image.open(img_path) mask Image.open(mask_path) width, height img.size left (width - output_size) / 2 top (height - output_size) / 2 right (width output_size) / 2 bottom (height output_size) / 2 img_cropped img.crop((left, top, right, bottom)) mask_cropped mask.crop((left, top, right, bottom)) return img_cropped, mask_cropped6. 构建标准数据集目录与数据加载器现在我们有了配对的图像切片和掩膜切片。接下来需要按照深度学习框架的惯例组织它们并编写数据加载代码。6.1 数据集目录结构一个清晰的结构有助于管理和后续的扩展。我推荐如下结构GF2_Segmentation_Dataset/ ├── images/ # 存放所有RGB图像切片 (JPEG) │ ├── train/ │ │ ├── patch_0001_x0_y0.jpg │ │ └── ... │ └── val/ │ ├── patch_1001_x5120_y0.jpg │ └── ... ├── masks/ # 存放所有对应的掩膜文件 (PNG) │ ├── train/ │ │ ├── patch_0001_x0_y0_mask.png │ │ └── ... │ └── val/ │ ├── patch_1001_x5120_y0_mask.png │ └── ... └── class_dict.csv # (可选) 类别索引与名称、颜色的对应关系文件你需要手动或写脚本将图像-掩膜对按照一定比例如8:2划分到train和val文件夹。class_dict.csv文件对于可视化预测结果非常有用。6.2 编写PyTorch Dataset类这是连接数据和模型的关键桥梁。下面是一个基本的实现示例import torch from torch.utils.data import Dataset from PIL import Image import os import torchvision.transforms as T class GF2SegDataset(Dataset): def __init__(self, image_dir, mask_dir, transformNone): self.image_dir image_dir self.mask_dir mask_dir self.transform transform # 假设images和masks目录下文件名一一对应除了后缀 self.image_names sorted([f for f in os.listdir(image_dir) if f.endswith(‘.jpg’)]) def __len__(self): return len(self.image_names) def __getitem__(self, idx): img_name self.image_names[idx] img_path os.path.join(self.image_dir, img_name) mask_name img_name.replace(‘.jpg’, ‘_mask.png’) # 根据你的命名规则调整 mask_path os.path.join(self.mask_dir, mask_name) # 打开图像和掩膜 image Image.open(img_path).convert(‘RGB’) mask Image.open(mask_path) # 模式为’P‘或’L‘ # 确保掩膜是单通道的numpy数组值为类别索引 mask np.array(mask) # 如果掩膜是RGB误存需要处理这里假设已经是索引图 if self.transform is not None: # 注意对图像和掩膜应用相同的空间变换如旋转、翻转 # 但颜色变换只应用于图像 augmented self.transform(imageimage, maskmask) image augmented[‘image’] mask augmented[‘mask’] else: # 基础转换图像转Tensor并归一化掩膜转LongTensor to_tensor T.ToTensor() image to_tensor(image) # 通常会对图像进行归一化例如 mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] normalize T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) image normalize(image) mask torch.from_numpy(mask).long() return image, mask这个Dataset类会返回一个已经预处理好的图像张量和掩膜张量可以直接送入模型进行训练。7. 数据增强策略与类别不平衡处理7.1 针对遥感影像的数据增强数据增强是提升模型泛化能力、防止过拟合的有效手段。对于遥感影像除了通用的旋转、翻转、缩放外还可以考虑色彩抖动轻微调整亮度、对比度、饱和度和色调模拟不同光照、大气条件下的成像效果。随机裁剪在训练时随机裁剪出子区域增加空间多样性。添加噪声模拟传感器噪声。模糊模拟不同的大气条件或轻微失焦。可以使用albumentations这个强大的库它支持对图像和掩膜进行同步增强。import albumentations as A train_transform A.Compose([ A.RandomRotate90(p0.5), A.Flip(p0.5), A.RandomBrightnessContrast(brightness_limit0.1, contrast_limit0.1, p0.5), A.HueSaturationValue(hue_shift_limit10, sat_shift_limit20, val_shift_limit10, p0.5), A.GaussNoise(var_limit(10.0, 50.0), p0.3), A.RandomCrop(height384, width384, p1.0), # 如果输入是512可以随机裁剪384 ])7.2 处理语义分割中的类别不平衡在遥感影像中“背景”如大片裸土或未知区域的像素数量可能远多于“道路”或“建筑”。直接训练会导致模型偏向于预测大类。常用解决方法有损失函数加权在交叉熵损失函数中为每个类别赋予不同的权重。权重通常与类别像素频率成反比。# 计算每个类别的权重 class_weights 1.0 / torch.log(frequency 1e-6) # 一种常见的计算方式 criterion torch.nn.CrossEntropyLoss(weightclass_weights)过采样/欠采样在数据加载阶段对包含稀有类别的图像进行更频繁的采样。在线难例挖掘OHEM在训练过程中更多地关注那些被当前模型错误分类的像素。使用Dice Loss、Focal Loss等这些损失函数本身就对类别不平衡有一定的鲁棒性。在实践中我通常会先尝试加权交叉熵损失并结合数据增强这往往能取得不错的效果。8. 常见问题与实战排坑记录在从原始图像制作数据集的全流程中会遇到各种各样的问题。以下是我在实践中总结的一些典型问题及解决方案。8.1 标注与转换环节问题Labelme标注的多边形在转换后的掩膜上出现锯齿或空洞。原因多边形顶点坐标可能是浮点数在转换为整数像素坐标时产生取整误差或者多边形本身自相交。解决在标注时尽量让多边形贴合目标边缘避免过于曲折的边界。在转换脚本中可以使用更稳健的多边形填充算法如cv2.fillPoly。确保多边形坐标在填充前转换为整数。# 使用OpenCV的fillPoly points np.array(polygon, dtypenp.int32) cv2.fillPoly(mask, [points], colorlabel_id)问题转换后的掩膜与原始图像对不齐。原因最常见的原因是图像在预处理如拉伸、保存后尺寸发生了变化但JSON中记录的仍是原始尺寸。解决确保用于标注的RGB图像与最终用于训练的图像是同一个文件或者尺寸严格一致。在预处理如切片后直接用处理后的图像进行标注。8.2 数据集构建与训练环节问题训练时损失不下降或震荡剧烈。原因学习率设置不当数据标注噪声太大类别权重设置不合理。排查可视化一批训练数据检查图像和掩膜是否对应正确掩膜值是否在预期的类别索引范围内如0,1,2,3。检查数据增强是否过于激进导致图像失真严重。尝试使用更小的学习率或使用学习率预热Warmup策略。计算并打印每个批次的类别分布看是否某个类别完全缺失。问题模型预测结果全是背景或某一类。原因严重的类别不平衡导致模型“偷懒”损失函数权重设置错误模型能力不足或训练轮次不够。解决首先检查数据确保你的训练集中确实包含所有类别的样本。检查损失函数权重如果使用了加权损失确保权重张量被正确加载到GPU如果使用GPU。简化问题先尝试只分割两类如“建筑”和“非建筑”看模型能否正常工作。使用更简单的模型架构如U-Net进行快速验证排除复杂模型带来的调试困难。问题在切片边界处预测结果出现明显的接缝。原因这是滑动窗口预测的固有难题。每个切片独立预测边界处缺乏上下文信息。解决重叠预测与融合在推理时使用比训练切片更大的重叠区域进行预测然后对重叠部分的概率取平均或加权平均。测试时增强TTA对同一区域进行多次预测如原图、水平翻转、垂直翻转将结果平均可以平滑边界。使用更优的模型考虑采用能够捕获更大上下文的模型如带有空洞卷积Dilated Conv或Transformer模块的模型。8.3 效率与工程化标注效率低下对于大面积、形状规则的地物如农田、大型厂房可以尝试使用“智能标注”工具辅助。例如使用SAMSegment Anything Model等大模型生成初步分割建议再由人工进行微调和修正可以极大提升标注效率。虽然SAM并非专为遥感设计但其强大的零样本分割能力对许多典型地物依然有效。数据版本管理数据集在迭代如增加样本、修正错误标注过程中需要有版本管理意识。可以使用dvcData Version Control工具或简单的文件夹命名规则如v1.0,v1.1来管理不同版本的数据集和对应的模型训练结果。从一张张原始的GF-2卫星影像到最终组织有序、可用于训练深度学习模型的数据集这个过程充满了细节和挑战。它不仅仅是技术操作更需要对任务目标的理解、对数据特性的把握以及严谨的工程习惯。这份自己亲手打造的数据集将成为你解决特定遥感问题最坚实的基础。当你看到模型在自己标注的数据上准确识别出目标时那种成就感是直接使用公开数据集无法比拟的。整个流程中最耗时的无疑是标注环节但这也是最值得投入的部分因为高质量的数据是AI模型成功的基石。在后续的模型训练与调优中你可能会发现数据层面的问题这时就需要回到标注环节进行修正和迭代形成一个“数据-模型”闭环优化的过程。
返回列表