🧠 第四章 数据标注:引导模型学习提升性能
📄 一句话概括

COCO 的 [50, 60, 120, 80] 转成 YOLO 的 0 0.171875 0.208333 0.187500 0.166667, 回算 3/3 一致。COCO 从 1 编号、YOLO 从 0 编号——差 1 就全错位。

📝 本节和前面几节的关系

上一节把名字理顺了。这一节把标注导出成训练能吃的格式,重点讲两个最容易出错的地方。

一、导出就是一次格式翻译

标注工具里存的是一套结构,训练框架要的是另一套结构。导出就是中间那次翻译。 翻译要动三样东西:坐标的表示方式、类别的编号方式、文件怎么组织。三样里任何一样错了, 训练都能跑起来,但结果全错。 这就是这一节的核心风险:格式错误不会报错,只会让 loss 降不下去。你会在那里调学习率、换优化器、 怀疑模型结构,而真正的问题在坐标多除了一个宽度。 所以导出的正确做法不是「点了导出就完事」,而是导出之后回算一遍——把导出的数按逆公式算回去, 跟原始值比。差值小于容差,才算转对了。

二、COCO 和 YOLO 差在哪

COCO 的 bbox 是 [x, y, w, h]:左上角的像素坐标,加上宽和高。单位是像素,数值范围是图像尺寸。 YOLO 的是 cx cy w h:中心点坐标加上宽高,而且全部除以图像宽高归一化到 0 到 1。 所以转换公式是:cx = (x + w/2) / W,cy = (y + h/2) / H,w' = w / W,h' = h / H。 两个坑:一是中心点(COCO 给左上角,YOLO 要中心,忘了加 w/2 就整体偏移半个框); 二是除以谁(cx 除以图宽、cy 除以图高,除反了框会被拉伸)。 第三个坑是类别编号: COCO 的 category_id 从 1 开始,YOLO 的 class 从 0 开始。 直接照抄就整体错一位—— 所有汽车被当成行人,所有行人被当成红绿灯。

三、常见格式一览

不同框架吃不同格式,导出前先确认下游要什么:

格式 坐标表示 典型用途
COCO JSON x,y,w,h 像素 通用交换、评测
YOLO txt cx,cy,w,h 归一化 YOLO 系列训练
VOC XML xmin,ymin,xmax,ymax 老项目、Pascal VOC
CSV 自定义列 交付、人工核对
同一个标注,四种写法。 所以「导出」这件事不能只点一次——要按下游需求导出对应的那一份,
并且留一份 COCO 原样存档。
存档那份很重要:COCO 是最中立的中间格式,其他格式基本都能从它转出去,反过来不一定。
所以项目一开始就存 COCO,后面要什么都能转。

四、在 MakeSense 里导出

📋 实操清单(要在浏览器里点): 第一步,点左侧或顶部的 Actions 菜单,选 Export Annotations。 第二步,选格式。MakeSense 支持 COCO、YOLO、VOC、CSV、TFRecord 等几种。 做检测选 COCO 或 YOLO,做分类选 CSV。 第三步,如果选 YOLO,工具会打成一个 zip——里面是每张图一个 txt,加一个 classes.txt 记录类别顺序。 这个 classes.txt 的顺序就是类别编号,别丢。 第四步,下载后立刻跑一次回算检查。这一步不能省。 🐍 沙箱等价物:转换和回算见 Demo。 沙箱里点不了导出按钮,但「转完对不对」是纯数学——回算一遍,差值小于千分之一就算过。 这个检查脚本应该放进每次导出的流程里,而不是出了问题才写。

五、动手 Demo:COCO 转 YOLO:三个框回算 3/3 一致,类别编号要减 1

造一份 COCO 格式的标注(640×480 的图、3 个框、2 个类别),按公式转成 YOLO 的 class cx cy w h, 写出文件,再把 YOLO 的数按逆公式算回去,跟 COCO 的原始值比对。

for a in coco[u'annotations']:
    x, y, w, h = a[u'bbox']
    cx = (x + w / 2.0) / W          # 注意加 w/2:COCO 给左上角
    cy = (y + h / 2.0) / Hh
    lines.append(u'%d %.6f %.6f %.6f %.6f'
                 % (cat2i[a[u'category_id']], cx, cy,
                    w / float(W), h / float(Hh)))

# 回算:YOLO 反推回 COCO,看跟原值差多少
bx = (cx - ww / 2.0) * W
by = (cy - hh / 2.0) * Hh

实跑输出:

--------------------------------------------------------------------
导出数据:坐标从 COCO 换到 YOLO,差一个除法和一个中心点
--------------------------------------------------------------------
  源格式                   COCO(bbox = x, y, 宽, 高,单位像素)
  目标格式                 YOLO(cx, cy, 宽, 高,全部归一化到 0 到 1)
  图像尺寸                 640 × 480
  标注数                   3 个

  转换公式:cx = (x + w/2) / W,cy = (y + h/2) / H,w' = w / W,h' = h / H。
  **两个容易错的地方:COCO 给的是左上角加宽高,YOLO 要的是中心点。**
  另一个是类别编号:COCO 从 1 开始,YOLO 从 0 开始,差 1 就全错位。
  标注 id   COCO                           YOLO
  -----------------------------------------------------------------------------
  1         [50.0, 60.0, 120.0, 80.0]      0 0.171875 0.208333 0.187500 0.166667
  2         [300.0, 200.0, 60.0, 150.0]    1 0.515625 0.572917 0.093750 0.312500
  3         [420.0, 320.0, 180.0, 100.0]   0 0.796875 0.770833 0.281250 0.208333

  写出来的文件(每行一个框,对应一张图的 txt):
    0 0.171875 0.208333 0.187500 0.166667
    1 0.515625 0.572917 0.093750 0.312500
    0 0.796875 0.770833 0.281250 0.208333

  转完必须回算一遍,确认没有精度损失:
  回算一致                 3/3

  最后核对三件事,一件都不能少:
  ① 框数                   COCO 里 3 个,txt 里也得 3 行
  ② 类别映射               COCO 从 1 开始,YOLO 从 0 开始,差 1 是最常见的错
  ③ 取值范围               归一化后所有数都该在 0 到 1 之间,超了就是算错
  **这三条都是「不检查也不会报错」的那一类——训练能跑,但框全歪了,loss 就是不降。**

done -> _out_39f.txt

表里三行,COCO 和 YOLO 一一对应。 看第一个:[50, 60, 120, 80] → 0 0.171875 0.208333 0.187500 0.166667。 手算一遍验证:cx = (50 + 60) / 640 = 0.171875,w' = 120 / 640 = 0.1875。 加的是 w/2 不是 w。 类别那一列:COCO 的 category_id 是 1 和 2,YOLO 里变成 0 和 1。 这一位差最容易漏,而且漏了不报错。 回算那一段是关键:把 YOLO 的数按逆公式还原成 COCO,跟原始值比,3/3 一致(容差千分之一)。 这就是「导出之后必须自检」的具体做法。 最后三项核对里,第三项「取值范围」也很好用:归一化之后所有数都该在 0 到 1 之间。 如果出现大于 1 的数,一定是忘了除或者除错了。 一条断言就能拦住。

30 秒实验

  1. 故意把 cx 的公式改成 x / W(忘了加 w/2),再跑回算,看偏差是多少。 偏差正好是半个框宽,这就是「整体偏移」的样子。
  2. 故意把 cx 和 cy 的除数对调,看回算对不上的是哪几个数。 体会一下「除反了」和「忘了加」在数值上的区别。
  3. 把类别编号改成直接从 1 开始(不减 1),看文件长什么样。 它完全合法,训练也能跑——这就是最难发现的那种错。

常见误区

  1. 「点了导出就完事」。格式错误不报错,只让 loss 降不下去。必须回算自检。
  2. 「COCO 和 YOLO 只是括号写法不同」。差的是中心点、归一化、类别起始值三件事,每件都能让训练失效。
  3. 「只存一种格式就够了」。建议始终存一份 COCO 当中间格式,要别的从它转;反过来不一定转得回去。
自测三问(点开对答案) 1. COCO 的 bbox 和 YOLO 的坐标差在哪三件事上? —— 左上角对中心点、像素对归一化、类别从 1 开始对从 0 开始。三件里任何一件错了,框都会整体偏掉。 2. 为什么导出后要回算一遍? —— 因为格式错误不会报错,训练照跑但 loss 不降。回算是唯一能自动发现「坐标算错了」的办法。 3. 为什么要留一份 COCO 格式存档? —— 因为 COCO 是最中立的中间格式,其他格式基本都能从它转出去,反过来不一定。 留它等于留了所有可能性。

第四章到这里结束:从「为什么标」到「怎么导」,一条线走完了。 第五章开始讲微调——把标注好的数据真正用起来。

相关笔记