
简介Ybat 是一款面向目标检测标注场景的轻量级边界框注释工具专为 YOLO 格式设计同时兼容 Pascal VOC 与 COCO 格式适合算法工程师、数据标注人员及计算机视觉学习者快速整理训练数据集。工具采用纯浏览器运行方式无需安装环境或上传图片所有图像均在本地处理并支持缩放平移辅助精确框选、快速导航、意外刷新自动保存、裁剪框并导出图像等实用功能。压缩包中共有 15 个文件大小约 215KB主要包含网页启动页面、脚本逻辑及依赖库、样式表、说明文档和类别标签文本结构简洁、便于直接使用。项目源码体积小巧利于阅读主逻辑并修改参数配置扩展类别对理解前端图像标注工具的实现也很有参考价值。目前已有 78 人学习下载。1. YbatYOLO 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_widthy_center ((ymin ymax) / 2) / image_heightwidth (xmax - xmin) / image_widthheight (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, encodingutf-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_id2 的框你以为在标 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(parentsTrue, exist_okTrue) txt_out.parent.mkdir(parentsTrue, exist_okTrue) shutil.copy2(img, img_out) if txt.exists(): shutil.copy2(txt, txt_out) else: print(f警告: 缺少标注 {txt.name}) print(ftrain{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(encodingutf-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 是 0bicycle 是 1car 是 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(encodingutf-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), encodingutf-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 改进思路都救不回来。希望这篇能帮你少走几步弯路。本文还有配套的精品资源点击获取