1. 项目概述:为什么从零开始制作遥感数据集
如果你正在研究遥感图像的语义分割,比如用深度学习模型去识别高分二号影像里的建筑、道路、水体,那你肯定遇到过最头疼的问题:找不到现成的、完全符合你研究区或任务需求的数据集。网上的公开数据集,要么区域不对,要么类别定义和你想要的不一样,要么标注质量参差不齐。这时候,自己动手,从原始的高分二号(GF-2)影像开始,制作一套量身定制的数据集,就成了必经之路,也是最能保证后续模型效果的关键一步。
这个过程听起来有点吓人,尤其是对刚入门的朋友。它涉及到数据获取、预处理、标注工具选择、标注规范制定、格式转换等一系列环节。但别担心,这其实是一个标准化流程,一旦跑通,你就会发现它并没有想象中那么复杂,而且能给你带来巨大的灵活性和控制力。我自己在多个城市建筑物提取、农田地块分割的项目里,都是这么一步步走过来的。今天,我就以国产的高分二号卫星影像为例,带你完整走一遍这个流程,把每个环节的“坑”和技巧都讲清楚。
简单来说,我们要做的是:拿到原始的GF-2多光谱或全色影像 -> 进行必要的预处理(如辐射定标、大气校正、图像融合、裁剪)-> 使用标注工具(比如我们这里会用
labelme
)人工勾绘出我们感兴趣的地物类别 -> 将标注结果转换成模型训练所需的格式(如PNG掩码图)-> 最后整理成标准的数据集目录。核心关键词就是:
遥感图像、语义分割、数据集制作、高分二号、labelme
。
2. 高分二号遥感影像的获取与预处理
2.1 理解高分二号数据源
高分二号(GF-2)是我们国家高分辨率对地观测系统的重要一员,它的数据在国土资源调查、城市规划、环境监测等领域应用非常广泛。选择它作为例子,一是因为数据相对容易获取(通过官方渠道或合作单位),二是因为其参数具有代表性。
GF-2搭载了两台高分辨率1米全色、4米多光谱相机。这里有几个关键参数你需要心里有数:
- 全色波段 :1米分辨率,只有一个波段,图像细节非常丰富,但缺乏色彩信息。
- 多光谱波段 :4米分辨率,包含蓝、绿、红、近红外四个波段。多光谱信息对于区分地物类型(比如植被、水体)至关重要。
- 幅宽 :约45公里,一次能拍挺大一块区域。
我们拿到的原始数据,通常是分发的“原始数据包”,里面包含全色和多光谱的单独文件,以及一堆描述成像参数的元数据文件(.xml或 .rpb)。你不可能直接把这一大包数据扔给标注软件,必须经过预处理。
2.2 数据预处理全流程拆解
预处理的目标是得到一幅清晰、色彩自然、几何精度高的“底图”,方便我们在上面进行精确标注。这个过程专业上叫“影像预处理流程”。
第一步:辐射定标与大气校正 这是为了将传感器记录的原始数字量化值(DN值)转换为具有物理意义的地表反射率。简单理解,就是消除大气散射、吸收等影响,让图像的颜色更接近地物真实的反射特性。这对于后续利用光谱信息区分地物(比如用近红外波段识别植被)非常重要。
-
常用工具
:ENVI、PCI Geomatica、或者开源工具如
Sen2Cor(针对哨兵,但原理相通)。GF-2数据通常提供定标系数,可以在ENVI中使用Radiometric Calibration工具,选择传感器类型为GF-2,输入定标系数文件来完成。 - 我的经验 :如果你的研究区域大气条件良好,且你主要关注形状信息而非精细光谱分析,有时为了快速验证,可以跳过大气校正,只做辐射定标。但对于严谨的研究,尤其是涉及植被、水体等光谱敏感地物,这一步建议不要省。
第二步:全色与多光谱图像融合 这是高分影像处理的精髓。我们要把1米分辨率的全色图像(细节好)和4米分辨率的多光谱图像(颜色好)合二为一,得到一幅兼具高空间分辨率和多光谱信息的新图像。
- 融合算法选择 :算法很多,如PCA(主成分分析)、Brovey、Gram-Schmidt、NNDiffuse等。对于GF-2, Gram-Schmidt(GS)融合 是公认效果较好的一种,它能较好地保持光谱保真度和空间细节。
-
实操步骤(以ENVI为例)
:
- 分别打开经过辐射定标的全色和多光谱影像。
-
在工具箱中搜索
Image Sharpening->Gram-Schmidt Pan Sharpening。 -
按照向导,选择多光谱影像作为
Low Resolution Multispectral Input,全色影像作为High Resolution Pan Input。 - 设置输出参数,运行即可得到融合后的1米分辨率多光谱影像。
- 注意事项 :融合后一定要目视检查,看看有没有出现色彩失真(比如植被变得不绿了)或者“重影”现象。如果效果不佳,可以尝试调整融合算法的参数,或者换一种算法(如NNDiffuse)试试。
第三步:研究区裁剪与格式转换 一整景GF-2影像太大了(好几GB),我们通常只关心其中一小块研究区。
- 裁剪 :在ENVI或QGIS中,根据你的研究区矢量边界(或者手动画一个矩形)对融合后的影像进行裁剪。这能极大减小数据量,加快后续标注和模型训练的速度。
-
格式转换
:将裁剪后的影像保存为标注工具兼容的格式。
labelme支持直接读取GeoTIFF,但有时大尺寸的GeoTIFF在labelme中浏览会卡顿。一个稳妥的做法是,将影像 输出为标准的RGB三波段JPEG或PNG图片 。具体操作是:在ENVI中,选择File -> Save As -> Image File,选择输出格式为JPEG/PNG,并在Spatial Subset中确认裁剪范围,在Spectral Subset中选择RGB Bands,通常对应红、绿、蓝三个波段(例如GF-2的Band3, Band2, Band1)。这样得到的是一张普通的图片文件,但坐标信息会丢失。 务必备份好原始的GeoTIFF文件和其对应的坐标信息文件(.jgw或 .tfw) ,以备后续可能需要的坐标回溯。
重要提示 :预处理流程并非一成不变。如果你的原始数据已经是经过正射校正和融合的“2级产品”,那么前两步可能已经由数据提供商完成。你需要仔细阅读数据说明文档。我们的核心原则是:为标注人员提供一幅 视觉解释性最强 的底图。
3. 使用Labelme进行语义分割标注
3.1 Labelme的安装与基础配置
labelme
是一个用Python编写的图形化图像标注工具,特别适合多边形标注,是语义分割数据制作的利器。它比一些商业软件轻量,且完全免费、开源。
-
安装(强烈推荐使用Conda环境)
:
安装完成后,在命令行输入# 创建并激活一个专门的标注环境 conda create -n labelme python=3.8 conda activate labelme # 使用pip安装labelme pip install labelmelabelme即可启动图形界面。 - 为什么用Conda :避免与系统中其他Python项目的包版本冲突。标注可能是个长期工作,一个独立的环境更干净。
启动
labelme
后,界面很简洁。我们先进行关键配置:点击顶部菜单栏的
File -> Change Output Dir
,设置一个文件夹用于存放所有标注产生的
.json
文件。建议为每个项目建立清晰的目录结构,例如:
My_GF2_Dataset/
├── images/ # 存放预处理后的JPEG底图
├── annotations/ # 存放labelme生成的json文件
└── labels/ # 后续存放转换得到的掩码图
3.2 制定标注规范与实操标注
在开始狂点鼠标之前,花时间制定一份清晰的《标注规范》是事半功倍的关键。这能保证不同标注人员(甚至不同时间的你自己)结果的一致性。
1. 定义类别及其对应颜色
:
在
labelme
中,类别是通过你在标注时输入的
label
名字来区分的。你需要预先规划好所有类别。例如一个简单的城市地块分类:
| 类别名称 (Label) | 含义说明 | 建议颜色 (RGB) | 备注 |
|---|---|---|---|
| building | 建筑物屋顶轮廓 | 红色 (255,0,0) | 包含居民楼、厂房等,忽略屋顶附属物 |
| road | 主干道、铺装道路 | 灰色 (128,128,128) | 包含路面,不含路肩、绿化带 |
| water | 河流、湖泊、池塘 | 蓝色 (0,0,255) | 静止或流动的水体 |
| vegetation | 树木、草地、农田 | 绿色 (0,255,0) | 连片的绿色植被区域 |
| background | 背景/未标注区域 | 黑色 (0,0,0) | 无需手动标注,是默认值 |
2. 标注实操步骤与技巧 :
-
打开图像
:在
labelme中点击Open Dir,选择你的images/文件夹。 -
创建多边形
:点击左侧工具栏的
Create Polygon按钮(或按快捷键Ctrl + P),然后沿着目标地物的边界依次点击,形成闭合多边形。双击最后一个点或按回车键完成闭合。 - 输入标签 :闭合后会自动弹出标签输入框。从你预定义的类别列表中选择或输入。 强烈建议使用英文标签 ,避免后续脚本处理出现编码问题。
-
高级技巧
:
- 放大与导航 :用鼠标滚轮放大缩小,按住空格键拖动画布。标注精细边界(如建筑屋檐)时,必须放大到像素级进行操作。
-
编辑形状
:标注错了?选中已创建的多边形,拖动顶点可以调整形状。右键点击多边形,可以选择
Edit Label修改标签,或Delete Shape删除。 -
复杂形状处理
:对于形状特别复杂的地物(如不规则湖泊),不必追求一个多边形搞定。可以用多个多边形(
Multi-polygon)来拼接,只要它们都属于同一个label(如water),后续转换时会自动合并。 -
“洞”的处理
:如果建筑物中间有天井或中空部分,你需要先标注外部轮廓,再在里面标注一个内部轮廓,并在输入标签时将其指定为
background或其他特定标签。labelme支持这种“孔洞”结构。
-
保存
:标注完一张图后,点击
Save,会在你设置的annotations/目录下生成一个同名的.json文件。这个文件记录了所有多边形顶点的坐标和对应的标签。
3. 标注质量把控 :
- 边界紧贴 :多边形边界应尽可能紧贴地物边缘,避免留出过多空白或包含无关区域。
-
类别一致
:确保同类地物标签完全一致,比如不要有些写
building,有些写Building。 -
定期检查
:标注一段时间后,随机抽查几张图的
.json文件,用labelme重新打开看看效果。也可以让同事交叉检查。
4. 将Labelme标注转换为模型训练格式
4.1 理解标注文件与目标格式
labelme
生成的
.json
文件是便于人类编辑和软件读取的中间格式,但绝大多数语义分割模型(如U-Net, DeepLab, SegFormer)训练时,需要的是
像素级的标签掩码图
。通常这是一张单通道的PNG图像,图像中每个像素点的值是一个整数,代表其所属的类别索引(例如,0代表背景,1代表建筑,2代表道路...)。
我们的核心任务就是将包含多边形信息的
.json
文件,“渲染”成这种单通道的掩码图。
4.2 使用脚本进行批量格式转换
手动转换是不可能的。我们需要编写或使用一个Python脚本进行批量处理。
labelme
官方提供了一些示例脚本,我们可以在此基础上修改。
1. 准备标签名到索引的映射文件
:
创建一个名为
labels.txt
的文本文件,内容如下:
__ignore__
_background_
building
road
water
vegetation
注意:前两行
__ignore__
和
_background_
是
labelme
转换脚本通常要求的,
_background_
对应索引0。后面的行就是我们的类别,索引从1开始自动分配。
2. 执行转换脚本
:
labelme
安装后,自带一个命令行工具
labelme_json_to_dataset
,但它一次只能处理一个文件。我们需要其批量版本。你可以使用以下Python脚本(保存为
json_to_dataset.py
):
import os
import json
import numpy as np
import PIL.Image
import PIL.ImageDraw
# 定义路径
json_dir = 'path/to/your/annotations' # 存放json文件的文件夹
img_dir = 'path/to/your/images' # 存放原图的文件夹(用于获取图像尺寸)
label_file = 'path/to/your/labels.txt' # 标签映射文件
output_dir = 'path/to/your/labels' # 输出掩码图的文件夹
os.makedirs(output_dir, exist_ok=True)
# 读取标签列表
with open(label_file, 'r') as f:
labels = f.read().splitlines()
label_map = {name: idx for idx, name in enumerate(labels)} # 创建标签到索引的映射
# 遍历所有json文件
for json_name in os.listdir(json_dir):
if not json_name.endswith('.json'):
continue
json_path = os.path.join(json_dir, json_name)
base_name = os.path.splitext(json_name)[0]
# 加载json数据
with open(json_path, 'r') as f:
data = json.load(f)
# 获取图像尺寸
img_path = os.path.join(img_dir, base_name + '.jpg') # 假设原图是jpg
img = PIL.Image.open(img_path)
img_width, img_height = img.size
# 创建全零的掩码图(背景为0)
mask = np.zeros((img_height, img_width), dtype=np.uint8)
# 遍历json中的每个形状(多边形)
for shape in data['shapes']:
label_name = shape['label']
# 忽略 `__ignore__` 标签
if label_name == '__ignore__':
continue
points = shape['points']
polygon = [(p[0], p[1]) for p in points] # 将点列表转换为多边形坐标
# 获取该标签对应的索引
label_idx = label_map.get(label_name)
if label_idx is None:
print(f"警告:在文件 {json_name} 中发现未定义标签 '{label_name}',已跳过。")
continue
# 使用PIL将多边形绘制到掩码图上
img_mask = PIL.Image.new('L', (img_width, img_height), 0)
draw = PIL.ImageDraw.Draw(img_mask)
# 注意:PIL的多边形填充要求坐标是整数元组
int_polygon = [(int(x), int(y)) for (x, y) in polygon]
draw.polygon(int_polygon, fill=label_idx)
# 将绘制好的多边形合并到总掩码中(使用np.maximum,后绘制的覆盖先绘制的)
mask = np.maximum(mask, np.array(img_mask))
# 保存掩码图
mask_img = PIL.Image.fromarray(mask)
mask_save_path = os.path.join(output_dir, base_name + '_label.png') # 常用后缀 _label.png 或 .png
mask_img.save(mask_save_path)
print(f'已处理: {json_name} -> {mask_save_path}')
print('所有标注文件转换完成!')
3. 运行与检查
:
在配置好
labelme
环境的终端中,运行
python json_to_dataset.py
。脚本会遍历所有
.json
文件,并在
output_dir
中生成对应的掩码PNG图。
关键检查
:用图片查看器打开生成的掩码图,它应该看起来是黑乎乎的,带有不同灰度的区域。你可以用简单的Python代码或图像处理软件将其映射为彩色查看,确保每个类别的区域都正确无误。
4.3 构建最终数据集目录
转换完成后,我们就有了模型训练所需的“图像-标签”对。标准的语义分割数据集目录结构如下:
GF2_Segmentation_Dataset/
├── images/
│ ├── train/ # 训练集原图
│ ├── val/ # 验证集原图
│ └── test/ # 测试集原图(可选)
├── masks/ # 或 labels/
│ ├── train/ # 训练集掩码图
│ ├── val/ # 验证集掩码图
│ └── test/ # 测试集掩码图(可选)
└── dataset_info.json # 可选,记录类别信息、数据统计等
你需要将预处理好的JPEG原图和对应的
_label.png
掩码图,按照一定的比例(如70%训练,15%验证,15%测试)分别放入
images/train
,
masks/train
等文件夹。
务必确保原图和掩码图的文件名严格对应
(例如
area1.jpg
对应
area1_label.png
)。
5. 标注过程中的常见问题与解决策略
制作数据集是个细致活,踩坑是常态。下面是我总结的几个典型问题及解决办法。
5.1 图像尺寸过大导致标注卡顿或崩溃
问题
:GF-2融合后1米分辨率的图像,即使裁剪后,单边可能也有上万像素。直接在
labelme
中打开这样的巨型图片,会非常卡顿,甚至导致程序无响应。
解决方案
:
-
预先分块
:在预处理裁剪阶段,不要直接裁剪出整个研究区。用一个脚本将大图切割成重叠的小图块(例如1024x1024像素)。用
labelme标注这些小图块,速度会快很多。训练时也直接使用这些小图块。 -
调整
labelme设置 :在labelme中,点击File -> Settings,可以调整缓存大小。但这对巨型图像帮助有限。 -
使用金字塔技术(高级)
:将影像处理为金字塔式TIFF,但
labelme支持有限。更实用的方法是使用专业的GIS软件(如QGIS)进行初标注,再导出为矢量格式,最后转换为labelme的json格式,但这流程更复杂。
5.2 地物边界模糊或存在阴影
问题 :建筑物阴影投射在道路上,或者水体边界因为反光而不清晰,导致标注时难以判断精确边界。 解决策略 :
- 多源辅助判断 :如果有时间序列影像,可以查看不同时相的图,帮助判断边界。例如,阴影位置会随时间变化,但建筑物边界不会。
- 参考更高分辨率数据 :如果条件允许,可以购买或寻找0.5米甚至更高分辨率的商业卫星影像(如WorldView)作为参考。
- 制定规则 :在标注规范中明确这类情况的处理方式。例如,“建筑物边界以屋顶投影到地面的 可见边缘 为准,忽略阴影部分”;“水体边界以水陆分界线的 平均位置 为准”。
-
保持一致性
:最重要的是,所有模糊边界的处理方式在整个数据集中要保持一致。可以保留少量“不确定”区域,将其标注为
__ignore__(如果转换脚本支持),这样模型在训练时会忽略这些区域。
5.3 类别不平衡与样本量不足
问题 :城市影像中“建筑”和“道路”的像素可能占80%,而“水体”只占5%。这会导致模型严重偏向多数类。 解决策略 :
-
数据层面
:
- 过采样 :在划分训练集时,刻意多选择一些包含稀有类别的图块。
- 数据增强 :对包含稀有类别的图块进行更强烈的数据增强(旋转、翻转、色彩抖动等),生成更多变体。
- 专门采集 :如果水体是你的关键研究目标,可以专门寻找包含湖泊、河流的影像区域进行标注,补充到数据集中。
-
算法层面
:
- 损失函数 :使用加权交叉熵损失(Weighted Cross-Entropy Loss)或Dice Loss、Focal Loss等对类别不平衡不敏感的损失函数。在损失计算时,给稀有类别赋予更高的权重。
- 评估指标 :不要只看整体的准确率(Accuracy),更要关注每个类别的交并比(IoU)或F1分数,特别是稀有类别的。
5.4 标注一致性检验与质量控制
问题 :自己标注时间长了会疲劳,多人标注标准可能不统一。 质量控制流程 :
- 规范文档 :一份详细的、图文并茂的标注规范文档是基石,必须人手一份,并定期讨论更新。
- 试标与校准 :项目开始前,所有人对同一小块区域进行标注,然后开会讨论差异点,统一认识,修正规范。
- 中间检查 :标注完成30%时,进行一轮集中检查。随机抽取每人一定数量的样本,由负责人或交叉检查,记录错误类型和频率。
- 工具辅助 :可以使用一些简单的脚本计算标注的一致性指标,如两个人对同一张图的标注之间的IoU。
- 最终验收 :全部完成后,再次抽样检查。只有通过验收的标注才能进入最终数据集。
6. 数据集制作后的下一步:模型训练初步准备
数据集做好后,你已经完成了最艰巨的80%的工作。在投入模型训练前,还有几个重要的准备步骤。
1. 创建数据加载器
:
你需要编写一个PyTorch的
Dataset
类或TensorFlow的
tf.data
管道,来读取
images/train
和
masks/train
下的文件对。这个类的主要工作是:
- 读取图像和掩码。
- 进行实时的数据增强(如随机裁剪、翻转、旋转、亮度对比度调整)。
- 将图像像素值归一化(如除以255.0缩放到[0,1]),将掩码图转换为类别索引的张量。
-
返回
(image_tensor, mask_tensor)对。
2. 划分训练集与验证集
:
确保划分是随机的,但也要保证验证集中包含所有类别的样本。可以使用
scikit-learn
的
train_test_split
函数。
3. 计算类别权重
:
如果你打算使用加权损失函数,需要根据训练集掩码统计每个类别的像素频率。权重通常与频率成反比,例如使用中值频率平衡:
weight = median_freq / class_freq
。
4. 选择与适配模型 : 对于遥感语义分割,U-Net及其变体(如Attention U-Net)是经典且有效的起点。DeepLabv3+在处理大尺度上下文信息方面表现优异。近年来,基于Transformer的模型(如SegFormer)也展现出强大性能。你可以从这些模型入手,将数据加载器接入,开始你的训练迭代。
从一张原始的GF-2卫星影像,到一份规整的、可用于训练深度学习模型的数据集,这个过程确实充满了细节和挑战。但每一步都有其明确的目的和可操作的方法。我个人的体会是,前期在数据预处理和标注规范上多花一小时,后期在模型调试上可能就能省下一天。标注数据虽然枯燥,但它是整个AI项目的基石,基石不稳,后面的大楼再华丽也容易倒塌。当你第一次用自己制作的数据集训练出一个能准确识别出图中建筑物的模型时,那种成就感是完全不一样的。最后再分享一个小技巧:在标注过程中,定期备份你的
labelme
的
.json
文件到云端或另一块硬盘,你永远不知道意外和手滑哪个先来。

842

被折叠的 条评论
为什么被折叠?



