在深度学习中,数据集格式转换是一个绕不开的准备工作。本文分享一个完整的 Python 脚本,将 LabelMe 格式的图像分割数据 (.json
标注)转换为 PaddleSeg 所需的格式。支持多类别、多边形标注、自动生成彩色掩码和配置文件,适用于分割任务的快速部署和训练准备。
✅ 项目背景
在使用 LabelMe 进行语义分割标注后,默认输出是 .json
文件,然而 PaddleSeg 框架训练模型需要 .jpg/.png
图像和灰度掩码对。本脚本帮助你:
-
自动生成灰度掩码图(每类一个灰度值)
-
可选生成彩色掩码图用于可视化
-
创建训练集 / 验证集划分列表
-
自动生成类别信息与数据集配置文件
📂输入目录结构(LabelMe格式)
input_dir/
├── image/
│ ├── xxx.jpg
│ └── ...
└── labels/
├── xxx.json
└── ...
🗂输出目录结构(PaddleSeg格式)
output_dir/
├── images/ ← 图像副本
├── labels/ ← 灰度标签图(掩码)
├── colored_masks/ ← 可视化彩色掩码(可选)
├── train_list.txt ← PaddleSeg训练文件列表
├── val_list.txt ← PaddleSeg验证文件列表
├── class_info.json ← 类别定义与颜色说明
└── dataset_config.json ← PaddleSeg数据集配置文件
🔨支持特性
-
✅ 多类分割支持(自定义类别映射)
-
✅ 多边形标注自动转换为掩码
-
✅ 彩色掩码图自动生成
-
✅ 自动划分训练/验证集
-
✅ 输出JSON格式的类别定义与PaddleSeg配置
📌主要函数说明
1. convert_labelme_to_paddleseg(...)
核心转换函数,读取图像和标注,生成掩码和文件结构。
2. create_mask_from_polygons(...)
将LabelMe中多边形标注转换为灰度掩码图。
3. create_colored_mask(...)
按类别ID生成RGB彩色掩码(用于可视化)。
4. create_train_val_split(...)
自动划分训练集和验证集,输出 PaddleSeg 需要的 .txt
文件列表。
🧠类别定义与颜色映射(可修改)
python
class_mapping = {
'bp': 1, # 红色
'kd': 2 # 绿色
}
color_map = {
0: [0, 0, 0], # 背景 - 黑色
1: [255, 0, 0], # bp - 红色
2: [0, 255, 0], # kd - 绿色
}
🖥使用示例(修改路径后直接运行)
python
if __name__ == "__main__":
input_dir = r"C:\Users\WIN10\Desktop\seg_change\bp"
output_dir = r"c:\Users\WIN10\Desktop\seg_change\paddleseg_bp"
class_mapping = {
'bp': 1,
'kd': 2
}
convert_labelme_to_paddleseg(input_dir, output_dir, class_mapping, generate_colored_masks=True)
create_train_val_split(output_dir, train_ratio=0.8)
🎉运行结果
运行后终端会输出:
转换完成!
成功处理: 123 个文件
错误: 0 个文件
输出目录: paddleseg_bp
训练集: 98 个样本
验证集: 25 个样本
📦完整代码
完整代码:
python
import os
import json
import cv2
import numpy as np
from PIL import Image, ImageDraw
import shutil
from pathlib import Path
def load_labelme_json(json_path):
"""加载LabelMe JSON文件"""
try:
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
except Exception as e:
print(f"加载JSON文件失败 {json_path}: {e}")
return None
def create_mask_from_polygons(image_shape, shapes, class_mapping):
"""从多边形创建分割掩码"""
height, width = image_shape[:2]
mask = np.zeros((height, width), dtype=np.uint8)
for shape in shapes:
if shape['shape_type'] == 'polygon':
label = shape['label']
if label in class_mapping:
class_id = class_mapping[label]
points = shape['points']
# 转换为PIL可用的格式
polygon_points = [(int(x), int(y)) for x, y in points]
# 创建PIL图像用于绘制多边形
pil_image = Image.fromarray(mask)
draw = ImageDraw.Draw(pil_image)
draw.polygon(polygon_points, fill=class_id)
mask = np.array(pil_image)
return mask
def create_colored_mask(mask, class_mapping, color_map=None):
"""创建彩色掩码用于可视化"""
if color_map is None:
# 默认颜色映射
color_map = {
0: [0, 0, 0], # 背景 - 黑色
1: [255, 0, 0], # bp - 红色
2: [0, 255, 0], # kd - 绿色
}
height, width = mask.shape
colored_mask = np.zeros((height, width, 3), dtype=np.uint8)
for class_id, color in color_map.items():
colored_mask[mask == class_id] = color
return colored_mask
def convert_labelme_to_paddleseg(input_dir, output_dir, class_mapping=None, generate_colored_masks=True):
"""将LabelMe格式转换为PaddleSeg格式"""
if class_mapping is None:
# 默认类别映射:背景=0, bp=1, kd=2
class_mapping = {'bp': 1, 'kd': 2}
input_path = Path(input_dir)
output_path = Path(output_dir)
# 创建输出目录
images_dir = output_path / 'images'
labels_dir = output_path / 'labels'
images_dir.mkdir(parents=True, exist_ok=True)
labels_dir.mkdir(parents=True, exist_ok=True)
# 如果需要生成彩色掩码,创建彩色掩码目录
if generate_colored_masks:
colored_masks_dir = output_path / 'colored_masks'
colored_masks_dir.mkdir(parents=True, exist_ok=True)
# 获取所有图像和标注文件
image_dir = input_path / 'image'
label_dir = input_path / 'labels'
if not image_dir.exists() or not label_dir.exists():
print(f"错误:找不到图像目录 {image_dir} 或标注目录 {label_dir}")
return
# 获取所有图像文件
image_files = list(image_dir.glob('*.jpg')) + list(image_dir.glob('*.png'))
processed_count = 0
error_count = 0
# 定义颜色映射
color_map = {
0: [0, 0, 0], # 背景 - 黑色
1: [255, 0, 0], # bp - 红色
2: [0, 255, 0], # kd - 绿色
}
for image_file in image_files:
try:
# 对应的JSON文件
json_file = label_dir / f"{image_file.stem}.json"
if not json_file.exists():
print(f"警告:找不到对应的标注文件 {json_file}")
continue
# 加载图像
image = cv2.imread(str(image_file))
if image is None:
print(f"错误:无法加载图像 {image_file}")
error_count += 1
continue
# 加载标注
labelme_data = load_labelme_json(json_file)
if labelme_data is None:
error_count += 1
continue
# 创建掩码
mask = create_mask_from_polygons(
image.shape,
labelme_data.get('shapes', []),
class_mapping
)
# 保存图像和掩码
output_image_path = images_dir / image_file.name
output_mask_path = labels_dir / f"{image_file.stem}.png"
# 复制图像
shutil.copy2(image_file, output_image_path)
# 保存灰度掩码
cv2.imwrite(str(output_mask_path), mask)
# 生成并保存彩色掩码
if generate_colored_masks:
colored_mask = create_colored_mask(mask, class_mapping, color_map)
colored_mask_path = colored_masks_dir / f"{image_file.stem}_colored.png"
cv2.imwrite(str(colored_mask_path), cv2.cvtColor(colored_mask, cv2.COLOR_RGB2BGR))
processed_count += 1
if processed_count % 50 == 0:
print(f"已处理 {processed_count} 个文件...")
except Exception as e:
print(f"处理文件时出错 {image_file}: {e}")
error_count += 1
print(f"\n转换完成!")
print(f"成功处理: {processed_count} 个文件")
print(f"错误: {error_count} 个文件")
print(f"输出目录: {output_path}")
# 创建类别信息文件
create_class_info_file(output_path, class_mapping, color_map)
# 创建数据集配置文件
create_dataset_config(output_path, processed_count, len(class_mapping) + 1)
def create_class_info_file(output_dir, class_mapping, color_map=None):
"""创建类别信息文件"""
class_info = {
'num_classes': len(class_mapping) + 1, # +1 for background
'class_mapping': {'background': 0, **class_mapping},
'class_names': ['background'] + list(class_mapping.keys())
}
# 添加颜色映射信息
if color_map:
class_info['color_mapping'] = color_map
class_info['color_description'] = {
0: 'background - black',
1: 'bp - red',
2: 'kd - green'
}
class_info_path = output_dir / 'class_info.json'
with open(class_info_path, 'w', encoding='utf-8') as f:
json.dump(class_info, f, ensure_ascii=False, indent=2)
print(f"类别信息已保存到: {class_info_path}")
def create_dataset_config(output_dir, total_samples, num_classes):
"""创建数据集配置文件"""
config = {
'dataset_info': {
'total_samples': total_samples,
'image_dir': 'images',
'label_dir': 'labels',
'colored_mask_dir': 'colored_masks',
'image_format': 'jpg',
'label_format': 'png'
},
'paddleseg_config': {
'dataset_root': str(output_dir),
'train_dataset': {
'type': 'Dataset',
'dataset_root': str(output_dir),
'train_path': 'train_list.txt',
'num_classes': num_classes,
'transforms': [
{'type': 'ResizeStepScaling', 'min_scale_factor': 0.5, 'max_scale_factor': 2.0, 'scale_step_size': 0.25},
{'type': 'RandomPaddingCrop', 'crop_size': [512, 512]},
{'type': 'RandomHorizontalFlip'},
{'type': 'RandomDistort'},
{'type': 'Normalize'}
]
},
'val_dataset': {
'type': 'Dataset',
'dataset_root': str(output_dir),
'val_path': 'val_list.txt',
'num_classes': num_classes,
'transforms': [
{'type': 'Normalize'}
]
}
}
}
config_path = output_dir / 'dataset_config.json'
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=2)
print(f"数据集配置已保存到: {config_path}")
def create_train_val_split(output_dir, train_ratio=0.8):
"""创建训练和验证集分割文件"""
images_dir = Path(output_dir) / 'images'
labels_dir = Path(output_dir) / 'labels'
# 获取所有图像文件
image_files = list(images_dir.glob('*.jpg')) + list(images_dir.glob('*.png'))
# 随机打乱
np.random.shuffle(image_files)
# 分割
split_idx = int(len(image_files) * train_ratio)
train_files = image_files[:split_idx]
val_files = image_files[split_idx:]
# 创建训练集列表
train_list_path = Path(output_dir) / 'train_list.txt'
with open(train_list_path, 'w') as f:
for img_file in train_files:
label_file = labels_dir / f"{img_file.stem}.png"
if label_file.exists():
f.write(f"images/{img_file.name} labels/{label_file.name}\n")
# 创建验证集列表
val_list_path = Path(output_dir) / 'val_list.txt'
with open(val_list_path, 'w') as f:
for img_file in val_files:
label_file = labels_dir / f"{img_file.stem}.png"
if label_file.exists():
f.write(f"images/{img_file.name} labels/{label_file.name}\n")
print(f"训练集: {len(train_files)} 个样本")
print(f"验证集: {len(val_files)} 个样本")
print(f"训练集列表: {train_list_path}")
print(f"验证集列表: {val_list_path}")
def main():
# 输入和输出路径
input_dir = r"C:\Users\WIN10\Desktop\seg_change\bp"
output_dir = r"c:\Users\WIN10\Desktop\seg_change\paddleseg_bp"
# 类别映射(支持多类分割)
class_mapping = {
'bp': 1, # bp类别映射为1
'kd': 2 # kd类别映射为2,背景为0
}
print("开始转换LabelMe数据为PaddleSeg格式...")
print(f"输入目录: {input_dir}")
print(f"输出目录: {output_dir}")
print(f"类别映射: {class_mapping}")
# 执行转换(启用彩色掩码生成)
convert_labelme_to_paddleseg(input_dir, output_dir, class_mapping, generate_colored_masks=True)
# 创建训练验证集分割
print("\n创建训练验证集分割...")
create_train_val_split(output_dir, train_ratio=0.8)
print("\n转换完成!数据集已准备好用于PaddleSeg训练。")
print("\n使用说明:")
print("1. 图像文件保存在: paddleseg_dataset/images/")
print("2. 标签掩码保存在: paddleseg_dataset/labels/")
print("3. 彩色掩码保存在: paddleseg_dataset/colored_masks/")
print("4. 训练集列表: paddleseg_dataset/train_list.txt")
print("5. 验证集列表: paddleseg_dataset/val_list.txt")
print("6. 类别信息: paddleseg_dataset/class_info.json")
print("7. 数据集配置: paddleseg_dataset/dataset_config.json")
print("\n类别说明:")
print("- 背景: 0 (黑色)")
print("- bp标签: 1 (红色)")
print("- kd标签: 2 (绿色)")
if __name__ == "__main__":
main()
💡结语
本脚本适用于:
-
PaddleSeg语义分割模型训练前数据准备
-
多类别图像分割任务标注转换
-
可视化检查和配置文件自动化生成
欢迎交流指正,若对你有帮助请点个赞 👍 或收藏 🌟,后续我会分享 PaddleSeg 训练流程 + 推理部署代码。