news 2026/9/12 10:29:18

LabelMe标注格式详解:从JSON到VOC/COCO/YOLO的训练数据转换实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LabelMe标注格式详解:从JSON到VOC/COCO/YOLO的训练数据转换实战

简介:LabelMe是由MIT开发的开源图像标注工具,这个压缩包包含其完整源码与配套文件,面向计算机视觉研究者、深度学习开发者及数据标注人员,用于高效制作语义分割、目标检测与关键点检测等任务所需的标注数据集。包内共251个文件,压缩后约12.4MB,以45个Python源码与依赖脚本为核心,搭配75张jpg和48张png示例图像、23个json标注结果、14个npy数据文件、9个md说明文档,还有yml/yaml/Dockerfile等环境配置与容器化部署支持,以及图标、桌面入口等辅助资源,目录结构清晰,便于本地安装和二次开发。随包提供的labelme命令行工具支持polygon、box、point三种标注方式,可自由绘制多边形、边界框和关键点,并附带labelme2voc.py、labelme2coco.py等转换脚本,直接将标注结果转为PASCAL VOC或COCO格式,无缝对接Mask R-CNN、YOLO、U-Net等主流模型训练流程。已有1072人学习使用该资源,适合需要快速搭建标注环境、系统掌握数据制作全流程的读者,可为深度学习项目构建高质量数据集。

1. LabelMe 不只是画框工具:标注格式的取舍决定模型上限

做计算机视觉的工程师基本都经历过同一个阶段:公开数据集跑通模型之后,面对自己的业务场景,必须从零开始构建标注数据。市面上的标注工具不少,商业化产品带 AI 预标注、云协作和自动推送,但 GitHub 上 LabelMe 的下载量和实际使用量依旧排在前面。原因不在界面,而在输出格式——一个标注结果就是一个 JSON 文件,顶层字段就shapesimagePathimageData这几个,没有数据库,没有权限模型,坐标直接写在数组里。这意味着可以徒手写脚本做批量修改、格式转换,甚至在训练代码里直接解析。按安装、标注、格式转换到接入训练管线的顺序,来看实际项目里怎么用、坑在哪、参数怎么调。

2. 环境搭建与标注工具链的选型细节

2.1 Python 虚拟环境、依赖安装与开发模式的选择

LabelMe 通过 PyPI 分发,一条命令就能装,但直接装进全局环境不是好习惯。先隔离虚拟环境,避免和项目里已有的 PyQt、opencv-python 版本打架:

# 创建并激活虚拟环境 python -m venv labelme_env source labelme_env/bin/activate # Windows 下换成 labelme_env\Scripts\activate # 从 PyPI 安装稳定版 pip install labelme # 查看依赖树 pip show labelme

安装完成后环境里会多出 labelme 可执行文件,同时自动拉入 PyQt5、numpy、Pillow、imgviz 等依赖。imgviz 是一个容易被忽略的库,负责标注结果的图像渲染,包括多边形轮廓、分割掩膜配色等。后续做批量可视化校验时,可以直接在脚本里import imgviz,渲染出来的结果和 GUI 里看到的基本一致,方便做自动化预览。

如果需要在源码层面做定制,比如新增一种自定义标注形状,建议从 GitHub 拉取源码后执行pip install -e .。开发模式安装让代码改动即时生效,不用反复重新安装。有一点要注意:GUI 依赖 PyQt 事件循环,在没有显示器的 Linux 服务器上,启动前需要设置QT_QPA_PLATFORM=offscreen,否则会提示could not connect to display。即便只是用脚本做格式转换,只要代码 import 了 labelme 的 GUI 模块,这个环境变量同样需要配置。

2.2 CLI 参数与数据目录组织

安装完成之后先别急着打开界面。建议在标注开始前就把数据目录和类别清单定义好,后面转换、训练才能一条路走到黑。

# 典型启动方式:指定类别文件、自动保存、不嵌入图像数据 labelme --labels labels.txt --autosave --nodata --output annotations
参数作用建议值
--labels指向类别清单文件,一行一个类别名类别名不要包含中文和空格,避免转换脚本出现编码问题
--nodata设置 JSON 中不保存 base64 图像数据建议开启,否则标注文件动辄几 MB
--autosave切换图片时自动保存当前标注批量标注时建议开启
--output指定 JSON 输出目录与图像目录分开,便于增量备份

--labels的作用是提供类别下拉框供选择,没有强制校验功能。如果标注时临时输入了类别清单之外的标签,LabelMe 也会正常保存,后续转换阶段才会暴露问题。为避免这种情况,通常把类别清单作为受控文件固定下来,转换脚本从同一个文件读取类别集合,保证两侧一致。这样出现标签拼写错误时,只需检查一个文件。

目录结构建议这样组织:

dataset/ ├── images/ # 原始图像 ├── annotations/ # LabelMe 输出的 JSON ├── labels.txt # 类别清单 ├── data_voc/ # VOC 格式输出 └── data_coco/ # COCO 格式输出

图像和标注分开存放,是因为两者生命周期不同:原始图像基本不变,标注文件会频繁修改。分开之后可以用增量同步单独处理标注目录,Git 版本管理也更容易追溯标注变更。

2.3 界面启动与首标操作

在同目录执行labelme images启动 GUI 后,左侧文件列表会列出目录下所有图片。用工具栏的 polygon、rectangle、point 工具开始标注。polygon 工具每单击一次落一个点,右键选择类别,回到第一个点闭合图形。rectangle 工具只需拖拽一次,保存格式为两个对角点,但要注意这两个点的拖拽顺序并不固定,后续解析时需要自行归一化。

一个常见误区是:标注完成后直接关掉窗口,但没有保存。如果没开--autosave,关闭当前图片时 LabelMe 会弹出保存确认,不要直接忽略。批量标注场景下,建议先标注 3~5 张图,打开 JSON 检查字段结构,确认无误后再继续,可以在早期拦截方向性错误。

3. JSON Schema 分工与三种标注类型的实现细节

3.1 顶层字段如何影响下游解析

当你在 LabelMe 里保存一个文件时,写出的是这样一个 JSON 文档:

{ "version": "5.3.1", "flags": {}, "shapes": [ { "label": "pedestrian", "points": [[552, 132], [614, 140], [661, 189], [553, 193]], "group_id": null, "shape_type": "polygon", "flags": {} } ], "imagePath": "frame_001.jpg", "imageData": null, "imageHeight": 720, "imageWidth": 1280 }

训练代码真正消费的核心是shapes数组和三个图像维度字段。shapes中的每一条记录对应一个标注对象,label是类别名,points是坐标点二维数组,shape_type决定 points 的解释方式,group_id用于把多个形状归到同一个实例,这在实例分割中很有用,比如同一个行人被多个多边形拼起来标注时,可以共享一个 group_id。

imageData字段比较特殊。默认情况下保存整张图的 base64 编码,体积很大。启动时加--nodata后,这个字段为null,下游解析可以走imagePath读取文件。无论哪种方式,imageHeightimageWidth都对应原始图像分辨率,坐标不会随 GUI 显示缩放发生变化。

3.2 polygon、rectangle、circle 的坐标语义

LabelMe 支持多种shape_type,不同形状的 points 语义差异明显,直接决定转换代码怎么写:

shape_typepoints 含义适用场景
polygon多边形顶点,按标注顺序连线语义分割、实例分割
rectangle仅 2 个点:两个对角点目标检测
circle2 个点:圆心和圆上一点圆形目标,如轮胎、细胞
line2 个点:线段端点车道线、血管中心线
point1 个点关键点检测

用 rectangle 标注时,points 里的两个对角点顺序不固定,和拖拽方向有关。稳妥的做法是解析时做归一化:xmin = min(points[0][0], points[1][0])ymin = min(points[0][1], points[1][1]),再算出xmaxymax。很多人在 YOLO 格式转换时报坐标越界或宽高为负,根因就在这。

circle 的半径需要根据第二个点计算欧氏距离,圆心的语义由第一个点给出。这类几何计算必须在转换阶段完成,不能直接套用 polygon 的遍历逻辑。

3.3 flags 与关键点标注的隐藏语义

flags字段有两层用途:顶层 flags 描述整张图像的属性,比如{"is_blurred": true};每个 shape 内部的 flags 用来存单个目标的属性,比如{"occluded": true}。GUI 里可以通过工具栏快速切换这些标记状态。

关键点标注场景中,真正重要的是shape_type: "point"。以人脸关键点为例,一张图上有几十个关键点,每个点作为独立 shape 输出,类别名会非常长。更常用的做法是利用group_id将属于同一目标的点绑定,再配合固定顺序的类别名解析。后续转换到 COCO keypoints 格式时,需要按 group_id 分组并按固定顺序重新排列点序列,这个排序决定了关键点索引到模型的映射关系,顺序一旦错乱,训练出的模型关键点位置就是乱的。

3.4 标注实施后的合法性校验脚本

标注完成后立刻跑一遍合法性检查,可以避免后期训练时才暴露数据问题。一个实用脚本如下:

import json, os, glob for json_path in glob.glob("annotations/*.json"): with open(json_path) as f: data = json.load(f) for shape in data["shapes"]: pts = shape["points"] if shape["shape_type"] == "polygon" and len(pts) < 3: print(f"错误: {json_path} 中 {shape['label']} 多边形点数不足") for x, y in pts: if x < 0 or y < 0 or x >= data["imageWidth"] or y >= data["imageHeight"]: print(f"越界: {json_path} 中坐标 {x},{y} 超出图像范围")

这段脚本检查两类最基础的问题:多边形点数不足和坐标越界。实际项目中还会加上类别名合法性检查、两个 shape 重叠面积占比检查。坐标越界时,VOC 或 COCO 的解析库默认会做裁剪,但裁剪后得到的是残缺掩膜,模型在这个区域的预测就没有可靠标签了。越界检查比想象中重要。

4. 从 JSON 到 VOC/COCO/YOLO:转换脚本的使用与改造

4.1 官方转换脚本的用法与输出结构

LabelMe 仓库的 examples 目录下提供了两个官方转换脚本,覆盖语义分割和目标检测的主流格式需求:

# 转换为 PASCAL VOC 格式 python labelme2voc.py annotations data_voc --labels labels.txt # 转换为 COCO 格式 python labelme2coco.py annotations data_coco --labels labels.txt

labelme2voc.py的输出目录结构如下:

data_voc/ ├── JPEGImages/ # 图像副本 ├── SegmentationClass/ # 按类别上色的可视化分割图 ├── SegmentationClassPNG/ # 单通道标注图,像素值=类别编号 ├── SegmentationObject/ # 实例分割图 ├── Visualization/ # 标注叠加可视化 ├── class_names.txt └── ...

关键点是SegmentationClassPNG里的像素值就是类别编号。如果 labels.txt 顺序是_background_personcar,那么 person 区域的像素值是 1,car 是 2。普通 PNG 位深为 8 位,类别数超过 255 时需要考虑其他编码方案。这里再次强调,labels.txt 的顺序一旦确定,训练前不要随意改动,否则所有已生成的掩膜都要重新转换。

4.2 转换中的高频错误定位

格式标签呈现方式适配的模型
VOC SegmentationClassPNG单通道像素值U-Net、DeepLab 等分割模型
COCO JSON多边形顶点列表加 bboxMask R-CNN、mmdetection、Detectron2
YOLO txt归一化中心坐标和宽高YOLOv5、YOLOv8 等检测模型

转 COCO 时有一个隐藏得很深的坑:COCO 对多边形要求闭合,而 LabelMe 的多边形默认不闭合,pycocotools 的某些版本在计算面积时对不闭合多边形有兼容逻辑,但 OpenMMLab 的严格校验模式会直接跳过面积计算失败的样本。表现症状是训练日志里 epoch 开始时提示某几张图没有 annotation。排查方法很简单,把 segmentation 的多边形输出通过 cv2.arcLength 检查闭合性。

转 VOC 方向也有一个常见问题:不要拿SegmentationClass彩色可视化图当训练标签。那张图是为人工查看设计的,里面的颜色是类别到 RGB 的映射,直接读入训练代码,分类损失会完全乱掉。训练请用SegmentationClassPNG

4.3 自定义脚本:LabelMe 转 YOLO txt

如果用 YOLOv8 训练自己的数据集,官方工具链里没有直接消费 LabelMe 格式的入口。可以让 LabelMe 数据直接转成 YOLO 的 txt:

import json, os, glob img_w, img_h = 1920, 1080 # 实际项目中从图像读取,不要硬编码 with open("labels.txt") as f: classes = [line.strip() for line in f.readlines()] for json_path in glob.glob("annotations/*.json"): with open(json_path) as f: data = json.load(f) txt_path = os.path.splitext(json_path)[0] + ".txt" with open(txt_path, "w") as out: for shape in data["shapes"]: if shape["shape_type"] != "rectangle": continue # 只处理矩形框 label = shape["label"] if label not in classes: print(f"警告: {json_path} 中存在未注册类别 {label}") continue cls_id = classes.index(label) x1, y1 = shape["points"][0] x2, y2 = shape["points"][1] x_center = (x1 + x2) / 2 / img_w y_center = (y1 + y2) / 2 / img_h w = abs(x2 - x1) / img_w h = abs(y2 - y1) / img_h out.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n")

这里有几个值得注意的细节。abs()保证宽高始终为正,避免拖拽方向不同带来的负宽高问题。归一化计算是先求中心坐标再除以图像宽高,不要先写x1 / img_w + x2 / img_w再除以 2,浮点误差会稍大。如果归一化后出现大于 1 或小于 0 的值,说明原始标注越界,需要回到第 3 章的校验脚本去修数据。

如果是做实例分割的 YOLOv5-seg,则不能只导出 box 信息,需要把 polygon 的顶点做归一化后,按<class> <x1> <y1> <x2> <y2> ...的顺序写入。顶点数量建议控制在 100 个以内,超过的话先用 Douglas-Peucker 抽稀,YOLOv5-seg 对超长序列的处理效率不高。

4.4 批量处理的性能要点

官方labelme2voc.py默认是单进程循环,一张张处理。几千张图时,大部分时间耗在 JSON 解码和掩膜填充上。简单改造法是把整个转换函数丢进 concurrent.futures.ProcessPoolExecutor,图像解码和 fillPoly 天然是 CPU 密集且相互独立的。核数不用拉满,物理核数减半通常能得到最优吞吐。

注意:进程池模式下每个 worker 都会重新加载一次类别列表和 label map,所以 labels.txt 不要放在每个进程里动态读取,应该在主进程读好后通过 initializer 传递。

5. 把标注结果直接接进训练管线:Dataset 实现与质量校验

5.1 直接在 PyTorch Dataset 中解析 LabelMe JSON

语义分割任务可以不转中间格式,在 Dataset 类里直接读 JSON 并渲染掩膜。这样省掉一次磁盘写放大,也方便在训练代码里维护版本一致性:

import json import cv2 import numpy as np from torch.utils.data import Dataset from PIL import Image class LabelMeSegDataset(Dataset): def __init__(self, json_files, label_map, img_dir): self.json_files = json_files self.label_map = label_map self.img_dir = img_dir def __len__(self): return len(self.json_files) def __getitem__(self, idx): with open(self.json_files[idx]) as f: data = json.load(f) img = Image.open(os.path.join(self.img_dir, data["imagePath"])) mask = np.zeros((data["imageHeight"], data["imageWidth"]), dtype=np.uint8) for shape in data["shapes"]: pts = np.array(shape["points"], dtype=np.int32) cv2.fillPoly(mask, [pts], self.label_map[shape["label"]]) return np.array(img), mask

cv2.fillPoly的时间复杂度与多边形顶点数近似线性,普通标注场景开销可忽略。但如果存在几千个顶点的大多边形,每个 epoch 都重绘一次会拖慢训练,建议第一次加载后把 mask 缓存成 npy 文件或直接用 labelme2voc 转成 PNG 一次性落盘。

5.2 数据增强必须同步掩膜

构建完数据集后,一个容易被忽略的问题是增强如何作用于标注。用 albumentations 可以很干净地处理图和掩膜同步变换:

import albumentations as A transform = A.Compose([ A.RandomResizedCrop(height=512, width=512, scale=(0.5, 1.0)), A.HorizontalFlip(p=0.5), A.ColorJitter(), ]) augmented = transform(image=img_array, mask=mask)
注意:ColorJitter 这类像素级增强会自动跳过 mask,只有空间变换才会作用到 mask。不需要额外传递 additional_targets,除非一张图有多个掩膜。

flip 和 crop 会同步修改 mask 和 image,保证坐标语义一致。如果用 PyTorch 原生的RandomResizedCrop处理 mask,需要保证两次随机采样的参数一致,实际操作麻烦很多,这也是推荐直接用 albumentations 的原因。

5.3 批量渲染标注结果用于抽检

训练前最后一步,把 JSON 和原图渲染成叠加图做人工抽检:

import os import cv2 import json import numpy as np for json_path in json_files: data = json.load(open(json_path)) img = cv2.imread(os.path.join(img_dir, data["imagePath"])) for shape in data["shapes"]: pts = np.array(shape["points"], dtype=np.int32) cv2.polylines(img, [pts], True, (0, 255, 0), 2) cv2.imwrite(f"viz/{os.path.basename(json_path)}.jpg", img)

渲染结果重点看三类问题:多边形顶点是否错位、矩形框是否没有贴合目标、类别名称是否配错。如果渲染速度异常慢,多数情况是标注文件过大或者多边形顶点数过多,可以先用最小外接矩形近似替代,保证抽检效率。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 10:28:41

Flask与Vue前后端分离开发实战指南

1. 项目概述&#xff1a;FlaskVue前后端分离架构解析前后端分离架构已成为现代Web开发的主流模式&#xff0c;它通过解耦前端展示与后端业务逻辑&#xff0c;大幅提升了开发效率和系统可维护性。本教程将详细演示如何使用Python Flask框架与Vue.js构建一个完整的前后端分离项目…

作者头像 李华
网站建设 2026/9/12 10:27:30

程序调试中的信号提示与处理技术详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 10:26:56

数据可视化核心技术解析与实践指南

1. 数据可视化概述数据可视化是将抽象数据转化为直观图形表达的过程。作为信息时代的"通用语言"&#xff0c;它帮助我们从海量数据中快速识别模式、发现异常并理解复杂关系。从简单的Excel图表到复杂的交互式仪表盘&#xff0c;数据可视化已成为商业分析、科研探索和…

作者头像 李华
网站建设 2026/9/12 10:26:03

2028年AI冲击白领岗位:技术原理、行业分化与职场应对策略

不知道你们注意到没有&#xff0c;最近几天我身边好几个做投资、做产品和做技术研究的朋友&#xff0c;都转发了同一份报告。标题一个比一个吓人&#xff0c;什么“AI终结白领”“2028年职场大洗牌”“这份报告让硅谷沉默了”&#xff0c;点进去一看&#xff0c;说的就是那份关…

作者头像 李华
网站建设 2026/9/12 10:25:32

水务水质监测管理系统:从样品到报告的全流程闭环解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华