深度学习数据标注实战:从零搭建Labelme、LabelImg与EISeg的高效工作流

最近在带几个实习生做计算机视觉项目,发现他们花在数据标注上的时间,往往比模型训练和调参还要多。不是工具装不上,就是格式转换出错,要么就是标注效率太低,反复折腾。这让我想起自己刚入门时踩过的那些坑——其实只要把标注环境搭建好,后续的工作会顺畅得多。

数据标注是深度学习项目里最基础、也最容易被低估的环节。一张图片标注得好不好,直接影响模型学习的上限。对于刚接触这个领域的新手来说,面对Labelme、LabelImg、EISeg这些工具,可能会感到无从下手。网上的教程要么过于简略,要么环境依赖复杂,跟着做总是报错。

这篇文章,我就结合自己这几年在多个实际项目中的标注经验,为你梳理一套开箱即用、避坑指南式的标注环境搭建方案。我们不只讲安装命令,更会深入每个工具的特点、适用场景,以及如何根据你的项目需求选择合适的工具链。无论你是要处理目标检测、实例分割还是简单的图像分类任务,这套流程都能帮你快速上手,把时间真正花在模型迭代上,而不是和环境配置较劲。

1. 标注工具全景图:如何根据任务选择你的“武器”

在开始安装之前,我们需要先搞清楚每个工具的核心定位。不同的标注工具擅长不同的任务,选对了工具,效率能提升好几倍。

LabelImg 是最经典的目标检测标注工具,它只支持矩形框标注,输出格式包括PASCAL VOC XML、YOLO txt和CreateML JSON。它的界面非常简洁,学习成本几乎为零。如果你做的是车辆检测、人脸识别这类标准的矩形框检测任务,LabelImg是首选。它的局限性也很明显:无法处理不规则形状的物体,比如医学图像中的病灶区域、遥感图像中的建筑物轮廓。

Labelme 由麻省理工学院计算机科学与人工智能实验室(MIT CSAIL)开发,它最大的优势是支持多边形标注,非常适合实例分割和语义分割任务。你可以用鼠标精确勾勒出物体的每一个边缘,生成精细的掩膜。Labelme默认输出JSON格式,这个格式包含了多边形的所有顶点坐标,信息非常丰富。不过,很多训练框架(如YOLO、MMDetection)需要特定的格式,因此通常需要额外的转换脚本。

EISeg 是百度飞桨团队推出的交互式智能分割标注工具。它最大的亮点是引入了AI辅助标注。你只需要在物体内部点一下,再在外部点一下,模型就能自动预测出完整的分割掩膜,大大减少了手动勾勒多边形的工作量。EISeg内置了通用、人像、遥感、医疗等多个预训练模型,对于专业领域的标注尤其高效。它支持导出COCO JSON和PaddleSeg格式。

为了更直观地对比,我整理了这三个工具的核心特性:

特性维度LabelImgLabelmeEISeg
主要标注类型矩形框多边形、矩形框交互式分割(多边形)
核心优势简单、快速、稳定标注精度高、灵活AI辅助、效率极高
典型输出格式XML (VOC), TXT (YOLO)JSONJSON (COCO), PaddleSeg格式
最适合任务目标检测实例/语义分割复杂形状分割、大批量标注
学习曲线极低中等中等(需理解交互逻辑)
环境依赖复杂度低中等较高(需安装PaddlePaddle)

提示:对于全新的项目,我建议你先用少量图片(20-30张)在三个工具上都试一下。感受一下各自的标注速度、操作手感,再决定主力使用哪个。很多时候,工具的选择也和个人习惯有关。

在实际项目中,我经常混合使用这些工具。例如,先用LabelImg快速拉出所有物体的矩形框,完成初筛和分类;对于其中需要精细分割的类别,再导入Labelme或EISeg进行多边形标注。这种流水线作业能兼顾速度和精度。

2. 避坑安装指南:一次性搞定所有依赖

安装环节是新手最容易卡住的地方。网上很多教程的安装命令已经过时,或者忽略了系统环境的差异。下面我提供的步骤,在Windows 10/11和Ubuntu 20.04/22.04上都亲自验证过,尽可能绕开了常见的依赖冲突。

2.1 基础Python环境搭建

强烈建议使用Anaconda或Miniconda来管理Python环境。这能完美解决不同项目间包版本冲突的问题。如果你已经安装了Python,可以跳过这一步。

# 以Miniconda为例,下载安装脚本(Linux/macOS)
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh

# Windows用户可直接从官网下载.exe安装包

安装完成后,创建一个专用于标注工作的虚拟环境:

conda create -n label_env python=3.8 -y
conda activate label_env

这里选择Python 3.8是因为它在深度学习生态中兼容性最好,绝大多数库的稳定版本都支持3.8。

2.2 LabelImg:最稳定的矩形框标注器

LabelImg的安装其实很简单,但很多人卡在PyQt5的安装上。我们使用pip直接安装编译好的轮子(wheel)即可。

# 1. 安装必要的系统依赖(Ubuntu/Debian)
sudo apt-get update
sudo apt-get install -y libxcb-xinerama0 libgl1-mesa-glx

# 2. 安装Python依赖和LabelImg
pip install pyqt5 lxml
pip install labelImg

安装完成后,直接在终端输入 labelImg 命令即可启动。如果提示找不到命令,很可能是你的Python脚本目录没有加入系统PATH。可以尝试用模块方式启动:

python -m labelImg

第一次启动可能会稍慢。界面打开后,你可以通过 Open Dir 打开图片目录,Change Save Dir 设置标签保存目录。快捷键能极大提升效率,务必记住这几个最常用的:

  • W:创建矩形框
  • Ctrl + S:保存当前图片的标注
  • D:下一张图片
  • A:上一张图片
  • Ctrl + Shift + S:更改标注格式(在VOC XML、YOLO、CreateML之间切换)

注意:在保存YOLO格式时,LabelImg需要你预先准备一个 classes.txt 文件,里面按行写入所有类别名称。把这个文件放在标签保存目录下,LabelImg会自动读取。

2.3 Labelme:多边形标注的瑞士军刀

Labelme的安装同样直接,但它对OpenCV的版本有一定要求。建议按照以下顺序安装:

# 1. 安装Labelme及其核心依赖
pip install labelme

# 2. 安装匹配的OpenCV(避免版本冲突)
pip install opencv-python==4.5.5.64
pip install opencv-contrib-python==4.5.5.64

安装后,运行 labelme 命令启动。它的界面比LabelImg稍复杂一些,但功能强大得多。除了多边形工具,还支持矩形、圆形、直线、点等多种标注类型。

Labelme工作流示例:

  1. 点击 Open Dir 加载图片文件夹。
  2. 使用左侧工具栏的 Polygon(多边形)工具,沿着物体边缘依次点击,最后点击第一个点闭合多边形。
  3. 在弹出的对话框中输入类别标签。
  4. 标注完一张图的所有物体后,点击 Save,会生成一个同名的 .json 文件。

这个JSON文件包含了图片路径、图像尺寸以及每个多边形的顶点坐标和类别。它是后续格式转换的基础。

2.4 EISeg:当AI成为你的标注助手

EISeg的安装相对复杂,因为它依赖于百度的PaddlePaddle深度学习框架。但为了其强大的AI辅助能力,这点复杂度是值得的。我们一步步来。

首先,安装PaddlePaddle。根据你是否有GPU,选择不同的版本。有NVIDIA GPU且已安装CUDA的用户强烈建议安装GPU版本,因为EISeg的交互模型推理会快很多。

# 选项A:安装CPU版本(通用,速度较慢)
python -m pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple

# 选项B:安装GPU版本(CUDA 11.2)
python -m pip install paddlepaddle-gpu==2.4.2.post112 -f https://www.paddlepaddle.org.cn/whl/linux/mkl/avx/stable.html

安装完PaddlePaddle后,再安装EISeg:

pip install eiseg

安装完成后,在终端输入 eiseg 启动。第一次启动时,软件会自动下载模型文件,或者提示你手动下载。建议根据你的标注场景选择合适的模型:

  • 通用场景:选择 hrnet18_ocr64_cocolvis,精度和速度平衡。
  • 人像分割:选择 hrnet18_ocr48_human,针对人像优化。
  • 遥感图像:选择 hrnet18_ocr48_rs。
  • 医疗图像:选择 hrnet18_ocr48_medical。

模型下载后,通过界面上的 加载模型 按钮导入。一个常见的报错是OpenMP库冲突(特别是Windows上),如果启动时看到 OMP: Error #15 之类的错误,可以在你的Python脚本或命令行中设置环境变量解决:

# 在启动eiseg前设置
set KMP_DUPLICATE_LIB_OK=TRUE  # Windows
export KMP_DUPLICATE_LIB_OK=TRUE  # Linux/macOS

或者更一劳永逸的方法是,找到EISeg的启动脚本或创建一个批处理文件,预先设置这个变量。

EISeg的使用体验非常直观:在想要分割的物体内部点击一下(正样本点),在物体外部背景处点击一下(负样本点),模型就会实时生成分割结果。你可以通过不断添加正负点来修正预测的掩膜,直到满意为止。

3. 格式转换实战:打通标注到训练的“最后一公里”

工具标注出的原始格式,很少能直接用于训练。这就是格式转换脚本大显身手的地方。很多人在这里放弃,其实只要理解逻辑,转换并不难。

3.1 Labelme JSON 转 YOLO TXT

YOLO格式的标签文件是一个 .txt 文件,每行代表一个物体,格式为:class_id x_center y_center width height。这里的坐标和宽高都是归一化后的值(即除以图片宽度和高度)。

Labelme的JSON里存储的是多边形的绝对坐标点。我们需要做的是:

  1. 将多边形顶点转换为一个最小的外接矩形。
  2. 计算这个矩形的中心点、宽度和高度。
  3. 将其归一化。

下面是一个我修改过的、更健壮的转换脚本:

import json
import os
import numpy as np
from pathlib import Path

def labelme_json_to_yolo_txt(json_path, output_dir, class_list):
    """
    将单个Labelme JSON文件转换为YOLO格式的TXT文件。
    Args:
        json_path: Labelme JSON文件路径
        output_dir: 输出TXT文件目录
        class_list: 类别名称列表,如 ['person', 'car', 'dog']
    """
    with open(json_path, 'r', encoding='utf-8') as f:
        data = json.load(f)

    img_height = data['imageHeight']
    img_width = data['imageWidth']

    txt_lines = []
    for shape in data['shapes']:
        label = shape['label']
        points = np.array(shape['points'])

        # 确保类别在列表中
        if label not in class_list:
            print(f"警告:在{json_path}中发现未知类别 '{label}',已跳过。")
            continue
        class_id = class_list.index(label)

        # 计算多边形的最小外接矩形
        x_coords = points[:, 0]
        y_coords = points[:, 1]
        x_min, x_max = np.min(x_coords), np.max(x_coords)
        y_min, y_max = np.min(y_coords), np.max(y_coords)

        # 计算中心点和宽高,并归一化
        x_center = (x_min + x_max) / 2.0
        y_center = (y_min + y_max) / 2.0
        width = x_max - x_min
        height = y_max - y_min

        x_center_norm = x_center / img_width
        y_center_norm = y_center / img_height
        width_norm = width / img_width
        height_norm = height / img_height

        # 写入一行,保留6位小数
        line = f"{class_id} {x_center_norm:.6f} {y_center_norm:.6f} {width_norm:.6f} {height_norm:.6f}"
        txt_lines.append(line)

    # 写入TXT文件
    json_stem = Path(json_path).stem
    txt_path = os.path.join(output_dir, f"{json_stem}.txt")
    with open(txt_path, 'w') as f:
        f.write('\n'.join(txt_lines))
    print(f"已转换: {json_path} -> {txt_path}")

# 批量转换示例
if __name__ == '__main__':
    json_dir = './labelme_annotations'  # 你的JSON文件夹
    output_dir = './yolo_labels'        # 输出文件夹
    classes = ['cat', 'dog', 'person'] # 你的类别,顺序很重要!

    os.makedirs(output_dir, exist_ok=True)
    for json_file in Path(json_dir).glob('*.json'):
        labelme_json_to_yolo_txt(json_file, output_dir, classes)

这个脚本增加了错误处理(未知类别跳过)、更精确的浮点数格式,并且使用了 pathlib 模块,路径处理更安全。

3.2 Labelme JSON 转 VOC XML

PASCAL VOC格式是另一种常见的XML格式,被很多早期框架使用。转换逻辑类似,但需要构建XML树。使用Python的 xml.etree.ElementTree 可以方便地完成。

import xml.etree.ElementTree as ET
from xml.dom import minidom

def json_to_voc_xml(json_data, xml_save_path):
    root = ET.Element('annotation')

    # 添加文件夹、文件名、路径等基本信息
    folder = ET.SubElement(root, 'folder')
    folder.text = 'images'
    filename_elem = ET.SubElement(root, 'filename')
    filename_elem.text = json_data['imagePath']

    size = ET.SubElement(root, 'size')
    ET.SubElement(size, 'width').text = str(json_data['imageWidth'])
    ET.SubElement(size, 'height').text = str(json_data['imageHeight'])
    ET.SubElement(size, 'depth').text = '3'  # 假设为RGB图像

    for shape in json_data['shapes']:
        obj = ET.SubElement(root, 'object')
        ET.SubElement(obj, 'name').text = shape['label']
        ET.SubElement(obj, 'pose').text = 'Unspecified'
        ET.SubElement(obj, 'truncated').text = '0'
        ET.SubElement(obj, 'difficult').text = '0'

        bndbox = ET.SubElement(obj, 'bndbox')
        points = np.array(shape['points'])
        xmin, ymin = np.min(points[:, 0]), np.min(points[:, 1])
        xmax, ymax = np.max(points[:, 0]), np.max(points[:, 1])
        ET.SubElement(bndbox, 'xmin').text = str(int(round(xmin)))
        ET.SubElement(bndbox, 'ymin').text = str(int(round(ymin)))
        ET.SubElement(bndbox, 'xmax').text = str(int(round(xmax)))
        ET.SubElement(bndbox, 'ymax').text = str(int(round(ymax)))

    # 美化输出XML
    rough_string = ET.tostring(root, 'utf-8')
    reparsed = minidom.parseString(rough_string)
    pretty_xml = reparsed.toprettyxml(indent="  ")

    with open(xml_save_path, 'w', encoding='utf-8') as f:
        f.write(pretty_xml)

3.3 EISeg COCO JSON 的转换与处理

EISeg可以导出COCO格式的JSON。COCO格式是一个集中式的标注文件,它包含了整个数据集的所有图片信息和标注信息,与Labelme的每图一个JSON不同。

处理COCO JSON时,我们通常需要根据任务需求进行拆分。例如,YOLO训练需要的是每张图片对应的TXT文件。我们可以使用 pycocotools 这个官方库来解析COCO JSON。

pip install pycocotools

然后使用下面的脚本片段进行提取和转换:

from pycocotools.coco import COCO

coco_anno_path = './eiseg_annotations/annotations.json'
coco = COCO(coco_anno_path)

# 获取所有图片ID
img_ids = coco.getImgIds()
for img_id in img_ids:
    img_info = coco.loadImgs(img_id)[0]
    ann_ids = coco.getAnnIds(imgIds=img_id)
    annotations = coco.loadAnns(ann_ids)

    # 针对每个annotation,提取bbox并转换为YOLO格式
    # ... 转换逻辑与之前类似,注意COCO的bbox格式是 [x_top_left, y_top_left, width, height]

4. 高效标注心法与高级技巧

工具装好了,格式也会转了,接下来就是如何真正提升标注效率和质量。这部分是我在大量标注工作中总结出的“软技能”。

4.1 建立标准化的标注流程

混乱的文件夹结构是噩梦的开始。在开始任何标注项目前,先建立这样的目录结构:

your_project/
├── images/
│   ├── train/
│   ├── val/
│   └── test/
├── labels/ (或 annotations/)
│   ├── labelme_json/   # 存放Labelme原始JSON
│   ├── yolo_txt/       # 存放转换后的YOLO标签
│   └── voc_xml/        # 存放转换后的VOC XML
├── classes.txt         # 统一的类别定义文件
└── scripts/            # 存放所有转换脚本

关键点:

  • 图片和标签的对应关系必须清晰。通常通过相同的文件名(仅后缀不同)来关联。
  • classes.txt 文件一旦确定,绝对不要中途更改类别顺序。否则所有已转换的标签文件中的类别ID都会错乱。
  • 对数据集进行训练集/验证集/测试集的划分,应该在标注完成之后、转换格式之前进行。你可以先划分好图片,然后分别对每个子集进行标注和转换。

4.2 多人协作标注的策略

当项目需要标注的数据量很大时,一个人无法完成,就需要团队协作。

  1. 统一标准会议:在开始前,召集所有标注人员,用一批示例图片进行标注演练。明确边界怎么划(例如,被遮挡的物体画到哪里?反光部分算不算?),类别如何定义(“SUV”和“轿车”要不要分开?)。
  2. 制作标注规范文档:将会议确定的标准写成文档,并配上正确和错误的标注示例图。这份文档是后续质检的依据。
  3. 使用中间格式:让所有人都使用同一种工具(如Labelme)进行标注,输出原始格式(JSON)。由专人负责统一进行格式转换和质量检查。这样可以避免每个人环境不同导致的格式混乱。
  4. 定期质检与校准:负责人定期抽查每个人的标注结果,发现问题及时反馈和纠正,防止错误标注大面积扩散。

4.3 利用预标注与主动学习

对于已有初步模型的项目,可以采用更智能的流程:

  1. 预标注:用现有的、哪怕精度不高的模型,对未标注的图片进行推理,生成初步的标注框或掩膜。
  2. 人工修正:标注员只需要在这些预标注的结果上进行修改和确认,而不是从零开始画。这通常能节省50%以上的时间。
  3. 主动学习:将模型预测不确定度高的那些图片(例如,置信度在0.3-0.7之间的)优先交给人工标注。用最少的人工干预,最大化地提升模型性能。

EISeg的交互式标注本身就是一种“微型的主动学习”,它把模型的预测和人工的修正实时结合在一起。

4.4 质量控制与常见陷阱

即使流程再规范,标注错误也难以完全避免。以下是一些高频陷阱和检查方法:

  • 标签错位:最常见的问题。检查方法是写一个简单的可视化脚本,把标注框画回原图上看是否对齐。
  • 类别混淆:特别是相似类别(如“狼”和“哈士奇”)。需要质检员重点复核。
  • 漏标:尤其是小目标。可以通过训练一个简单模型,用模型在验证集上的“假阴性”(False Negative)结果来反查可能漏标的物体。
  • 标注不一致:同一物体在不同图片中,有的标得紧贴边缘,有的留了白边。这会影响模型学习边界的精度。需要在规范中明确边缘的处理方式。

我习惯在转换格式后,随机抽取5%-10%的标签文件,用可视化脚本快速过一遍。花半小时做这个检查,能避免后续几天模型训练走弯路。

说到底,数据标注是一项需要耐心和细心的工作,但它绝不是毫无技术含量的重复劳动。选择合适的工具,建立高效的流程,运用一些智能化的技巧,你就能把这块“绊脚石”变成项目快速推进的“垫脚石”。当你看到自己亲手标注的数据训练出的模型,在真实场景中稳定工作时,那种成就感,和调出一个好模型是完全不同的。

更多推荐