4. 数据集标注(下)

上一节我们把标注工具和基础概念捋了一遍。这一节,咱们直接上手干。

我会带着你从 Labelme 的安装开始,一路走到 COCO 格式的细节,最后再聊聊怎么保证你的标注数据不出幺蛾子。嗯,这部分内容,说白了就是决定你模型上限的关键一步。

4.1 Labelme 安装与快速上手

Labelme 是我个人最常用的标注工具之一。它轻量、跨平台,而且输出格式非常灵活。我最早接触它的时候,还是用它来标注语义分割数据,后来发现做目标检测的矩形框标注也完全够用。

4.1.1 安装

安装其实很简单,一行命令搞定。前提是你已经装好了 Python 和 pip。

pip install labelme

如果你是在 conda 环境里干活,我建议先创建一个干净的环境:

conda create -n labelme python=3.8
conda activate labelme
pip install labelme
小提示: 我遇到过有些 Windows 机器上直接 pip 安装会报错,通常是 PyQt5 的依赖问题。这时候可以试试先手动装 PyQt5:pip install PyQt5,再装 labelme。

4.1.2 启动与界面

安装完成后,在终端输入:

labelme

界面就弹出来了。你想想看,一个干干净净的窗口,左边是工具栏,右边是图片显示区。常用的操作就几个:

  • Open – 打开单张图片
  • Open Dir – 打开整个图片文件夹(批量标注时用这个)
  • Create Polygons – 创建多边形标注(默认是画多边形,但也能画矩形)
  • Save – 保存标注结果,默认生成 JSON 文件

我个人习惯把图片文件夹和标注文件分开存放,这样方便后续脚本处理。

4.1.3 标注矩形框

做目标检测,我们主要用矩形框。Labelme 默认是多边形模式,但你可以通过设置让它直接画矩形:

  1. 点击菜单栏 File → Change Output Dir,选择你的标注输出文件夹。
  2. 点击左侧工具栏的 Create Polygons 按钮(或者按快捷键 Ctrl+N)。
  3. 在图片上点击第一个点,然后点击对角点,右键结束。这时候会弹出一个对话框让你输入标签名称。
  4. 输入类别名,比如 person、car,点 OK。

嗯,这里要注意:Labelme 的矩形框本质上是四个点的多边形。所以它保存的 JSON 里,points 字段会有四个坐标点。这在后面转 COCO 格式时需要处理一下。

4.2 COCO 格式详解

COCO 格式,说白了就是一套标准化的 JSON 结构。YOLO 官方虽然推荐用 TXT 格式,但很多开源项目、预训练模型、评估工具都认 COCO。我建议你至少学会看懂它。

一个标准的 COCO JSON 文件,最核心的是三个字段:images、annotations、categories。

字段 类型 说明
images 列表 每张图片的信息:id、文件名、宽、高
annotations 列表 每个标注框的信息:所属图片 id、类别 id、bbox、面积等
categories 列表 类别定义:id 和 name 的映射

举个例子,一个简单的 COCO JSON 长这样:

{
  "images": [
    {
      "id": 1,
      "file_name": "000001.jpg",
      "width": 1920,
      "height": 1080
    }
  ],
  "annotations": [
    {
      "id": 1,
      "image_id": 1,
      "category_id": 1,
      "bbox": [100, 200, 300, 400],
      "area": 120000,
      "iscrowd": 0
    }
  ],
  "categories": [
    {
      "id": 1,
      "name": "person"
    }
  ]
}
关键点: COCO 的 bbox 格式是 [x, y, width, height],也就是左上角坐标 + 宽高。而 YOLO 的 TXT 格式是 [class_id, x_center, y_center, width, height],并且所有坐标都是归一化的。这两个格式之间的转换,是咱们后面写脚本的必修课。

4.3 多类别标注实战

假设我们要做一个交通场景检测,包含三个类别:person、car、traffic_light。

操作步骤其实很简单:

  1. 打开 Labelme,加载图片文件夹。
  2. 对每张图片,依次框出所有目标。
  3. 每个框都输入对应的类别名。
  4. 保存后,每张图片会生成一个同名的 JSON 文件。

但这里有个坑,我踩过。Labelme 不会自动帮你记住类别列表。如果你标注了 100 张图,每次都要手动输入 person,很容易打错字。比如把 person 打成 persn,后面脚本一跑就报错。

避坑指南: 我曾经因为一个类别名拼写错误,导致整个数据集转换脚本崩溃,排查了半小时才发现。建议你提前创建一个类别标签文件,比如 labels.txt,里面每行一个类别名。然后在 Labelme 的 File → Save Automatically 旁边,有个 Edit → Create Line 之类的选项,其实没有直接导入功能。更靠谱的做法是写个小脚本,批量检查 JSON 文件里的类别名是否都在你的预期列表里。

标注完一批数据后,你的文件夹结构应该是这样的:

dataset/
├── images/
│   ├── 000001.jpg
│   ├── 000002.jpg
│   └── ...
└── labels/
    ├── 000001.json
    ├── 000002.json
    └── ...

4.4 数据质量检查

标注完了别急着高兴。数据质量不过关,模型训练就是白费功夫。我见过太多人标注完直接开训,结果模型 loss 死活降不下去,最后发现是标注框歪了。

我一般会做以下几项检查:

4.4.1 可视化检查

写一个简单的脚本,把标注框画回图片上,肉眼扫一遍。这一步最笨,但也最有效。

import json
import cv2

def check_annotation(image_path, json_path):
    with open(json_path, 'r') as f:
        data = json.load(f)
    img = cv2.imread(image_path)
    for shape in data['shapes']:
        points = shape['points']
        # 矩形框:取左上和右下
        x1, y1 = points[0]
        x2, y2 = points[2]
        cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
        cv2.putText(img, shape['label'], (int(x1), int(y1)-5),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
    cv2.imshow('check', img)
    cv2.waitKey(0)

4.4.2 统计检查

我会统计一下每张图片的标注框数量、每个类别的样本数。如果某个类别只有几张图有标注,那这个类别基本学不出来。

检查项 正常范围 异常信号
每张图框数 1-20 超过50,可能漏标或重复标
类别分布 各类别比例接近 某个类别占比 < 5%
框大小 占图片面积 1%-50% 框太小或太大,可能是误标

4.4.3 边界检查

检查有没有框超出图片边界。Labelme 允许你画到图片外面去,但训练时这些框会导致坐标归一化后出现负数或大于1的值,直接报错。

小技巧: 我写过一个脚本,自动把超出边界的框裁剪到图片范围内,并记录日志。这样既不影响训练,又能知道哪些图片有问题,方便回头修正。

4.5 本章知识结构

下面这张图,帮你把这一节的核心流程串起来:

数据集标注(下)核心流程 Labelme 安装与启动 多类别标注实战 COCO 格式详解 数据质量检查 可视化检查 统计检查 边界检查 避坑提醒 • 类别名不要拼错 • 框不要超出边界 • 每张图框数要合理

从 Labelme 安装开始,到多类别标注,再到理解 COCO 格式,最后用三种检查手段兜底。这一套流程走下来,你的数据集质量基本就有保障了。

一句话总结: 标注不是画框就完事,质量检查才是决定模型上限的关键。我见过太多项目,标注花了 80% 的时间,检查只花了 5%,结果模型训练效果大打折扣。别省这一步。