1. 为什么需要从Labelme转换到COCO格式

当你用Labelme标注完几百张图片后,看着密密麻麻的json文件可能会突然意识到一个问题:这些标注数据怎么才能喂给主流的深度学习模型训练?这时候COCO格式就闪亮登场了。COCO(Common Objects in Context)是目前计算机视觉领域最通用的数据集格式之一,像YOLOv5、Mask R-CNN这些明星模型都直接支持COCO格式的数据加载。

我去年帮一家电商客户做商品检测时就踩过这个坑。他们的标注团队用Labelme标注了上万张商品图片,结果发现训练代码读不懂这些数据。后来我们花了三天时间研究格式转换,期间还因为category_id映射错误导致模型把"手机"识别成"鼠标"。所以今天我就把完整的避坑指南整理出来,让你少走弯路。

COCO格式的核心优势在于它的结构化设计。一个标准的COCO格式json文件包含四个关键部分:

  • images:记录所有图片的基本信息(宽度、高度、文件名等)
  • annotations:保存每个实例的详细标注(bbox坐标、分割多边形、所属类别等)
  • categories:定义所有类别名称和ID的对应关系
  • licenses(可选):标注数据的使用许可信息

这种结构特别适合处理一张图片包含多个目标实例的场景。相比之下,Labelme的每个json文件只对应单张图片的标注,训练前需要先整合成COCO这种"图片库+标注池"的形式。

2. 转换前的准备工作

2.1 检查你的Labelme标注质量

在开始转换前,强烈建议先做一轮数据质检。我遇到过最离谱的情况是标注员把全部bbox的坐标都存成了字符串格式,导致转换脚本直接报错。这里分享几个快速检查的小技巧:

# 用jq工具快速查看json结构
sudo apt install jq  # Ubuntu安装命令
jq '.' example.json | head -20

# 检查标注文件与图片是否匹配
for f in *.json; do 
    img=${f%.*}.jpg
    if [ ! -f "$img" ]; then
        echo "缺失图片: $img"
    fi
done

常见问题包括:

  1. 图片路径错误(绝对路径导致在其他机器无法读取)
  2. 存在空标注文件(标注时误保存)
  3. 多边形坐标点数量不足(小于3个点无法构成闭合区域)
  4. 类别名称不一致(比如"car"和"Car"会被视为不同类别)

2.2 准备类别映射文件

COCO格式要求每个类别必须有唯一的整数ID。建议创建一个labels.txt文件来明确定义这种映射关系。例如:

__ignore__
background
person
car
dog

注意第一行的__ignore__是Labelme的特殊标识,对应的category_id会是-1。从第二行开始,category_id从0开始递增。这个文件要保存好,因为训练模型时也需要完全一致的类别顺序。

3. 两种转换方法实战

3.1 使用Labelme官方工具

Labelme其实自带了转换脚本,藏在源码的examples/instance_segmentation目录下。安装依赖时要注意版本兼容性:

# 推荐使用Python虚拟环境
python -m venv labelme2coco
source labelme2coco/bin/activate

# 安装指定版本依赖
pip install labelme==5.1.1 pycocotools==2.0.4 imgviz==1.7.1

转换命令示例:

python labelme2coco.py \
    --input_dir ./labelme_annotations \
    --output_dir ./coco_dataset \
    --labels labels.txt \
    --noviz  # 跳过可视化可加快速度

这个脚本会自动做三件事:

  1. 将所有图片复制到JPEGImages目录
  2. 生成annotations.json
  3. 为每张图片生成标注可视化效果图(除非加--noviz参数)

常见报错处理:

  • "AttributeError: 'NoneType' object has no attribute 'shape'":通常是图片读取失败,检查json中的imagePath字段
  • "KeyError: 'xxx' in class_name_to_id":labels.txt中缺少对应的类别
  • "ValueError: invalid literal for int() with base 10":标注坐标中包含非数字字符

3.2 自定义转换脚本

当需要特殊处理时(比如合并多个数据集),可能需要自己写转换逻辑。以下是核心代码解析:

def shape_to_coco(shape, image_id, category_id, annotation_id):
    """将Labelme的单个shape转换为COCO标注格式"""
    points = np.array(shape["points"])
    segmentation = points.flatten().tolist()
    
    # 计算bbox [x,y,width,height]
    x_min, y_min = np.min(points, axis=0)
    x_max, y_max = np.max(points, axis=0)
    bbox = [x_min, y_min, x_max - x_min, y_max - y_min]
    
    # 计算面积(多边形面积公式)
    area = 0.5 * np.abs(np.dot(points[:,0], np.roll(points[:,1],1)) 
                       - np.dot(points[:,1], np.roll(points[:,0],1)))
    
    return {
        "id": annotation_id,
        "image_id": image_id,
        "category_id": category_id,
        "segmentation": [segmentation],
        "area": float(area),
        "bbox": bbox,
        "iscrowd": 0  # 0表示单个对象,1表示一组对象
    }

处理矩形标注时要特别注意:Labelme存储的是对角两点坐标,而COCO需要转为[x,y,width,height]格式。实例分割中的group_id字段也要妥善处理,它用于区分同一类别的不同实例。

4. 转换后的数据验证

4.1 结构检查

用Python快速验证生成的COCO文件:

import json
from pycocotools.coco import COCO

with open("annotations.json") as f:
    data = json.load(f)

print(f"图片数量: {len(data['images'])}")
print(f"标注数量: {len(data['annotations'])}")
print(f"类别数量: {len(data['categories'])}")

# 检查标注与图片的对应关系
coco = COCO("annotations.json")
img_ids = coco.getImgIds()
ann_ids = coco.getAnnIds(imgIds=img_ids[:1])
print(f"第一张图片的标注数: {len(ann_ids)}")

4.2 可视化验证

安装labelme后可以直接查看COCO格式标注:

labelme --labels labels.txt --input JPEGImages/ --annotations annotations.json

重点检查:

  1. 所有标注框是否与物体对齐
  2. 实例分割边缘是否精确
  3. 类别标签是否正确
  4. 是否有漏标的物体

5. 进阶技巧与避坑指南

5.1 处理crowd区域

当标注密集小物体(如人群)时,可能需要设置iscrowd=1。这时bbox应该覆盖整个区域,而segmentation可以是简化的轮廓。在Labelme中可以通过给标注添加iscrowd属性实现:

{
  "shape_type": "polygon",
  "label": "person",
  "points": [[...]],
  "group_id": 1,
  "iscrowd": 1
}

5.2 多数据集合并

合并多个COCO格式数据集时要注意:

  1. 统一category_id映射
  2. 重新编排image_id和annotation_id
  3. 处理可能重复的文件名

建议使用COCO API的merge函数:

from pycocotools.coco import COCO

coco1 = COCO("dataset1.json")
coco2 = COCO("dataset2.json")

merged = COCO()
merged.dataset = {
    "images": coco1.dataset["images"] + coco2.dataset["images"],
    "annotations": coco1.dataset["annotations"] + coco2.dataset["annotations"],
    "categories": coco1.dataset["categories"]  # 假设类别一致
}

5.3 性能优化

当处理上万张图片时,转换过程可能很耗时。几个优化建议:

  1. 使用多进程处理(Python的multiprocessing模块)
  2. 先将所有图片调整为统一尺寸,减少I/O压力
  3. 使用更快的json库(如orjson)替代标准json模块
import orjson

# 比标准json快5-10倍
with open("big.json", "rb") as f:
    data = orjson.loads(f.read())
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐