
简介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 文件顶层字段就shapes、imagePath、imageData这几个没有数据库没有权限模型坐标直接写在数组里。这意味着可以徒手写脚本做批量修改、格式转换甚至在训练代码里直接解析。按安装、标注、格式转换到接入训练管线的顺序来看实际项目里怎么用、坑在哪、参数怎么调。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_PLATFORMoffscreen否则会提示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读取文件。无论哪种方式imageHeight和imageWidth都对应原始图像分辨率坐标不会随 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])再算出xmax、ymax。很多人在 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.txtlabelme2voc.py的输出目录结构如下data_voc/ ├── JPEGImages/ # 图像副本 ├── SegmentationClass/ # 按类别上色的可视化分割图 ├── SegmentationClassPNG/ # 单通道标注图像素值类别编号 ├── SegmentationObject/ # 实例分割图 ├── Visualization/ # 标注叠加可视化 ├── class_names.txt └── ...关键点是SegmentationClassPNG里的像素值就是类别编号。如果 labels.txt 顺序是_background_、person、car那么 person 区域的像素值是 1car 是 2。普通 PNG 位深为 8 位类别数超过 255 时需要考虑其他编码方案。这里再次强调labels.txt 的顺序一旦确定训练前不要随意改动否则所有已生成的掩膜都要重新转换。4.2 转换中的高频错误定位格式标签呈现方式适配的模型VOC SegmentationClassPNG单通道像素值U-Net、DeepLab 等分割模型COCO JSON多边形顶点列表加 bboxMask R-CNN、mmdetection、Detectron2YOLO 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 的 txtimport 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]), dtypenp.uint8) for shape in data[shapes]: pts np.array(shape[points], dtypenp.int32) cv2.fillPoly(mask, [pts], self.label_map[shape[label]]) return np.array(img), maskcv2.fillPoly的时间复杂度与多边形顶点数近似线性普通标注场景开销可忽略。但如果存在几千个顶点的大多边形每个 epoch 都重绘一次会拖慢训练建议第一次加载后把 mask 缓存成 npy 文件或直接用 labelme2voc 转成 PNG 一次性落盘。5.2 数据增强必须同步掩膜构建完数据集后一个容易被忽略的问题是增强如何作用于标注。用 albumentations 可以很干净地处理图和掩膜同步变换import albumentations as A transform A.Compose([ A.RandomResizedCrop(height512, width512, scale(0.5, 1.0)), A.HorizontalFlip(p0.5), A.ColorJitter(), ]) augmented transform(imageimg_array, maskmask)注意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], dtypenp.int32) cv2.polylines(img, [pts], True, (0, 255, 0), 2) cv2.imwrite(fviz/{os.path.basename(json_path)}.jpg, img)渲染结果重点看三类问题多边形顶点是否错位、矩形框是否没有贴合目标、类别名称是否配错。如果渲染速度异常慢多数情况是标注文件过大或者多边形顶点数过多可以先用最小外接矩形近似替代保证抽检效率。本文还有配套的精品资源点击获取