1. 为什么我至今还在用 labelme 做标注
如果你正在做计算机视觉相关的项目,不管是目标检测、语义分割还是实例分割,数据标注这一关是绕不过去的。市面上标注工具不少,有在线的、有客户端的、有商业的、有开源的,但我在实际项目里用得最多、推荐给团队新人最多的,还是labelme。原因很简单:它足够轻量、足够灵活、支持多边形标注、输出格式通用,而且完全离线运行,数据不出本地,这对很多有数据保密要求的场景来说几乎是刚需。
labelme 本质上是一个基于 Python 和 Qt 构建的图像标注工具,最早由 MIT 的计算机视觉团队开源出来。它的核心能力是让你在图像上画多边形、矩形、圆形、线段甚至点,然后把这些标注信息保存成 JSON 文件。这个 JSON 里记录了每个标注的形状、类别标签、坐标点,后续你可以写脚本把它转换成 COCO、VOC、YOLO 等训练框架需要的格式。说白了,labelme 负责“画”,格式转换的活儿你自己写几行 Python 就能搞定,灵活度非常高。
这篇文章适合谁看?如果你是刚入门深度学习、第一次接触数据标注的新手,我会从零开始带你装好、跑通、标出第一批数据;如果你已经用过 labelme 但总是被各种报错、中文路径、闪退、格式转换搞得头大,我也会把踩过的坑和解决方案一并整理出来。整篇内容基于我多个实际项目的操作经验,补充了大量官方文档里不会写的细节。
2. 安装前的环境梳理与方案选型
2.1 为什么安装方式的选择很关键
labelme 的安装看起来就是一句pip install labelme的事,但实际操作中,很多人卡在第一步就放弃了。核心矛盾在于:labelme 依赖 Qt 图形界面库(PyQt5 或 PyQt6),而 Qt 在不同操作系统上的表现差异很大,再加上 Python 版本、conda 环境、系统图形库的兼容性问题,很容易出现装完了打不开、打开就闪退、界面乱码等情况。
所以我在动手之前,一定会先想清楚三件事:第一,我用的是 Windows、macOS 还是 Linux;第二,我打算用系统 Python、conda 环境还是独立虚拟环境;第三,我是否需要 GPU 相关的依赖(labelme 本身不需要 GPU,但你可能在同一个环境里跑训练)。把这三点想清楚,安装方式基本就定了。
我的建议是:永远不要在系统全局 Python 里装 labelme。原因很现实,labelme 会锁定特定版本的 Qt 和 numpy,一旦和系统里其他包的版本冲突,你整个 Python 环境都可能出问题。用 conda 或者 venv 建一个独立环境,是成本最低、最省心的做法。
2.2 三种主流安装方式对比
我把常见的三种安装方式整理成表格,你可以根据自己的情况对号入座。
| 安装方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| pip 直接安装 | 临时试用、环境干净 | 一条命令搞定 | 容易和系统包冲突 |
| conda 独立环境 | 长期使用、多项目共存 | 隔离性好、依赖自动解决 | 需要先装 conda |
| 源码安装 | 需要改源码、二次开发 | 可定制 | 步骤多、易出错 |
对于 90% 的人,我推荐 conda 独立环境这条路。下面我把完整流程拆开讲。
2.3 conda 环境的创建与 Python 版本选择
Python 版本这块有个经验:labelme 对 Python 3.9 到 3.11 的支持最稳定。太老的版本(3.7 以下)很多新特性不支持,太新的版本(3.12 以上)有时候 Qt 的 wheel 包还没跟上,容易出问题。我一般用 3.10,兼顾稳定和新特性。
conda create -n labelme_env python=3.10 -y conda activate labelme_env创建完环境后,先别急着装 labelme,我习惯先升级一下 pip,避免因为 pip 版本太老导致下载 wheel 失败。
python -m pip install --upgrade pip这一步看起来多余,但我遇到过好几次因为 pip 版本问题导致安装卡住的情况,提前升级能省不少事。
2.4 正式安装 labelme 及依赖
环境准备好之后,安装就很简单了:
pip install labelme如果你需要用到 labelme 的一些扩展功能,比如把标注结果直接转成 COCO 格式,可以顺便装上配套工具:
pip install labelme2coco安装完成后,验证一下是否成功:
labelme --version能正常打印出版本号,说明安装基本没问题。如果这一步报“command not found”,大概率是环境没激活,或者 Scripts 目录没加到 PATH 里,Windows 上尤其常见。
注意:如果你在公司内网或者网络受限的环境下安装,pip 可能会因为下载超时而失败。这时候可以配置国内镜像源,比如清华源或者阿里源,具体命令网上很多,这里不展开。核心思路就是让 pip 从更近的服务器拉包。
3. 启动与界面核心功能拆解
3.1 第一次启动的正确姿势
安装好之后,直接在终端输入:
labelme正常情况下会弹出一个图形界面窗口。如果窗口一闪而过,或者报 Qt 相关的错误,先别慌,这几乎是新手必踩的坑。最常见的原因是缺少系统图形库,Linux 上尤其明显,需要装libxcb-xinerama0之类的依赖。Windows 上则多半是 Qt 插件路径问题,重装 PyQt5 通常能解决。
启动时你还可以直接指定图片目录,省得进去之后再打开:
labelme /path/to/your/images这样启动后左侧的文件列表会直接加载这个目录下的所有图片,标注效率会高很多。
3.2 界面布局与核心按钮说明
labelme 的界面非常朴素,但每个按钮都有明确用途。我按使用频率从高到低给你梳理一遍。
左侧是文件列表区,显示当前目录下的图片。中间是画布区,你的标注操作都在这里完成。右侧是标签列表区,显示当前图片上已经标注的类别和对应的多边形。顶部工具栏是核心操作区,常用的几个按钮包括:
- Open Dir:打开图片目录,这是你每次开始工作的第一步。
- Create Polygons:创建多边形标注,这是做分割任务最常用的功能。
- Create Rectangle:创建矩形框,做目标检测时用。
- Edit Polygons:编辑已有的多边形顶点,调整标注精度。
- Next Image / Prev Image:切换上一张、下一张图片。
- Save:保存当前标注为 JSON 文件。
这里有个细节很多人不知道:labelme 默认不会自动保存,你标完一张图必须手动点 Save 或者按快捷键Ctrl+S。我见过太多人标了一下午,结果没保存,心态直接崩了。所以我的习惯是标完一张立刻保存,形成肌肉记忆。
3.3 快捷键体系与效率提升
真正把 labelme 用出效率的人,几乎都是靠快捷键操作的。鼠标点来点去太慢了,下面这几个快捷键你一定要记住:
| 快捷键 | 功能 |
|---|---|
| Ctrl+S | 保存当前标注 |
| Ctrl+O | 打开文件 |
| D | 下一张图片 |
| A | 上一张图片 |
| Ctrl+Z | 撤销上一步 |
| Ctrl+滚轮 | 缩放画布 |
| Delete | 删除选中的标注 |
我实测下来,熟练使用快捷键之后,标注速度能提升至少 40%。尤其是D和A切换图片,配合Ctrl+S,基本可以做到手不离键盘完成整个标注流程。
3.4 标签管理与类别命名规范
labelme 的标签是你在画多边形时手动输入的。这里有个非常重要的经验:类别名称一定要提前规划好,并且保持严格一致。我见过有人标同一类物体,一会儿写“car”,一会儿写“Car”,一会儿写“汽车”,最后转格式的时候类别全乱了,只能返工。
我的做法是,在开始标注之前,先写一个类别清单文档,比如:
person car bicycle traffic_light然后标注时严格按这个清单输入。labelme 有个贴心的地方是,你输入过的标签会出现在下拉列表里,直接选就行,不用每次手打,这也间接保证了命名一致性。
4. 完整标注流程与实操细节
4.1 从打开目录到保存第一张标注
我把一次完整的标注流程拆成下面这几步,你照着做就能跑通。
第一步,启动 labelme 并打开图片目录。假设你的图片放在D:/dataset/images下,直接:
labelme D:/dataset/images第二步,在左侧列表点击第一张图片,画布区会显示出来。
第三步,点击Create Polygons按钮,开始沿着目标物体的边缘逐点点击。注意,多边形是首尾闭合的,当你点回起点附近时,labelme 会自动闭合,然后弹出输入框让你填类别名。
第四步,输入类别名,比如person,回车确认。右侧列表就会出现这个标注。
第五步,按Ctrl+S保存。保存后会在图片同目录下生成一个同名的 JSON 文件,比如IMG_001.jpg对应IMG_001.json。
第六步,按D切换到下一张,重复以上操作。
整个过程听起来简单,但实际操作中有几个细节直接决定标注质量。
4.2 多边形标注的精度控制技巧
做分割任务时,多边形的顶点数量直接决定了标注精度和后续训练效果。点太少,边缘粗糙,模型学不到细节;点太多,标注耗时翻倍,而且容易过拟合到标注噪声上。
我的经验是:对于边缘规则、曲率小的物体,顶点间距可以大一些;对于边缘复杂、有凹陷或尖角的物体,关键转折点必须点上。比如标注一辆车,车轮的圆弧部分点 5 到 6 个点就够了,但后视镜、车灯这些突出部位一定要单独点出来。
另外,标注时尽量放大画布。labelme 支持Ctrl+滚轮缩放,放大到能看清像素级别再点,精度会高很多。缩小状态下点出来的多边形,放大一看全是偏差。
4.3 遮挡与边界情况的处理原则
实际数据里,物体被遮挡是常态。这时候标注策略要统一,否则模型会学乱。我一般遵循两条原则:
第一,被遮挡部分不猜。如果一个人被车挡住了一半,我只标注可见部分,不去脑补被挡住的身体。因为脑补的坐标是错的,反而会引入噪声。
第二,边界物体按可见区域标。如果物体在图像边缘被截断,我只标注图像内可见的部分,不延伸到图像外。
这两条原则看起来简单,但团队协作时如果不提前统一,不同人标出来的数据风格差异会很大,模型训练效果直接受影响。所以我在项目开始前一定会写一份标注规范文档,把这些边界情况写清楚。
4.4 批量标注的效率优化思路
当你有几千张图要标时,效率就是生命。除了前面说的快捷键,还有几个技巧能帮你提速。
一是先易后难。先把简单、清晰的图片快速标完,建立节奏感,再处理复杂的。一上来就啃硬骨头容易疲劳。
二是同类图片连续标。如果数据集里有一批相似场景的图,集中处理它们,因为你对这类物体的边缘已经熟悉了,标注速度会明显加快。
三是善用复制粘贴。labelme 支持Ctrl+C和Ctrl+V复制粘贴标注形状,对于视频抽帧得到的连续图片,物体位置变化不大时,复制上一张的标注再微调,比重新画快得多。
5. 标注文件格式解析与格式转换
5.1 JSON 文件结构逐字段拆解
labelme 保存的 JSON 文件结构其实很清晰,理解它之后你就能自由地写转换脚本。一个典型的 JSON 长这样:
{ "version": "5.3.1", "flags": {}, "shapes": [ { "label": "person", "points": [[100, 200], [150, 200], [150, 300], [100, 300]], "group_id": null, "shape_type": "polygon", "flags": {} } ], "imagePath": "IMG_001.jpg", "imageData": null, "imageHeight": 720, "imageWidth": 1280 }几个关键字段说明一下。shapes是核心,里面每个元素代表一个标注对象。label是类别名,points是多边形的顶点坐标列表,注意坐标是[x, y]格式,原点在左上角。shape_type表示标注类型,常见的有polygon、rectangle、circle、line、point。imagePath是图片相对路径,imageHeight和imageWidth是图片尺寸。
imageData这个字段默认是 null,如果你在保存时勾选了“保存图像数据”,它会把整张图 base64 编码塞进 JSON,文件会变得巨大,一般不建议开。
5.2 转 COCO 格式的实操脚本
COCO 格式是目标检测和实例分割最通用的格式之一。labelme 官方提供了labelme2coco工具,但实际用的时候经常需要微调。我一般直接写脚本转换,可控性更强。
import json import os from PIL import Image def labelme_to_coco(json_dir, output_file): coco = { "images": [], "annotations": [], "categories": [] } categories = {} ann_id = 1 for idx, fname in enumerate(os.listdir(json_dir)): if not fname.endswith(".json"): continue with open(os.path.join(json_dir, fname), "r", encoding="utf-8") as f: data = json.load(f) img_id = idx + 1 coco["images"].append({ "id": img_id, "file_name": data["imagePath"], "height": data["imageHeight"], "width": data["imageWidth"] }) for shape in data["shapes"]: label = shape["label"] if label not in categories: categories[label] = len(categories) + 1 coco["categories"].append({ "id": categories[label], "name": label, "supercategory": "none" }) points = shape["points"] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) coco["annotations"].append({ "id": ann_id, "image_id": img_id, "category_id": categories[label], "bbox": [x_min, y_min, x_max - x_min, y_max - y_min], "area": (x_max - x_min) * (y_max - y_min), "segmentation": [[coord for p in points for coord in p]], "iscrowd": 0 }) ann_id += 1 with open(output_file, "w", encoding="utf-8") as f: json.dump(coco, f, ensure_ascii=False, indent=2) labelme_to_coco("./labels", "./coco.json")这段脚本的核心逻辑就是遍历所有 JSON,把 shapes 里的信息映射到 COCO 的 images、annotations、categories 三个数组里。segmentation字段把多边形顶点展平成一维列表,这是 COCO 分割格式的要求。
5.3 转 YOLO 格式的坐标归一化计算
YOLO 格式要求坐标归一化到 0 到 1 之间,而且格式是类别 x_center y_center width height。转换时要注意,YOLO 用的是归一化后的中心点和宽高,不是左上角坐标。
def labelme_to_yolo(json_file, output_txt, class_map): with open(json_file, "r", encoding="utf-8") as f: data = json.load(f) img_w = data["imageWidth"] img_h = data["imageHeight"] lines = [] for shape in data["shapes"]: label = shape["label"] if label not in class_map: continue cls_id = class_map[label] points = shape["points"] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) x_center = (x_min + x_max) / 2 / img_w y_center = (y_min + y_max) / 2 / img_h width = (x_max - x_min) / img_w height = (y_max - y_min) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") with open(output_txt, "w") as f: f.write("\n".join(lines))归一化这一步是必须的,因为 YOLO 训练时输入图片尺寸会做缩放,用绝对坐标会导致框错位。计算中心点和宽高时,记得先除以图片的宽高,得到 0 到 1 之间的比例值。
5.4 格式转换中的常见陷阱
转换过程中有几个坑我踩过不止一次。第一个是坐标越界。有时候标注时不小心点到图片外面,坐标会出现负数或者超过图片尺寸,转 COCO 时 bbox 就变成负宽高,训练直接报错。解决办法是在转换脚本里加一层裁剪,把坐标限制在[0, width]和[0, height]范围内。
第二个是类别名大小写不一致。前面提过,这个必须在标注阶段就统一,转换阶段再处理就晚了。
第三个是空标注文件。有些图片可能没有目标,JSON 里 shapes 是空数组,转换时要跳过,否则会生成空的标注行,影响训练。
6. 常见报错与问题排查实录
6.1 启动类问题速查
启动阶段的问题最让人抓狂,因为界面都进不去。我把常见的几种整理成表格,方便你对照排查。
| 报错现象 | 可能原因 | 解决方案 |
|---|---|---|
| 命令找不到 | 环境未激活 | 重新 activate 环境 |
| 窗口闪退 | Qt 插件缺失 | 重装 PyQt5 |
| 界面乱码 | 字体缺失 | 安装中文字体 |
| 报 xcb 错误 | 系统图形库缺失 | 安装 libxcb 相关库 |
| 卡在启动画面 | 显卡驱动冲突 | 设置 Qt 软件渲染 |
其中 Linux 上的 xcb 错误最典型,报错信息里通常会提示缺哪个库,照着装就行。Windows 上闪退多半是 PyQt5 版本和 Python 版本不匹配,卸载重装指定版本通常能解决。
6.2 标注过程中的异常处理
标注到一半出问题也很常见。比如画多边形时点错了想撤销,直接按Ctrl+Z就行,但要注意 labelme 的撤销是分步的,一次撤销一个顶点。如果整个多边形都画错了,选中它按Delete删掉重画。
还有一种情况是标注到一半软件卡死。这时候千万别强行关,先等几秒,有时候只是渲染卡顿。如果确实无响应,只能强制关闭,但之前没保存的标注就丢了。所以再次强调,勤按 Ctrl+S。
6.3 中文路径与编码问题
中文路径是 labelme 的一个老大难问题。早期版本对中文支持不好,图片放在中文目录下会打不开或者保存失败。新版本改善了很多,但为了保险,我建议图片目录和文件名全部用英文和数字,不要用中文、空格和特殊符号。
如果实在要用中文路径,确保你的系统编码是 UTF-8,Python 环境也设置成 UTF-8。Windows 上可以在代码里加:
import sys import io sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')但这只是缓解,根治办法还是用英文路径。
6.4 大尺寸图片与内存问题
labelme 加载超大图片(比如 8000x6000 像素)时会非常吃内存,有时候直接卡死。我的处理办法是,标注前先把图片缩放到合理尺寸,比如长边不超过 2000 像素。缩放后再标注,坐标转换时按比例还原即可。
如果必须标注原图,可以关闭 labelme 的实时渲染预览,减少内存占用。另外,标注时不要同时打开太多图片,标完一张关一张。
7. 团队协作与标注质量管理
7.1 多人标注的规范统一
团队标注最大的挑战不是技术,而是一致性。十个人标同一批数据,如果规范不统一,标出来的结果风格能差出十万八千里。我的做法是,项目开始前先做三件事。
第一,写一份详细的标注规范文档,把类别定义、边界处理、遮挡处理、最小标注尺寸都写清楚,配上正例和反例截图。
第二,先让每个人标 20 张样图,交叉检查,把分歧点拿出来讨论,统一认识。
第三,正式标注后定期抽检,发现问题及时纠正,不要等到全部标完才发现要返工。
7.2 标注质量的抽检方法
抽检不是随便看看,要有明确的检查项。我一般检查这几个方面:类别是否正确、多边形是否贴合物体边缘、遮挡处理是否符合规范、有没有漏标、有没有重复标注。
抽检比例我一般定在 10% 左右,如果发现错误率超过 5%,就扩大抽检比例,甚至全检。质量永远比数量重要,一批脏数据能让模型训练效果倒退好几个百分点。
7.3 标注进度的追踪方式
进度追踪我推荐用最简单的表格,记录每个人每天标了多少张、抽检合格率多少。不要搞太复杂的系统,表格足够。关键是让每个人知道自己的进度和质量,形成正向反馈。
另外,标注任务要拆解到天,不要笼统地说“这周标完”。每天定一个小目标,比如每人每天 100 张,完成就打勾,进度一目了然。
8. 一些让我少走弯路的实操心得
标注这件事,工具只是载体,真正决定效率和质量的是流程和习惯。我用了这么多年 labelme,最大的体会是:前期多花一小时规划,后期能省十小时返工。类别清单、标注规范、目录结构,这些看起来琐碎的东西,恰恰是项目能不能顺利推进的关键。
还有一个心得是,不要追求一次标到完美。标注是个迭代过程,第一遍粗标,第二遍精修,第三遍抽检,比一次性死磕效率高得多。人的注意力有限,长时间盯着像素级边缘,精度反而会下降。
最后分享一个小技巧:labelme 的 JSON 文件其实可以直接用文本编辑器打开修改。有时候某个标注只差一两个像素,与其在界面里拖来拖去,不如直接改 JSON 里的坐标值,改完保存,重新加载即可。这个技巧在处理个别瑕疵标注时特别管用,但批量修改还是要靠脚本,手动改容易出错。