labelme标注转YOLOv8语义分割:批量转换工具与避坑指南
发布时间:2026/10/7 5:25:40 作者:尧图编辑部 阅读量:1,286

简介基于Python的Labelme标注转YoloV8语义分割数据集工具面向计算机视觉领域的算法工程师、科研人员及高校学生解决图像标注完成后难以直接用于YoloV8训练的问题。工具能批量转换Labelme JSON标注为YoloV8语义分割格式并自动划分训练集与验证集大幅节省手动整理时间。压缩包共14个文件总体积仅1.95MB包含5个JSON标注文件、6张JPEG/JPG示例图像、2个Python脚本及1份Markdown使用说明文件构成完整便于对照学习。工具会解析JSON中的多边形坐标生成YoloV8可用的标签结构转换逻辑清晰适合快速验证。目前已有71人浏览学习。代码经过严格测试可直接用于课程设计、毕业设计或科研项目尤其适合自动化、电子信息、人工智能等专业学生支持二次开发可帮助初学者理解Labelme与YoloV8数据集映射关系。对于刚入门语义分割的开发者可参照使用说明快速上手省去重新编写转换脚本的时间有经验的研究者则能按需修改代码适配不同标注项目。1. labelme标注转YOLOv8语义分割为什么这一步卡住了大多数人用过labelme做过多边形标注的人都有个共识标注一时爽转换火葬场。Labelme保存的json里每一笔都是像素坐标而YOLOv8语义分割要的是归一化后的多边形坐标txt二者格式完全不在一个频道上。我在帮朋友处理遥感图像分割项目时就卡在手动转换上——几十张图还能用在线工具凑合一旦超过两百张手改坐标根本不可能。这份基于Python的转换工具就是把labelme的json批量转成YOLOv8能直接吃的分割数据集并且顺手完成训练集和验证集的自动划分。适合那些已经用labelme标完数据、正准备训yolov8语义分割模型的人也适合课程设计和毕设里需要把标注数据落地的场景。核心就两个脚本convert_folder.py负责批量转换train_example.py给你示范怎么把转换结果喂给yolov8训练。2. 转换脚本的核心逻辑从labelme的json到YOLOv8的txt2.1 两种格式的本质差异Labelme的标注文件是json里面有一个shapes数组每个元素包含label标签和points多边形顶点坐标。这个坐标是像素级的直接对应原图的尺寸。而YOLOv8语义分割的数据集格式要求每个图像对应一个同名的txt文件放在labels文件夹里txt每行代表一个目标实例格式为class_id x1 y1 x2 y2 ...所有坐标点必须归一化到0到1之间即用像素坐标除以图像的宽和高。这里有个关键点YOLOv8分割格式跟目标检测的格式很相似但它的点数是不固定的因为每个多边形的顶点数量不同。而labelme的points也是可变数量的所以转换时只需要把每个点循环除以宽高然后按顺序拼接即可。此外labelme里一个类别可能出现多次比如一张图里有三个人就会有三条记录每条记录对应一个多边形。转换脚本需要把每个独立的shapes元素都转换成txt中的一行而不是合并。理解了格式差异再看代码就不会晕。脚本里最核心的函数大致逻辑如下def convert_labelme_to_yolo(json_path, output_txt_path, class_dict, img_width, img_height): with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for shape in data[shapes]: label shape[label] if label not in class_dict: # 常见做法是跳过未定义类别避免训练时类别错乱 continue class_id class_dict[label] points shape[points] normalized_points [] for x, y in points: nx min(max(x / img_width, 0.0), 1.0) # 强制裁剪到[0,1] ny min(max(y / img_height, 0.0), 1.0) normalized_points.extend([f{nx:.6f}, f{ny:.6f}]) line f{class_id} .join(normalized_points) \n lines.append(line) with open(output_txt_path, w, encodingutf-8) as f: f.writelines(lines)这里用了min(max(...))把坐标强制限制在0到1之间。原因是我见过不少labelme标注时手抖点到了图像外边缘如果直接归一化会得到负数或大于1的值YOLOv8训练时会报错或者生成退化边框。做这个裁剪虽然会丢失一点边界信息但总比训练中途崩溃好。注意class_dict是类别名到整数ID的映射。这个映射必须在转换前固定下来否则第二次转换时类别顺序变了训练集和验证集的标签就对不上了。我一般习惯单独保存一个classes.txt第一行对应id 0第二行对应id 1以此类推。2.2 坐标归一化与类别映射的细节很多人拿到脚本后直接跑结果生成的txt里坐标全是一串奇怪的数字或者类别ID全是0。问题往往出在两点一是图像尺寸读取方式二是类别映射的构建。转换时有两个思路获取图像尺寸import cv2 # 方式一直接从原图文件读取 img cv2.imread(image_path) img_height, img_width img.shape[:2] # 方式二从json的imageHeight和imageWidth字段读取 img_height data[imageHeight] img_width data[imageWidth]两种写法我都用过。从json读取更快因为不用额外加载图片但有个隐患如果标注后图片被裁剪或缩放json里的尺寸就和实际不一致了导致坐标偏。从原图读取最保险不过会稍微慢一些。类别映射的推荐做法是写一个固定顺序的字典class_dict { person: 0, car: 1, background: 2, # 注意顺序一旦定了就别改 }如果类别很多也可以用列表自动生成class_names [person, car, background] class_dict {name: idx for idx, name in enumerate(class_names)}但这个方法要求class_names的顺序在你的训练脚本里保持一致。yolov8官方训练时读取的是数据集配置yaml里的names列表顺序错一个整个训练就成玄学了。2.3 训练集和验证集自动划分的实现转换脚本除了生成labels还要把原始图片和txt分到训练集和验证集。常见做法是先用glob找出所有json随机打乱按比例切分然后建立对应的目录结构。这一步如果手动做几百个文件要拖半天脚本几秒搞定。import glob import os import random def split_dataset(json_files, train_ratio0.8, seed42): random.seed(seed) # 固定随机种子保证结果可复现 random.shuffle(json_files) split_idx int(len(json_files) * train_ratio) train_files json_files[:split_idx] val_files json_files[split_idx:] return train_files, val_files参数说明train_ratio默认0.8表示80%数据用于训练20%用于验证。seed默认42这个值很重要——如果你不固定种子每次运行划分结果不同会导致上次测试过的那几张图下次跑到训练集里模型验证结果失去可比性。我通常把seed也做成命令行参数方便复现实验。划分后目录结构建议这样datasets/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── dataset.yamlYOLOv8训练时只会去找images/train和labels/train它要求labels的路径是把images路径里的images替换成labels。这个约定虽然死板但能避免很多路径混乱的问题。3. 动手跑通convert_folder.py与train_example.py的完整使用流程3.1 环境准备与文件结构先把项目文件解压你会看到convert_folder.py、train_example.py、examples文件夹和使用说明.md。这个资源里带了一组example1到example5的json和jpg正好用来验证转换流程。在跑任何代码之前先确认Python环境建议Python 3.8以上用conda建一个干净环境conda create -n labelme_yolo python3.8 conda activate labelme_yolo pip install opencv-python pillow numpy labelme ultralytics这里装ultralytics是为了最后用yolov8训练如果你只想转换可以不装。labelme库其实不需要因为我们是读取json而不是标注但装上也不碍事。打开使用说明.md里面应该有资源作者写的操作步骤。我建议你先跑通内置examples再转到自己的数据。文件结构里examples下有成对的json和jpegdemo.jpg是一张汇总演示图。3.2 转换命令与参数详解先看下convert_folder.py的命令行入口我根据自己的习惯整理了一下运行方式python convert_folder.py --json_dir ./examples \ --img_dir ./examples \ --output_dir ./yolo_dataset \ --classes person car background \ --train_ratio 0.8 \ --seed 42参数说明--json_dir存放labelme json文件的目录。--img_dir存放原始图片的目录。如果json里的imagePath写的是相对路径脚本会用这个目录补全图片路径。--output_dir转换后数据集的输出目录脚本会自动创建images/train、images/val、labels/train、labels/val。--classes按空格分隔的类别名称顺序就是ID顺序。注意这里的顺序必须和你的语义分割类别定义一致。--train_ratio训练集比例默认0.8。如果你的数据量很小可以设0.7甚至0.6但注意验证集太大可能训不好。--seed随机种子用于复现划分。执行后屏幕会打印每张图的转换状态比如Processed: example1.json - labels/train/example1.txt。如果中间有json读取失败脚本不建议用continue跳过——我遇到过跳过问题json导致后续图片和标签错位的情况。更稳妥的方式是失败就中断先修数据再转换。转换完成后检查输出目录tree yolo_dataset --dirsfirst正常你会看到yolo_dataset/ ├── dataset.yaml ├── images │ ├── train │ │ ├── example1.jpg │ │ └── ... │ └── val │ └── ... └── labels ├── train │ ├── example1.txt │ └── ... └── val └── ...同时还会生成一个dataset.yaml内容是yolov8训练需要的配置。我习惯检查一下这个yaml确认names顺序和路径正确。3.3 训练脚本的实际用法train_example.py是配套的训练示例它本质上就是对ultralytics接口的封装。核心代码大概长这样from ultralytics import YOLO def main(): model YOLO(yolov8n-seg.pt) # 使用预训练的分割模型 model.train( datayolo_dataset/dataset.yaml, epochs100, imgsz640, batch8, devicecuda, # 没有GPU就改成cpu workers4, seed42 )参数说明imgsz是训练输入尺寸一般用640如果你的目标很小可以设1024但显存占用会翻倍。batch根据显卡内存调整GTX 1660 Ti 这种6GB卡建议batch为4或8。workers是数据加载线程数Windows上建议设0否则容易报DataLoader worker崩溃。直接用这个脚本跑examples数据集总共就5张图训练结果肯定没有意义但流程能跑通。你可以在终端看到类似fitness、mAP50-95的指标输出这说明数据集格式没问题。确认格式正确后再把脚本里的data路径指向你自己的数据集把epochs改大。3.4 从examples到自己数据的迁移要点很多人把examples跑通后直接把convert_folder.py用到自己的项目里结果发现生成的txt全是空的或者报错。原因通常在于自己的json里有一些特殊元素。我遇到过的情况labelme标注的时候不小心用了矩形框而不是多边形或者画完多边形忘了选类别标签。这时可以写个小脚本检查所有json的格式import json, glob for path in glob.glob(my_data/*.json): with open(path, encodingutf-8) as f: data json.load(f) for shape in data[shapes]: assert shape[shape_type] polygon, f{path} 里的 shape_type 是 {shape[shape_type]} assert shape[label], f{path} 里有一个未命名的多边形 # 顺便检查图像尺寸字段 if imageWidth not in data: print(f警告: {path} 缺少 imageWidth 字段)shape_type有两种polygon和rectangle。如果你的标注里混入了rectangle转换脚本必须单独处理因为矩形只有左上和右下两个点不能直接当成多边形点序列。建议回到labelme里把矩形改成多边形或者修改脚本兼容矩形。我一般倾向于重新标注因为矩形在语义分割里边界太粗糙。4. 避坑指南转换语义分割数据集时最常见的5个坑4.1 图像路径错乱导致找不到图片现象转换脚本报错FileNotFoundError: example1.jpg但json文件明明在同目录。原因labelme的json里记录的是标注时图片的绝对路径或者某个相对路径换电脑后这个路径就失效了。若json里的imagePath是C:/Users/xxx/Desktop/data/example1.jpg脚本直接拼接就会出错。解决统一用--img_dir参数并在脚本里使用os.path.basename(json[imagePath])来获取文件名然后跟--img_dir拼接。如果json里存的路径带了子目录最好把图片全放在同一个目录里或者自己写个路径修正函数。4.2 类别ID顺序前后不一致现象训练时loss降不下去或者验证集mAP特别低输出里预测的类别和标注对不上。原因你用了--classes a b c转换后来又用--classes c b a转换两个批次的数据集类别ID完全错位。yolov8训练时是根据dataset.yaml里的names顺序分配ID的转换脚本里的顺序必须和yaml保持一致。解决我现在的习惯是把类别顺序写死在classes.txt里转换脚本读取这个文件生成ID训练yaml也引用这个文件。还要在转换脚本里加一个断言如果json里出现了classes.txt中不存在的类别名直接报错而不是悄悄跳过。4.3 多边形坐标超出图像边界现象生成的txt里有负数坐标或大于1的值训练时提示RuntimeError: invalid polygon。原因标注时鼠标拖着多边形边缘到了图像外侧或者放大缩小画布时误操作。尽管脚本里用min/max裁剪了但裁剪后的多边形可能退化成一条线或零面积。解决转换前先用OpenCV绘制每个多边形看看有明显越界的直接删掉该标注。更彻底的做法是在转换脚本里增加面积过滤比如cv2.contourArea(points)小于某个阈值就跳过这条记录。阈值我常用的是图像面积乘以0.0001太小的多边形多半是误标注。4.4 图片是单通道或带透明度导致读图崩溃现象转换时cv2.imread返回None或者训练时图片通道报错。原因有些遥感影像或医学图像是灰度图单通道还有的是PNG带alpha通道四通道。cv2.imread默认读成三通道但遇到特殊格式直接读取失败。解决转换脚本里加一个兜底函数def load_image_safe(path): img cv2.imread(path, cv2.IMREAD_UNCHANGED) if img is None: raise ValueError(f无法读取图片: {path}) if len(img.shape) 2: img cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) elif img.shape[2] 4: img cv2.cvtColor(img, cv2.COLOR_BGRA2BGR) return img注意转换到YOLOv8的txt只需要图像的宽和高所以其实不需要把图片真的转换颜色空间。但如果你在脚本里要显示或检查图片这个函数就很有用。4.5 训练集和验证集划分没有固定种子导致结果不可复现现象同一份数据跑两次训练最后一个epoch的指标完全不一样甚至验证集里的图片都不同。原因脚本每次运行都生成新的随机序列忘了设置random.seed。解决把--seed参数写死在命令行里并在代码开头用random.seed(seed)和np.random.seed(seed)固定所有随机源。另外如果划分后你又增删了json文件最好重新划分整个数据集不要保留旧的划分文件再新增那样会让同名的图片在训练和验证里同时出现导致数据泄漏。5. 进阶技巧用可视化脚本快速确认转换结果再开训转换完不等于万事大吉我见过太多人拿错误的txt去训练跑了两天损失爆炸才发现是标签坐标原始值没归一化。所以我强烈建议在训练前做一次可视化验证把YOLO格式的txt还原成多边形画到原图上。这个步骤比看任何训练曲线都直接。你可以写一个验证脚本读取txt每行的类别ID和坐标点乘以图像宽高还原像素坐标然后用cv2.polylines画在原图上import cv2 import numpy as np def draw_yolo_seg_txt(txt_path, img_path, class_names, output_path): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path, r) as f: lines f.readlines() for line in lines: parts line.strip().split() class_id int(parts[0]) points np.array([[float(parts[i]) * w, float(parts[i1]) * h] for i in range(1, len(parts)-1, 2)], dtypenp.int32) color (0, 255, 0) cv2.polylines(img, [points], isClosedTrue, colorcolor, thickness2) cv2.putText(img, class_names[class_id], tuple(points[0]), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1) cv2.imwrite(output_path, img)使用的时候随便挑训练集和验证集各一张图传入对应的txt和图像路径。如果画出来的多边形边界跟原图标注物完全贴合说明转换正确。我还要再检查一个细节确认txt里的坐标点顺序没有反转。YOLOv8要求的点顺序没有硬性规定但最好所有实例都是顺时针或都在同一方向否则某些增强策略下多边形会自相交。从那次之后我每次转换完数据集都会强制走一遍可视化抽查至少看三张训练图和三张验证图。多花五分钟能省下后面几十次因标签错误导致的无效训练。这份脚本的干净之处在于convert和train解耦你可以只用它生成标准格式再套进自己的yolov8环境。希望帮到你要不要下载下来先跑通examples试试本文还有配套的精品资源点击获取