[特殊字符]LabelMe标注转PaddleSeg数据集:多类掩码自动生成+配置文件输出(附完整Python脚本)

在深度学习中,数据集格式转换是一个绕不开的准备工作。本文分享一个完整的 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 训练流程 + 推理部署代码。