简介:Ybat 是一款面向目标检测标注场景的轻量级边界框注释工具,专为 YOLO 格式设计,同时兼容 Pascal VOC 与 COCO 格式,适合算法工程师、数据标注人员及计算机视觉学习者快速整理训练数据集。工具采用纯浏览器运行方式,无需安装环境或上传图片,所有图像均在本地处理,并支持缩放平移辅助精确框选、快速导航、意外刷新自动保存、裁剪框并导出图像等实用功能。压缩包中共有 15 个文件,大小约 215KB,主要包含网页启动页面、脚本逻辑及依赖库、样式表、说明文档和类别标签文本,结构简洁、便于直接使用。项目源码体积小巧,利于阅读主逻辑并修改参数配置扩展类别,对理解前端图像标注工具的实现也很有参考价值。目前已有 78 人学习下载。
1. Ybat:YOLO BBox 注释工具,一个把“拉框”从体力活变成流水线的桌面工具
准备过 YOLO 数据集的人都经历过这种尴尬:图早就选好了,模型结构也调通了,结果一周时间全耗在“拉框”上。Ybat 正是冲着这个痛点来的——它是一款开箱即用的 YOLO BBox 注释工具,打开图片目录、拖一个矩形、点一下保存,标注结果直接落到 YOLO 需要的 txt 文件里,不用自己写转换脚本,也不用对着 COCO 的 JSON 发愁。它的核心价值就一句话:把标注输出格式固定成 YOLO 标准,让你从第一步就在正确的坐标系里干活。适合单人标注小数据集、快速做 demo、给 yolo 入门训练准备首批样本的开发者。如果你要的是多边形分割或视频追踪标注,它不是最优解,但纯 bbox 场景下,它是“解压即用”里最省事的那个。
2. 拿到 zip 后先别双击:Ybat 的定位、格式与运行前提
2.1 为什么标注格式必须先谈 YOLO 坐标系:归一化让损失函数少背锅
Ybat 输出的是 YOLO 风格的标注。很多第一次做 yolo 数据集的人打开 txt 文件时会愣住:里面的数字 0.523 0.441 0.231 0.678 既不是像素值,也不是直观的“左上角右下角”坐标。要理解 Ybat 里那个保存按钮在替你做什么,先得看懂这套坐标系。
YOLO 的 bbox 标注用的是相对坐标,一行五个数:类别 ID、x_center、y_center、width、height。其中后四个数全部是归一化后的比例值,取值范围在 0 到 1 之间。换算公式并不复杂:
x_center = ((xmin + xmax) / 2) / image_width
y_center = ((ymin + ymax) / 2) / image_height
width = (xmax - xmin) / image_width
height = (ymax - ymin) / image_height
算的是“目标中心在图片的什么位置、目标占了图片多大比例”。这一设计贯穿 yolo 原理的始终:不管输入图片被缩放到 640×640 还是 1280×1280,坐标值始终是 0 到 1 的相对量,模型在特征图上回归时只需要预测偏移量,不需要关心原始分辨率。
归一化的直接好处是标注结果和图像尺寸解耦。同一张 1920×1080 的图和它缩略到 320×180 的版本,标注内容完全相同。这也让 yolo 损失函数计算坐标误差时不至于因为某张图特别大而“带偏”梯度——这是自写标注脚本最容易踩漏的地方:像素坐标一进损失函数,大图的目标天然有更高权重,模型学出来的框在中小分辨率下会系统性偏移。所以你看,Ybat 这类工具存在的意义不只是省手,是直接省掉一套容易出错的自制转换逻辑。
2.2 Ybat 和 LabelImg、labelme 的定位差异:什么时候值得用它
标注工具不少,选型比标注本身更影响效率。下面这张对比表是我在不同项目里的实际体感,不按功能多少排优劣,只看“做 yolo 数据集时谁少折腾”。
| 工具 | 标注粒度 | 输出格式 | 是否需要二次转换 | 适合场景 |
|---|---|---|---|---|
| LabelImg | 矩形框 | PASCAL VOC XML / YOLO txt | 选 YOLO 模式可免转换 | 通用目标检测,续接老项目顺手 |
| labelme | 多边形、矩形 | JSON | 必须写脚本转成 YOLO | 想做分割又想顺带出检测框 |
| Ybat | 矩形框 | YOLO txt 直接输出 | 零转换 | 纯 BBox 快速量产,小数据集冲刺 |
| CVAT | 矩形、多边形、跟踪 | 多种导出格式 | 要装服务端,导出需配插件 | 团队协作、大批量多人标注 |
选择 Ybat 的理由,我总结成三条。第一,输出格式天然是 YOLO txt,不需要在 XML、JSON 之间来回倒腾,少写一个转换脚本就少一个出错点。第二,它足够轻量,解压就能跑,不依赖数据库、不需要起服务端,对一台普通工作站完全够用。第三,操作路径短:打开目录、连续画框、自动保存,比功能臃肿的在线平台更适合一个人埋头干活。
什么时候别用它?如果你的数据集包含多边形分割标注,或者你要做视频序列的目标追踪,Ybat 的矩形框模型满足不了需求。那属于 labelme 或 CVAT 的领域。认清这个边界,才能让工具为流程服务,而不是为工具调整流程。
2.3 运行前提:JDK 版本、解压结构与启动入口
Ybat 是 Java 写的桌面工具,zip 解压之前先确认机器上有 JDK。很多人装的是 JRE,双击时能启动一部分功能,但某些图像解码模块会报缺类。我给个经验值:用 JDK 8 或 JDK 11 跑这类老牌 Java 工具最稳,太新的 JDK 17 偶尔会遇到 Swing 组件初始化报错,多数是模块化限制导致。
# 先确认 Java 运行时存在 java -version解压 zip 后,目录里通常能看到一个可执行 jar 包、一个 Windows 下的 bat 脚本或 Linux/macOS 下的 sh 脚本,以及 README。不要急着双击 bat,先看 README 里写明的入口 jar 名,再决定启动方式。这一步能省掉后面很多玄学问题。
3. 用 Ybat 拉出第一组 BBox:启动参数、界面流程与输出对账
3.1 最小启动命令与内存参数:图片一多就不卡的秘诀
我第一次用 Ybat 直接双击了启动脚本,结果大图目录一加载就卡成 PPT。后来改回命令行启动并给了堆内存,世界清净了。最小可用的启动命令是这样:
# 4G 堆内存运行,标注超大图或上千张图时才不卡 java -Xmx4g -Xms512m -jar ybat.jar逻辑说明:-Xmx4g 设置最大堆内存 4GB,-Xms512m 设置初始堆 512MB。-Xms 调小是为了减少启动等待,界面图片列表铺开后再让堆慢慢扩容。这是 CPU 内存调配,和显卡显存无关,别搞混。
参数为什么这么给?图片目录里如果有 4000×3000 的原始照片,Java 的 ImageIO 默认会把图片按 ARGB 全量解码到内存,一张的像素缓冲就是 400030004 字节,约 48MB。100 张同时驻留就是 4.8GB。堆内存给不够的表现很典型:滚动图片列表时界面白屏、画框时框线跟不上鼠标、切图越来越慢,最后可能直接抛 OutOfMemoryError。如果你的图集超过 500 张,-Xmx4g 是起点而不是上限。
3.2 界面三类操作:开目录、画框、切类别
Ybat 的界面不复杂,核心操作就三类,十分钟能上手。
第一步,在界面上找到打开目录按钮,选中放图片的文件夹。程序会把该目录下的 jpg、png 等图片按文件名排序载入左侧或底部的文件列表。注意,它不递归子目录,一个文件夹就是一任务批次。
第二步,画框。鼠标左键按住拖拽,松开后一个 bbox 落在图上。画错不要找撤销菜单,直接在图上右键点击这个框删除,或者选中后按 Delete。框选的时候尽量把目标边界包完整,宁可略大一点,不要切掉目标边缘,这对后续 yolo 训练时的特征学习影响很大。
第三步,类别切换。在侧边栏维护类别列表,点选当前框归属的类别。这里有个要命的细节:类别列表的排列顺序,就是最终 txt 里 class_id 的编号顺序。第一个类别是 0,第二个是 1。Ybat 不会给你做任何校验,顺序错了它也不知道。
没有撤销功能是个麻烦事,我的习惯是标完十几张就主动核对一次:去图片目录里看一眼同名 txt 是否生成、大小是否非零。工具一般会在切换下一张图或退出时自动写盘,但“一般”不等于“一定”。标了 300 张发现没保存,连后悔药都没处买。
3.3 拉完两张图就检查一遍输出:txt 内容与像素坐标的对账
这一步值得养成肌肉记忆。“工具能保存”不代表“保存得对”。标完两张图后用 Python 看一眼实际输出:
from pathlib import Path def parse_yolo_txt(txt_file: Path): boxes = [] with open(txt_file, encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) != 5: print(f"行格式异常: {line.strip()}") continue cls, xc, yc, w, h = parts boxes.append({ "cls": int(cls), "xc": float(xc), "yc": float(yc), "w": float(w), "h": float(h), }) return boxes # 用法示例:解析 labels 目录下 0001 号图片的标注 for box in parse_yolo_txt(Path("labels/0001.txt")): print(box)逻辑说明:YOLO txt 每行五个字段,空格分隔。Ybat 保存的就是这种空格分隔格式,也是 YOLO 训练脚本默认读取的格式。解析时先检查字段数量,少于五个说明这一行被截断或混入了额外字符串,常见原因是类别名里带了空格,工具把类名拆成了多个字段写进去。解析坐标时顺便看数值范围,超出 0 到 1 的坐标要立刻停下排查,这就是后面避坑章节的主线问题之一。
4. 把 Ybat 输出整理成 YOLO 训练数据集:类别文件、目录结构与划分流
4.1 classes.txt 与 data.yaml:类别顺序就是 class_id,改顺序等于重新标注
Ybat 只负责产生图片旁边或指定输出目录下的 txt,它不管你这批 txt 要喂给哪个训练框架。真正把“一批 txt 文件”变成“yolo 数据集”,靠的是类别文件和 data.yaml。类别文件的约定写法:
person car bicycle每行一个类,行号从 0 开始。Ybat 界面类别列表的顺序必须和 classes.txt 完全一致,否则 txt 里 class_id=2 的框,你以为在标 bicycle,模型读到的是 car。这种错位不会报错,只会让你的模型在推理阶段把自行车全预测成小汽车,而且 mAP 数值还不难看,属于最阴间的错误。检查这个问题的唯一笨办法:随机抽一张已标注图,对照原始图像人工核验几个框的类别。
data.yaml 是训练时真正吃进去的配置:
# data.yaml 放在标注输出根目录下 train: ./train/images val: ./val/images nc: 3 names: 0: person 1: car 2: bicycle参数说明:train 和 val 指向图片目录,不是 labels 目录。训练框架会根据图片名到同级 labels 目录找对应 txt。nc 必须真实等于类别数量,写错会导致类别索引越界。names 写成键值对的好处是直白,避免列表顺序错位。如果你手里有 COCO80 的类别文件,想对照一下自己的类和它的 ID 是否一致,可以拿这份 yaml 当索引,后面第 6 章会讲怎么用它做映射。
4.2 images 与 labels 分离加 train/val 随机划分:一条龙脚本
YOLO 训练时目录结构通常长这样:train/images 与 train/labels 并排,val 同理。图片与 txt 同名,一个不多一个不少。手动复制几百对文件太蠢,而且容易漏掉某个没有 txt 的图片。用一段 Python 脚本搞定拆分:
import random import shutil from pathlib import Path src = Path("ybat_output") # Ybat 输出的图片与 txt 所在目录 ratio = 0.8 # 训练集占比 imgs = sorted(src.glob("*.jpg")) + sorted(src.glob("*.png")) random.seed(42) random.shuffle(imgs) n_train = int(len(imgs) * ratio) for i, img in enumerate(imgs): sub = "train" if i < n_train else "val" txt = img.with_suffix(".txt") img_out = src / sub / "images" / img.name txt_out = src / sub / "labels" / txt.name img_out.parent.mkdir(parents=True, exist_ok=True) txt_out.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(img, img_out) if txt.exists(): shutil.copy2(txt, txt_out) else: print(f"警告: 缺少标注 {txt.name}") print(f"train={n_train} val={len(imgs) - n_train}")逻辑说明:为什么先 shuffle 而不是直接取前 80%?因为图片通常按拍摄批次排布,前面可能全是晴天白天的场景,后面全是夜间或雨天。不洗牌会导致验证集分布严重偏离训练集,训练出的模型在验证集上表现虚高,部署到现场立刻翻车。random.seed(42) 保证每次运行划分结果一致,复现实验时不会因为数据划分不同而困惑。空标注图片也照常复制进训练集,它不是错误,是负样本,模型需要它们来学习“没有目标”的场景。
4.3 超界坐标和空文件检查:训练前最后一道闸
标注数据在训练前必须过一次体检。我见过太多人直接python train.py开跑,几天后才发现某个 txt 里混进了一个 1.4 的坐标,整个训练白跑。下面的检查脚本专门扫三类问题:
from pathlib import Path def check_labels(label_dir: Path, img_dir: Path): issues = [] for txt in sorted(label_dir.glob("*.txt")): # 同名图片必须存在 img = img_dir / txt.with_suffix(".jpg").name if not img.exists(): issues.append(f"缺图: {img.name}") lines = [ln.strip() for ln in txt.read_text(encoding="utf-8").splitlines() if ln.strip()] if not lines: issues.append(f"空标注: {txt.name}") for ln in lines: parts = ln.split() if len(parts) != 5: issues.append(f"行格式错: {txt.name}: {ln}") continue vals = list(map(float, parts[1:])) if any(v < 0 or v > 1 for v in vals): issues.append(f"超界: {txt.name}: {ln}") return issues # 调用方式:分别传入训练集和验证集的 labels 与 images 目录 for issue in check_labels(Path("train/labels"), Path("train/images")): print(issue)逻辑说明:超界坐标的成因多半是标注过程中图片显示被缩放、工具保存了画布坐标而不是原图坐标,加上边缘目标被手误拉出画布边界。检查脚本把所有不合规行一次性打印出来,再决定人工修还是直接弃掉那张图,不要带着问题进训练。
5. Ybat 标注避坑指南:5 个让我翻车的现场与排查顺序
这 5 个坑都是我自己或同事在 Ybat 上翻车后的血泪经验,按出现频率排序,每一条都按“现象、原因、解决”给全。
5.1 双击启动脚本没反应
现象:双击 ybat.bat 或 sh 脚本,鼠标转了几圈,什么窗口都没出现。任务管理器里能看到 java 进程,但界面就是起不来。
原因:机器上没装 JDK,或者 PATH 里只有 JRE。另一种是 JDK 版本太老,jar 需要更高版本的功能,报错信息被脚本窗口直接吞掉,你什么都看不到。
解决:回到命令行手动执行启动命令,把异常输出贴出来再判断。如果提示 java 命令找不到,重新安装 JDK 并配置 JAVA_HOME;如果报 UnsupportedClassVersionError,说明 jar 编译版本高于当前 JDK,升级 JDK 版本。这一步能解决九成启动失败问题,别再双击了。
5.2 中文路径下图片加载一片空白
现象:图片目录在 D:\数据集\车辆 下面,目录能打开,图片列表也有文件名,但画布区域全是灰的,一张图都显示不出来。
原因:老牌 Java 工具在读取带中文或特殊字符的路径时,文件系统编码不匹配,Windows 环境尤其常见。表现就是“能列出文件名、打不开文件内容”。
解决:把整个标注工作目录挪到纯英文路径下,比如 D:\datasets\vehicle。不要试图在工具里找编码选项,这类桌面工具基本没有。英文路径对后续 yolo 训练也有好处,很多数据加载脚本在中文路径下会直接报找不到文件,不如从一开始就避开这个隐患。
5.3 类别名带空格导致 txt 解析串行
现象:标完一批图,训练时 loss 正常下降但 mAP 一直上不去。打开 txt 一看,某一行是 1 0.5 0.5 0.2 0.3 truck,看起来没问题,但行数比实际框数多。
原因:把类别写成了 pickup truck,类别名里的空格被当作字段分隔符,txt 里变成六个字段。训练脚本按前五个字段读取,多余的字符串要么被丢弃,要么触发解析异常。行格式在视觉上极具欺骗性,因为读起来通顺,但结构已经错了。
解决:类别名一律用单个英文单词,不要用空格,更不要用中文。pickup truck 改写成 pickup_truck。改完类别名后重新导出标注,不要手动替换 txt 里的字符串——人工替换几百行文件一定会漏。这个坑不只在 Ybat 里有,任何按空格分隔的标注格式都躲不开。
5.4 一千张大图目录卡成 PPT
现象:打开一个包含 1200 张 4000×3000 图片的目录,界面每隔几秒白屏一次,画框时框线跟不上鼠标,切图要等好几秒。
原因:界面加载图片列表时,可能对大量图片做了解码或缩略图生成。高分辨率图片一次性解码,堆内存不够时频繁触发 GC,界面线程被卡死。
解决:第一,把大图批次拆开标,一个子目录不超过 300 张,比如 day01、day02 分批处理。第二,把原始大图先压缩到 1920 宽再标注。目标检测对训练图分辨率的要求没那么苛刻,标注图和训练图一致就行,1920 宽足够绝大多数场景。第三,启动参数提到 -Xmx4g 以上,三者配合基本能消除卡顿。
5.5 保存后的框发生位移
现象:标注时框在目标上,保存后重新打开,框偏到了目标左上方,偏移量还不固定,有的图偏多有的图偏少。
原因:界面画布一般会缩放显示图片。缩放比例不是整数时,像素坐标在取整过程中产生换算误差。如果工具内部保存的是显示坐标而不是原始图片坐标,误差会被进一步放大。
解决:先看界面状态栏显示的图片尺寸是否等于原始图片尺寸,标注时尽量用 100% 显示比例画框。发现已有偏移的框,结合 4.3 的超界检查脚本定位,把偏移严重的删掉重画。这个坑也提示了一个选工具标准:保存时取的是“画布坐标”还是“原图坐标”。我后来每换一个标注工具,都会拿一张已知坐标的图试标一次,保存后手工核验,再批量开工。
6. 从 Ybat 无缝衔接到 COCO80 预训练模型的类别映射技巧
最后一个技巧,解决一个高频烦恼:你在 Ybat 里用自定义类别顺序标完几百张图,回头想微调一个 COCO80 预训练权重,但数据集的 class_id 和 COCO 的 ID 对不上。这时千万别重标,直接写脚本改 class_id,十分钟搞定。
先拿到你的真实类别顺序,就是你 Ybat 界面上从上到下的列表。然后对照 COCO80 的类别文件,构建一个映射表。COCO80 里 person 是 0,bicycle 是 1,car 是 2,这个顺序以你下载的类别文件为准,不要凭记忆,我就曾经把 bicycle 和 car 的 ID 记反,模型把所有汽车预测成自行车,排查了半天才发现是映射错位。
import csv from pathlib import Path # 你的数据集类名 -> 目标 COCO80 类 ID mapping = { "person": 0, "car": 2, "bicycle": 1, } # Ybat 界面里的类别顺序,数量必须和实际一致 cls_names = ["person", "car", "bicycle"] def remap_labels(label_dir: Path, mapping: dict, cls_names: list): for txt in label_dir.glob("*.txt"): lines = [] for line in txt.read_text(encoding="utf-8").splitlines(): parts = line.split() if len(parts) != 5: continue old_id = int(parts[0]) if old_id >= len(cls_names): print(f"越界: {txt.name} 中 class_id={old_id}") continue new_id = mapping[cls_names[old_id]] # 由旧 ID 反查类名,再映射 parts[0] = str(new_id) lines.append(" ".join(parts)) txt.write_text("\n".join(lines), encoding="utf-8") # 调用示例 remap_labels(Path("train/labels"), mapping, cls_names)逻辑说明:脚本先通过旧 class_id 从 cls_names 里反查出真实类名,再用 mapping 字典找到目标 ID。这样做的好处是,即使你把自己的类别顺序调乱了,只要 cls_names 写对,映射结果就不会错。如果你的映射表条目很多,从 CSV 读入字典更省心,避免在代码里堆上百行硬编码。
注意一个连带动作:改完 txt 里的 class_id 之后,data.yaml 的 names 顺序必须同步改成目标顺序,否则第 4 章说过的“类别错位”会再次出现。另外,这类映射最好在训练前完成,如果已经导出过 onnx 模型再回头改类别,那就要重新训练重新导出,得不偿失。
我现在做 yolo 数据集的标准动作是:第一天先标 20 张图,跑完本章所有检查逻辑——坐标解析、划分、类别映射、可视化抽检——确认全链路无误,再放开手脚标剩下的几百张。标注是整个 yolo 落地流程里最不性感但最决定上限的一环,数据错了,再好的 yolo 改进思路都救不回来。希望这篇能帮你少走几步弯路。
本文还有配套的精品资源,点击获取