从AnyLabeling到YOLO:高效数据标注与模型训练全流程实战

1次阅读
没有评论

共计 3987 个字符,预计需要花费 10 分钟才能阅读完成。

image.webp

背景痛点

在目标检测任务中,数据标注和模型训练往往是割裂的两个环节。传统标注工具(如 LabelImg)生成的标注文件格式(如 Pascal VOC)与 YOLO 训练框架所需的格式不一致,导致开发者需要花费大量时间进行格式转换和手动调整。这不仅降低了工作效率,还容易引入人为错误。

从 AnyLabeling 到 YOLO:高效数据标注与模型训练全流程实战

技术方案

AnyLabeling 标注数据格式解析

AnyLabeling 支持多种标注格式,默认输出为 JSON 格式。一个典型的 JSON 标注文件如下:

{
  "version": "0.1.0",
  "flags": {},
  "shapes": [
    {
      "label": "person",
      "points": [[100, 200],
        [300, 400]
      ],
      "group_id": null,
      "shape_type": "rectangle",
      "flags": {}}
  ],
  "imagePath": "example.jpg",
  "imageData": null,
  "imageHeight": 1080,
  "imageWidth": 1920
}

格式转换脚本示例

以下 Python 脚本将 AnyLabeling 的 JSON 标注转换为 YOLO 格式:

import json
import os

def convert_anylabeling_to_yolo(json_path, output_dir, class_mapping):
    """
    Convert AnyLabeling JSON annotations to YOLO format.
    :param json_path: Path to the JSON annotation file
    :param output_dir: Directory to save YOLO format annotations
    :param class_mapping: Dictionary mapping class names to class IDs
    """
    try:
        with open(json_path, 'r') as f:
            data = json.load(f)

        image_width = data['imageWidth']
        image_height = data['imageHeight']

        txt_filename = os.path.splitext(os.path.basename(json_path))[0] + '.txt'
        txt_path = os.path.join(output_dir, txt_filename)

        with open(txt_path, 'w') as f:
            for shape in data['shapes']:
                label = shape['label']
                if label not in class_mapping:
                    continue

                class_id = class_mapping[label]
                points = shape['points']

                # Convert absolute coordinates to relative coordinates
                x_center = (points[0][0] + points[1][0]) / 2 / image_width
                y_center = (points[0][1] + points[1][1]) / 2 / image_height
                width = abs(points[1][0] - points[0][0]) / image_width
                height = abs(points[1][1] - points[0][1]) / image_height

                f.write(f"{class_id} {x_center} {y_center} {width} {height}\n")
    except Exception as e:
        print(f"Error converting {json_path}: {str(e)}")

# Example usage
class_mapping = {"person": 0, "car": 1, "dog": 2}
convert_anylabeling_to_yolo("example.json", "yolo_labels", class_mapping)

YOLO 数据集目录结构规范

YOLO 期望的数据集目录结构如下:

dataset/
├── images/
│   ├── train/
│   ├── val/
│   └── test/
└── labels/
    ├── train/
    ├── val/
    └── test/

关键配置文件调整

data.yaml 示例:

train: ../dataset/images/train
val: ../dataset/images/val

# number of classes
nc: 3

# class names
names: ['person', 'car', 'dog']

实现细节

标注坐标系的转换原理

YOLO 使用相对坐标(0- 1 之间),而 AnyLabeling 输出的是绝对坐标(像素值)。转换公式如下:

  • x_center = (x1 + x2) / 2 / image_width
  • y_center = (y1 + y2) / 2 / image_height
  • width = (x2 – x1) / image_width
  • height = (y2 – y1) / image_height

多类别标签的映射策略

建议在项目开始时建立明确的类别映射表,并在整个项目中保持一致。可以使用字典来维护这种映射关系。

数据集自动划分脚本

以下脚本将数据集划分为 train/val/test:

import os
import random
import shutil

def split_dataset(image_dir, label_dir, output_dir, ratios=(0.7, 0.2, 0.1)):
    """
    Split dataset into train/val/test sets.
    :param image_dir: Directory containing images
    :param label_dir: Directory containing labels
    :param output_dir: Root directory for output
    :param ratios: Tuple of (train_ratio, val_ratio, test_ratio)
    """
    # Create output directories
    os.makedirs(os.path.join(output_dir, 'images', 'train'), exist_ok=True)
    os.makedirs(os.path.join(output_dir, 'images', 'val'), exist_ok=True)
    os.makedirs(os.path.join(output_dir, 'images', 'test'), exist_ok=True)
    os.makedirs(os.path.join(output_dir, 'labels', 'train'), exist_ok=True)
    os.makedirs(os.path.join(output_dir, 'labels', 'val'), exist_ok=True)
    os.makedirs(os.path.join(output_dir, 'labels', 'test'), exist_ok=True)

    # Get all image files
    image_files = [f for f in os.listdir(image_dir) if f.endswith(('.jpg', '.png'))]
    random.shuffle(image_files)

    # Calculate split indices
    total = len(image_files)
    train_end = int(total * ratios[0])
    val_end = train_end + int(total * ratios[1])

    # Split and copy files
    for i, filename in enumerate(image_files):
        basename = os.path.splitext(filename)[0]
        label_file = basename + '.txt'

        if i < train_end:
            subset = 'train'
        elif i < val_end:
            subset = 'val'
        else:
            subset = 'test'

        # Copy image
        shutil.copy(os.path.join(image_dir, filename),
                   os.path.join(output_dir, 'images', subset, filename))

        # Copy label if exists
        if os.path.exists(os.path.join(label_dir, label_file)):
            shutil.copy(os.path.join(label_dir, label_file),
                       os.path.join(output_dir, 'labels', subset, label_file))

# Example usage
split_dataset("raw_images", "raw_labels", "dataset")

避坑指南

标注质量检查要点

  1. 检查是否有漏标的对象
  2. 验证标注框是否准确覆盖目标
  3. 确保类别标签正确无误
  4. 检查标注框是否超出图像边界

常见格式错误排查

  1. 文件编码问题:确保使用 UTF- 8 编码
  2. 路径问题:使用绝对路径或正确的相对路径
  3. 格式错误:YOLO 标签文件每行应为 ”class_id x_center y_center width height”
  4. 图像与标签不匹配:检查文件名是否一致

小样本场景下的数据增强建议

  1. 使用 Albumentations 或 torchvision 进行数据增强
  2. 考虑使用 Mosaic 增强
  3. 尝试复制少量样本并进行随机变换
  4. 使用迁移学习或预训练模型

延伸思考

  1. 如何处理标注数据中的类别不平衡问题?
  2. 在实时标注场景下,如何优化标注到训练的流程?
  3. 对于大规模数据集,如何设计更高效的标注 - 训练迭代流程?
正文完
 0
评论(没有评论)