三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Labelme目标检测标注实战:从环境配置到YOLO格式转换全流程

Labelme目标检测标注实战:从环境配置到YOLO格式转换全流程

1. 从零开始:为什么选择Labelme进行目标检测标注?

如果你刚开始接触计算机视觉项目,尤其是目标检测任务,那么“数据标注”这个环节绝对是你绕不开、也最容易被低估的一环。很多人拿到一个开源模型,比如YOLOv8,兴冲冲地准备用自己的数据训练,结果第一步就卡在了数据准备上:图片有了,框怎么画?用什么工具画?画出来的格式模型认不认?这一连串问题,足以让热情消退大半。

我经历过这个阶段,也用过不少标注工具,从早期的LabelImg,到一些在线的标注平台,再到功能更强大的CVAT。最终,在个人项目和小团队协作中,我几乎固定在了Labelme上。这不是说其他工具不好,而是Labelme在灵活性、格式支持和上手成本之间,找到了一个非常棒的平衡点。它不是一个“傻瓜式”点击就完事的工具,而是给了标注者足够的控制权,让你清楚地知道自己在创造什么样的数据。对于目标检测任务来说,这意味着你可以精确地定义每一个边界框(Bounding Box),并且为其附上正确的类别标签,生成模型训练直接可用的JSON格式标注文件。

简单来说,Labelme解决了几个核心痛点:第一,它本地运行,数据隐私和安全完全自己掌控,适合处理不便上传的敏感或专有数据;第二,它生成的是一种结构化的JSON格式,清晰地记录了每个标注对象的形状(多边形、矩形等)、位置和标签,这种格式易于被各种训练脚本(如YOLO、Detectron2等)解析和转换;第三,它支持多边形标注,这对于不规则形状的目标(遥感图像中的建筑物、医疗图像中的病灶)比单纯的矩形框更精准,虽然目标检测多用矩形框,但Labelme的矩形标注功能同样完备且易用。

所以,这篇笔记不是一份冰冷的官方文档翻译,而是我作为实践者,从环境搭建、标注实操、格式处理到避坑排错的全流程记录。无论你是想标注一批图片来训练自己的YOLO模型,还是处理一些特殊的视觉任务,希望这份带着“体温”的经验能帮你少走弯路。

2. Labelme环境部署与基础配置详解

工欲善其事,必先利其器。Labelme的安装看似简单,但不同的操作系统和Python环境可能会遇到一些“小惊喜”。下面我会分别针对Windows、macOS和Linux(包括服务器环境)给出最稳妥的安装方案。

2.1 跨平台安装方案与依赖管理

Labelme是一个Python包,因此核心前提是有一个可用的Python环境。强烈建议使用Python 3.7至3.10之间的版本,这是其依赖库兼容性最好的范围。Python 3.11及以上版本可能会遇到某些底层库(如PyQt5)的编译或兼容性问题。

首选方案:使用Conda创建独立环境这是最干净、冲突最少的方式,尤其适合同时进行多个机器学习项目的你。

# 1. 创建并激活一个名为`labelme`的虚拟环境(Python版本指定为3.8) conda create -n labelme python=3.8 conda activate labelme # 2. 安装Labelme # 使用清华镜像源加速,安装核心包 pip install labelme -i https://pypi.tuna.tsinghua.edu.cn/simple # 3. (可选但推荐)安装一些常用的图像处理库,方便后续处理 pip install opencv-python pillow numpy -i https://pypi.tuna.tsinghua.edu.cn/simple

备选方案:使用原生pip如果你系统里只有一个Python环境,或者习惯使用venv,可以这样操作:

# 使用pip直接安装(确保pip版本较新) python -m pip install --upgrade pip pip install labelme

注意:在Windows上,如果遇到关于pycocotoolslxml等库安装失败,通常是因为缺少C++编译环境。一个简单的解决方法是访问 Christoph Gohlke的Windows预编译库页面 ,下载对应Python版本和系统位数的.whl文件进行手动安装,或者更简单地,直接使用Conda安装,因为Conda会帮你处理好这些二进制依赖。

安装成功后,在命令行输入labelme,应该会弹出图形化界面。如果提示“command not found”,可能是脚本安装路径没有添加到系统PATH中,可以尝试用python -m labelme命令来启动。

2.2 首次启动与界面核心功能速览

第一次启动Labelme,你会看到一个简洁的界面。别被它的简单迷惑,关键功能都藏在了菜单和鼠标操作里。我们来快速熟悉一下:

  • 菜单栏

    • File: 打开图片/目录、保存标注、导出等。
    • Edit: 编辑标注(复制、粘贴、删除),这里有个神器是Edit -> Create Rectangle,可以快速切换到矩形标注模式,这是我们做目标检测最常用的。
    • View: 调整视图,如放大缩小。
    • Help: 查看快捷键,务必花两分钟看看,效率提升全靠它。
  • 工具栏图标(从左到右常见功能):

    • 打开文件/文件夹。
    • 保存当前标注(生成.json文件)。
    • 删除选中的标注。
    • 编辑多边形形状(拖动顶点)。
    • 绘制多边形(默认)。我们需要将其切换为绘制矩形
    • 复制、粘贴标注。
  • 核心操作逻辑

    1. 打开数据:点击File -> Open Dir,选择存放所有待标注图片的文件夹。这样软件会自动按顺序加载,方便连续标注。
    2. 切换标注形状:默认是绘制多边形(用于分割)。对于目标检测,我们需要矩形框。点击Edit -> Create Rectangle,或者更常用的,直接按快捷键Ctrl + R。此时鼠标光标会变成十字准星。
    3. 开始标注:在目标物体上,按住鼠标左键拖动,拉出一个矩形框。松开鼠标后,会弹出一个对话框让你输入标签(Label)。比如标注狗,就输入“dog”。你可以提前规划好类别,保持命名一致(大小写敏感)。
    4. 保存:标注完一张图片的所有目标后,按Ctrl + S保存。Labelme会在图片同级目录下生成一个同名的.json文件。这个JSON文件包含了这张图片所有标注的绝对信息。

这里有一个非常重要的个人习惯:我会在开始标注前,先建立一个清晰的目录结构。例如:

project/ ├── images/ # 存放所有原始图片 │ ├── img_001.jpg │ └── img_002.jpg ├── annotations/ # 存放Labelme生成的JSON文件(可选,可通过设置更改保存路径) └── labels/ # 用于存放最终转换后的YOLO格式txt文件(后续步骤)

在Labelme中,通过File -> Open Dir打开images文件夹进行标注,并通过File -> Change Output Dir将JSON文件的输出目录指定到annotations,可以让你的项目文件井井有条。

3. 目标检测标注实战:流程、规范与高效技巧

现在环境准备好了,界面也熟悉了,让我们进入实战环节。标注不仅仅是“画框”,它是一套需要严谨态度和规范流程的工作。

3.1 标注前必须明确的规范与准则

在动笔(鼠标)之前,和你的团队(或者未来的自己)明确以下几点,可以节省大量后期清洗和统一格式的时间:

  1. 标签命名规范

    • 一致性:同一个物体永远用同一个标签。cat,Cat,CAT在模型看来是三个不同的类别。建议使用全小写英文单词下划线连接,如person,car,traffic_light
    • 避免歧义:不要用“其他”、“杂项”这种模糊标签。如果确实有难以归类的物体,可以建立一个“unknown”或“background_object”类别,但需谨慎使用。
    • 层级关系:对于细粒度分类,可以考虑层级标签,如vehicle:car,vehicle:truck。但要注意,很多模型训练框架需要平铺的类别列表,你可能需要在后续转换时处理。
  2. 边界框(Bounding Box)绘制准则

    • 紧密度:框应该尽可能紧密地包围目标物体,减少背景区域的纳入。但也不必紧贴像素边缘,可以留出1-2个像素的微小空隙,避免包含背景噪声。
    • 完整性:确保框住了物体的全部可见部分。对于被遮挡的物体,根据任务要求决定:如果是检测“部分可见”的物体,就框住可见部分;如果必须检测完整物体,则该遮挡样本可能不适合用于训练。
    • 横平竖直:对于绝大多数自然场景目标,使用与图像边缘平行的矩形框(即axis-aligned bounding box)。Labelme的矩形工具默认就是这样的。除非你在做旋转目标检测(如遥感、文本),否则不要倾斜框。
  3. 困难样本与歧义处理

    • 小目标:对于像素面积很小的物体(如远处的行人),要更加仔细地框选。可以适当放大图像进行标注。
    • 密集目标:当多个同类物体紧密堆积时(如一群鸟),确保每个框只包含一个实例,框与框之间可以有轻微重叠,但要避免一个框包含多个物体。
    • 歧义目标:如果无法确定物体类别,应记录下来并统一讨论,而不是随意标注。可以建立一个“待定”列表。

3.2 高效标注的全流程与快捷键秘籍

遵循一个流程化的操作,可以极大提升效率和减少错误。以下是我的标准操作流程:

  1. 准备工作

    • 打开图片目录。
    • 立即按下Ctrl + R切换到矩形标注模式。我建议将这个模式设为默认,但Labelme目前每次启动会重置,所以养成开场按快捷键的习惯
  2. 标注循环

    • 定位:用鼠标滚轮或[]键缩放图像,将目标调整到合适大小。
    • 绘制:在目标左上角附近按住左键,拖动到右下角松开。技巧:不必追求一次拉准,可以先画个大概。
    • 微调:画完后,框体处于选中状态(有四个顶点和控制点)。你可以直接拖动框体内部移动整个框,或者拖动边或角来调整大小。这个交互非常流畅。
    • 打标签:在弹出的对话框中输入预设好的标签。高效技巧:对于连续标注同一类物体,在输入标签的对话框弹出时,直接按Enter键可以快速复用上一个标签。你也可以在Edit -> Label List中预定义标签列表,然后通过下拉菜单选择。
    • 跳过/下一张:如果当前图片没有目标(负样本),直接按Ctrl + D进入下一张。务必保存(哪怕是空标注),这样在生成数据集列表时不会遗漏。
  3. 核心快捷键清单(Windows/Linux)

    • Ctrl + R: 切换到矩形标注模式(最重要)。
    • Ctrl + S: 保存当前图片标注。
    • Ctrl + D: 打开下一张图片。
    • Ctrl + Shift + D: 打开上一张图片。
    • Ctrl + F: 拟合多边形到矩形(如果你误用了多边形,可以用这个快速转换)。
    • DelCtrl + Del: 删除选中的标注框。
    • Ctrl + C/Ctrl + V: 复制/粘贴选中的标注(适用于同一张图里多个相同物体)。
    • W: 放大图像。
    • S: 缩小图像。
    • Space: 拖拽画布。

熟练使用这些快捷键,你的标注速度至少能提升50%。特别是Ctrl+R,Ctrl+S,Ctrl+D这三个,几乎构成了标注操作的核心循环。

3.3 常见场景标注策略与SAM2辅助初探

  • 处理大量相似图片:如果一段视频抽帧出来的图片,相邻帧之间目标位置变化不大,可以使用Ctrl+C/V复制粘贴标注,然后进行微调,能节省大量时间。
  • 标注尺寸不一的目标:对于大小差异巨大的目标(如远景的人和近景的车),需要频繁缩放视图。使用W/S键或鼠标滚轮配合空格键拖拽,比用工具栏按钮快得多。
  • 关于SAM2的辅助:最近Meta发布的Segment Anything Model 2 (SAM2) 在图像分割上非常强大。有一些社区项目正在尝试将SAM2集成到Labelme中,实现“点选”或“框选”自动生成分割掩膜。对于目标检测,我们可以利用这个思路:用SAM2快速生成一个目标的分割区域,然后取这个区域的外接矩形框作为检测标注。这在大规模数据标注中潜力巨大。目前,你可以通过一些第三方脚本,先用SAM2对图片进行预标注,生成粗略的掩膜文件,再导入Labelme进行矩形框的修正和标签确认,这比完全从零开始画框要快。不过,这需要一定的工程整合能力,对于新手,建议先熟练掌握手动标注流程。

4. 从Labelme JSON到模型训练格式的终极转换

标注保存后,你得到了一堆.json文件。但YOLO、PyTorch、TensorFlow等框架通常不直接读取Labelme的JSON格式。因此,格式转换是通往训练前的最后一道,也是至关重要的一道工序。

4.1 深度解析Labelme JSON文件结构

理解输出格式,是正确转换的前提。打开一个JSON文件,你会看到类似下面的结构(已简化):

{ "version": "5.1.1", "flags": {}, "shapes": [ { "label": "dog", "points": [[x1, y1], [x2, y2]], "group_id": null, "shape_type": "rectangle", "flags": {} }, { "label": "cat", "points": [[x3, y3], [x4, y4]], "group_id": null, "shape_type": "rectangle", "flags": {} } ], "imagePath": "img_001.jpg", "imageData": null, // 或包含base64编码的图片数据 "imageHeight": 600, "imageWidth": 800 }
  • shapes: 列表,包含了所有标注对象。每个对象是一个字典。
    • label: 字符串,就是我们标注时输入的类别标签。
    • points: 列表,表示形状的顶点。对于矩形(rectangle),它只有两个点:[左上角x, y], [右下角x, y]。注意这里的坐标是绝对像素坐标
    • shape_type: 字符串,标注形状类型。我们关注的是"rectangle"
    • group_id: 可用于关联多个形状(如同一个物体的不同部分),目标检测中通常为null
  • imagePath: 图片的相对或绝对路径。
  • imageHeight/imageWidth: 图片的高和宽,这是进行坐标归一化的关键

4.2 转换为YOLO格式的完整脚本与详解

YOLO格式要求每个图片对应一个.txt文件,文件每一行代表一个物体,格式为:<class_id> <x_center> <y_center> <width> <height>其中,x_center, y_center, width, height相对于图片宽度和高度的归一化值,范围在[0, 1]之间。

下面是一个健壮的、带错误处理的Python转换脚本。请将其保存为labelme2yolo.py,并根据你的项目修改classes列表。

import json import os import argparse from pathlib import Path def convert_labelme_to_yolo(json_dir, output_dir, classes): """ 将Labelme生成的JSON标注文件转换为YOLO格式的txt文件。 参数: json_dir: 存放JSON文件的目录路径。 output_dir: 输出YOLO格式txt文件的目录路径。 classes: 类别列表,索引号将作为class_id。 """ json_dir = Path(json_dir) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) # 创建输出目录 # 创建类别映射字典,方便通过标签名查找索引 class_dict = {cls: idx for idx, cls in enumerate(classes)} # 遍历所有JSON文件 for json_file in json_dir.glob('*.json'): with open(json_file, 'r', encoding='utf-8') as f: data = json.load(f) img_height = data['imageHeight'] img_width = data['imageWidth'] txt_filename = output_dir / (json_file.stem + '.txt') # 同名txt文件 with open(txt_filename, 'w', encoding='utf-8') as f_txt: for shape in data['shapes']: label = shape['label'] # 检查标签是否在预定义的类别列表中 if label not in class_dict: print(f"警告: 在文件 {json_file.name} 中发现未知标签 '{label}',已跳过。") continue # 或者可以将其归为一个特定的“未知”类别 class_id = class_dict[label] # 确保是矩形标注 if shape['shape_type'] != 'rectangle': print(f"警告: 文件 {json_file.name} 中包含非矩形标注 ({shape['shape_type']}),已跳过。") continue points = shape['points'] # 获取矩形左上角和右下角坐标 x1, y1 = points[0] x2, y2 = points[1] # 计算矩形框中心点、宽度和高度(像素单位) x_center = (x1 + x2) / 2.0 y_center = (y1 + y2) / 2.0 box_width = abs(x2 - x1) # 使用绝对值避免坐标顺序问题 box_height = abs(y2 - y1) # 归一化到 [0, 1] x_center_norm = x_center / img_width y_center_norm = y_center / img_height box_width_norm = box_width / img_width box_height_norm = box_height / img_height # 写入YOLO格式行 # 使用格式化字符串控制精度,通常6位小数足够 f_txt.write(f"{class_id} {x_center_norm:.6f} {y_center_norm:.6f} {box_width_norm:.6f} {box_height_norm:.6f}\n") print(f"已转换: {json_file.name} -> {txt_filename.name}") if __name__ == '__main__': parser = argparse.ArgumentParser(description='将Labelme JSON标注转换为YOLO格式。') parser.add_argument('--json_dir', type=str, required=True, help='Labelme JSON文件所在目录') parser.add_argument('--output_dir', type=str, required=True, help='YOLO格式txt文件输出目录') parser.add_argument('--classes', type=str, nargs='+', required=True, help='类别名称列表,用空格分隔,例如:person car dog') args = parser.parse_args() # 示例: python labelme2yolo.py --json_dir ./annotations --output_dir ./labels --classes person car bicycle convert_labelme_to_yolo(args.json_dir, args.output_dir, args.classes)

使用方式

  1. 将上述脚本保存。
  2. 在命令行中,切换到脚本所在目录,运行:
    python labelme2yolo.py --json_dir ./annotations --output_dir ./labels --classes cat dog person
    其中--classes参数后面按顺序列出你所有的类别。这个顺序非常重要,它决定了每个类别的ID(从0开始)。在后续训练YOLO模型时,data.yaml文件里的names列表必须与此完全一致。

4.3 生成YOLO训练所需的配置文件

转换出labels文件夹后,你还需要一个数据集配置文件。在YOLOv5/v8中,通常是一个data.yaml文件。

# data.yaml path: /path/to/your/project # 数据集根目录 train: images/train # 训练集图片相对路径(相对于path) val: images/val # 验证集图片相对路径 # 类别数 nc: 3 # 类别名称列表,顺序必须与转换脚本中的--classes参数一致! names: ['cat', 'dog', 'person']

你需要手动或写脚本将图片划分为训练集和验证集(例如80%/20%),并把对应的图片和标签文件分别放到images/train,images/val,labels/train,labels/val目录下。最终目录结构如下:

project/ ├── data.yaml ├── images/ │ ├── train/ │ │ ├── img_001.jpg │ │ └── ... │ └── val/ │ ├── img_101.jpg │ └── ... └── labels/ ├── train/ │ ├── img_001.txt │ └── ... └── val/ ├── img_101.txt └── ...

这样,你就可以在YOLO训练命令中通过data=data.yaml来指定数据集了。

5. 标注质量检查、常见问题与排错指南

即使再小心,标注过程中和转换后也可能出现问题。在投入训练前,进行一次系统的质量检查至关重要。

5.1 可视化检查:确保框与标签对应正确

最直接的方法是将生成的YOLO格式标签画回原图上看。使用OpenCV可以轻松实现:

import cv2 import os def visualize_yolo_label(img_path, label_path, classes): img = cv2.imread(img_path) h, w, _ = img.shape with open(label_path, 'r') as f: lines = f.readlines() for line in lines: parts = line.strip().split() class_id, x_c, y_c, bw, bh = map(float, parts) # 将归一化坐标转换回像素坐标 x_c_abs = int(x_c * w) y_c_abs = int(y_c * h) bw_abs = int(bw * w) bh_abs = int(bh * h) # 计算左上角坐标 x1 = int(x_c_abs - bw_abs / 2) y1 = int(y_c_abs - bh_abs / 2) x2 = int(x_c_abs + bw_abs / 2) y2 = int(y_c_abs + bh_abs / 2) # 画矩形和标签 cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) label = classes[int(class_id)] cv2.putText(img, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,255,0), 2) cv2.imshow('Check', img) cv2.waitKey(0) cv2.destroyAllWindows() # 示例使用 classes = ['cat', 'dog', 'person'] visualize_yolo_label('project/images/train/img_001.jpg', 'project/labels/train/img_001.txt', classes)

随机抽查一些图片,查看框的位置是否准确、标签是否正确、是否有漏标或错标。

5.2 常见错误与解决方案排查表

问题现象可能原因解决方案
转换脚本报错:KeyError: 'imageHeight'JSON文件中缺少imageHeightimageWidth字段。可能是Labelme版本问题或文件损坏。1. 检查JSON文件完整性。2. 尝试用Labelme重新打开并保存一下该图片。3. 在脚本中添加异常处理,打印出错文件名。
YOLO训练时提示“Label file is empty”某个.txt标签文件是空的(没有目标),但图片路径存在于训练列表中。这是正常的,负样本(没有目标的图片)可以有空标签文件。确保你的数据集划分脚本正确处理了这种情况。
框的位置在可视化时完全错乱坐标归一化计算错误,或图片宽高获取有误。最常见的是弄混了X,Y坐标顺序检查转换脚本:OpenCV/PIL读取图片的尺寸是(高,宽),而Labelme的JSON里是imageWidth(宽),imageHeight(高)。确保用对。
类别ID对不上,预测时标签错误转换脚本中的classes列表顺序与训练时data.yaml中的names顺序不一致。绝对确保两者完全一致。建议将类别列表写在一个单独的classes.txt文件中,转换和配置都读取这个文件。
标注框超出图片边界在Labelme中画框时,起点或终点拖到了画布外。在转换脚本中加入边界检查逻辑,将越界的坐标裁剪到[0, width-1]和[0, height-1]范围内。
同一个物体有多个重叠框标注时不小心重复标注了。在Labelme中检查并删除重复的标注。在转换前,可以写一个脚本检查同一类别框的IoU(交并比),如果过高则报警。
JSON文件中有非矩形标注不小心用成了多边形工具。在转换脚本中通过shape_type过滤,只处理rectangle类型,并对其他类型给出警告。

5.3 高级技巧:自动化检查与数据清洗脚本雏形

对于大型数据集,手动检查不现实。可以编写简单的自动化检查脚本,集成到你的标注流水线中:

import json from pathlib import Path def basic_label_check(json_dir): """基础检查:空标注、无效形状、标签不在列表等""" all_classes = set() error_files = [] for json_file in Path(json_dir).glob('*.json'): with open(json_file, 'r') as f: data = json.load(f) if 'shapes' not in data or len(data['shapes']) == 0: print(f"提示: {json_file.name} 是空标注(负样本)。") continue for shape in data['shapes']: all_classes.add(shape['label']) if shape['shape_type'] != 'rectangle': error_files.append((json_file.name, f"非矩形标注: {shape['shape_type']}")) # 检查坐标点数量 if len(shape['points']) != 2: error_files.append((json_file.name, "矩形坐标点数量错误")) # 检查坐标是否在图片范围内(简单示例) x1, y1 = shape['points'][0] x2, y2 = shape['points'][1] if not (0 <= x1 <= data['imageWidth'] and 0 <= x2 <= data['imageWidth']): error_files.append((json_file.name, f"X坐标越界: {x1}, {x2}")) print(f"数据集中出现的所有标签: {all_classes}") if error_files: print("\n发现错误文件:") for f, e in error_files: print(f" - {f}: {e}") else: print("\n基础检查通过。") # 运行检查 basic_label_check('./annotations')

这个脚本能快速帮你发现一些明显的问题,比如意外的标签、非矩形标注和明显的坐标越界,在投入训练前做一次这样的检查非常有必要。标注工作虽然繁琐,但它是模型效果的基石。一份高质量、规范的数据集,往往比尝试更复杂的模型结构带来的提升更大。在Labelme的帮助下,通过规范的流程、高效的技巧和严谨的检查,你可以为自己的目标检测项目打下最坚实的基础。

← 返回列表