简介:这是一份面向计算机视觉初学者与模型训练者的正样本标注工具objectmarker源码包,解决自制物体识别数据集中缺少标注工具的痛点。用户通过鼠标框选图像目标区域,程序自动记录边界框坐标并写入info.txt,便于后续训练YOLO、SSD等检测模型;源码基于OpenCV实现鼠标事件处理与图像显示,适合学习交互式标注逻辑和OpenCV基础API。整个压缩包约1.01MB,共10个文件,主要包含可直接运行的exe程序、OpenCV相关dll动态库、cpp源码、示例bmp图像、说明文档与结果txt文件,结构与用途一目了然。资源当前已有289人学习下载,比较适合希望摆脱通用标注工具、快速搭建自定义检测数据集的学习者。通过这份资源可以掌握从图像加载、鼠标框选、空格键记录到回车退出的完整标注流程,同时理解info.txt中文件名与左上/右下坐标的存储格式,为后续数据预处理和数据增强等模型优化工作打下基础。 做目标检测训练的同学应该都知道,数据标注有多烦。尤其是准备正样本时,既要圈出目标,又要确保标注文件里的坐标和类别一一对应,稍不留神就把某个框的归一化坐标写错,训练时各种报错。我之前在做一个检测小项目的时候就深受其害,于是自己用 Python 写了一个小工具,取名叫objectmarker,专门用来快速生成正样本的描述文件,日常项目里已经用得很顺手了。
这个东西本质上就是一个轻量级的标注工具,核心能力是:打开一张图片,按住鼠标拖拽画出目标框,释放之后自动生成 YOLO 格式的标注描述文本,同时支持类别切换、撤销回退、下一张继续标。所有代码我放在了开源仓库里,结构很简单,依赖很少,想改的人可以直接拿去改成自己的标注工具。这篇文章我就从设计思路、源码实现到实际操作,完整拆一遍自己做它的过程,顺便把踩过的坑也一并倒出来。
1. 为什么需要一个生成正样本描述的小工具
1.1 正样本描述到底在描述什么
很多刚接触目标检测的人会混淆“标注”和“描述”这两个概念。标注通常是人工在图像上画框、给标签,这是第一步;而“描述”指的是把标注的结果转成模型训练时能消费的文本格式,例如 YOLO 的class_id x_center y_center width height,其中坐标是相对图片宽高的归一化数值。 objectmarker 做的就是把这套“画框 → 生成描述文本”的流程一体化,画完即生成,不用再手动算坐标,也不用到标注软件里再导出一次。
1.2 现成工具很多,为什么还要自己写
LabelImg、LabelMe 这些工具确实功能完善,但在我实际使用中总有几个痛点:一是启动慢,动不动要配置 Python 环境或 Qt 依赖;二是画框之后需要手动确认保存,连续标注几百张图的时候效率不高;三是坐标格式和自定义类别表需要额外设置,如果只是临时做一个简单数据集,反而觉得配置成本有点高。 objectmarker 的设计目标就很明确——不搞复杂功能,只解决“批量生成正样本描述”这一个核心需求,脚本到一个目录就能跑,还能随时改源码。
1.3 这个项目适合谁参考
如果你正在做入门级的目标检测实验,或者需要一个极简的标注代码作为学习参考,objectmarker 是一个不错的起点。它的源码只有几百行,主要用到 OpenCV 读取图像、Tkinter 做界面和文本输出,逻辑非常直接。你可以在此基础上加“自动保存”、“多边形标注”、“类别联想”等功能。对于那些只想快速生成几百个正样本描述文件来做训练验证的人来说,它比大型标注软件更轻,也更容易理解背后的实现原理。
2. 源码核心模块与实现思路
2.1 界面交互:用 OpenCV 窗口还是 Tkinter
写这个工具时,我最纠结的是界面用哪个方案。OpenCV 的窗口虽然能显示图像,但鼠标响应事件不够灵活,而且按钮、列表这些组件都得自己画。Tkinter 则是 Python 自带的 GUI 库,放按钮、下拉框都很方便,但直接在它上面画图像性能又一般。后来我干脆做了个折中:用 Tkinter 做主界面,左侧放功能按钮和类别列表,右侧用 Label 组件绑定 OpenCV 转换后的图像数据,然后通过绑定鼠标事件获取画框的起点和终点。
这样做的好处是界面逻辑和图像处理的分离非常干净:鼠标事件负责记录坐标,OpenCV 只负责图像缩放和画框显示,描述文本的生成则是独立的函数,不依赖界面层。如果你也想改造成 Web 版或者 PyQt 版,只需保留画框坐标收集和描述生成的核心部分,前端怎么换都行。
2.2 描述文件生成的核心逻辑
模型训练需要的坐标是归一化的,也就是x_center / img_width,box_width / img_width这样的比例。手动操作最痛苦的就是每个框都要拿计算器除一遍,尤其图片分辨率不统一时更是灾难。 objectmarker 里的generate_description函数就专门干这事,传入图片宽高和矩形框的左上角、右下角坐标,自动计算出中心点、宽高并做归一化,最终拼接成一行文本写入.txt文件。
这里我贴一下核心代码片段:
def generate_description(class_id, x1, y1, x2, y2, img_w, img_h): # 保证坐标顺序,避免反向框 x1, x2 = min(x1, x2), max(x1, x2) y1, y2 = min(y1, y2), max(y1, y2) box_w = x2 - x1 box_h = y2 - y1 # 归一化中心点和宽高 x_center = (x1 + x2) / 2.0 / img_w y_center = (y1 + y2) / 2.0 / img_h norm_w = box_w / img_w norm_h = box_h / img_h # 裁剪到 [0,1] 区间,防止越界 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) norm_w = min(max(norm_w, 0.0), 1.0) norm_h = min(max(norm_h, 0.0), 1.0) return f"{class_id} {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f}"这段代码有几个值得注意的细节:坐标排序很重要,否则会在某些脚本里造成负宽度;而归一化后的数值必须裁剪,因为鼠标拖拽偶尔会超出图像边界,不处理的话描述文件里就出现大于 1 的坐标,模型训练时极容易报错。
2.3 类别管理的实现方式
正样本描述的第一列是类别 ID。 objectmarker 里用一个简单的列表来维护类别字典,界面上的下拉框跟字典的 key 对应,选择不同类别时,实际生成文本的 class_id 也跟着变化。我在源码里预设了一个简单的类别文件,格式是单行一个类名,例如person car dog,启动时自动加载。如果项目类别很多,直接在文本里增删即可,不需要改代码。
同理,如果想支持 COCO 格式或者 VOC 格式,只要在保存描述时替换输出函数即可,核心的坐标计算逻辑完全复用。这也是源码解耦设计的价值所在。
3. 从零开始实操:快速生成第一批正样本描述
3.1 环境准备与启动
运行 objectmarker 不需要复杂环境,我用的是 Python 3.8 以上版本,依赖只有opencv-python和numpy。Tkinter 是标准库,装好 Python 后一般自带,不需要额外安装。启动过程大概是这样:
git clone https://github.com/yourname/objectmarker.git cd objectmarker pip install opencv-python numpy python main.py --img_dir ./images --output_dir ./labels--img_dir是你的原始图片目录,--output_dir是描述文件输出目录。启动后会自动读取图片列表,并在左上角显示当前图片序号和文件名。
3.2 标准标注流程
打开界面后,整个标注流程被刻意设计成“三步走”。第一步,在右侧图片上按住鼠标左键拖拽,会出现一个实时变化的矩形框,框的正上方会显示当前画框的类别;第二步,拖到合适位置松开鼠标,这个框就被记录到当前图片的框列表里,同时在图片上画出来;第三步,如果框画错了,按 Ctrl+Z 回退上一个框,确认无误后点击“保存当前图片”,就会生成对应的 txt 描述文件,然后自动切换到下一张图。
我使用下来感觉这个流程最顺畅的顺序是:先设定当前要标注的类别,然后连续画同类的目标,等一批同类目标全部标注完再切换类别。因为界面上切换类别需要点一下下拉框,如果每画一个框都切一次,效率会低很多。比如标注街景里的车和行人,我会先把所有车框完,再切到行人类别继续画。
3.3 自动输出的描述文件长什么样
当你保存一张图片后,生成的文件与图片同名,但扩展名是.txt。例如image_001.jpg会对应image_001.txt,文件里每一行是一个目标描述。一个包含两个行人和一辆车的图片,描述文件内容如下:
0 0.512345 0.678901 0.123455 0.245678 0 0.823456 0.432109 0.098765 0.187654 1 0.643210 0.567890 0.234567 0.312345如果类别表第一个是car,第二个是person,那第一行就表示一个汽车框,第二行和第三行都是行人框。把这样的 txt 文件放到 YOLO 训练目录中,与图片一一对应,模型就能正确读取正样本信息了。
4. 常见问题与排查技巧实录
4.1 标注框位置总是偏了一点
刚开始用的时候,我发现画出来的框总感觉比鼠标位置偏了大概几个像素,后来发现不是代码逻辑问题,而是 Tkinter 中 Label 组件显示的图像和原始图像的坐标系不一样。图像在界面上被缩放显示,鼠标事件拿到的坐标是相对于 Label 控件的像素坐标,必须按缩放比例换算回原始图片坐标,否则就会出现偏移。解决方法是记录原始图像宽高和显示宽高的比例,在鼠标事件里做一次坐标换算。
这个问题的典型表现形式就是“框和鼠标对不上”,很多人以为是 OpenCV 显示的问题。其实核心就是坐标系变换没有处理。源码中我专门写了一个to_original_coords函数,专门负责这件事,也给后来者省了不少排查力气。
4.2 保存时提示图片不存在或读取失败
在批量标注过程中,偶尔会遇到个别图片文件损坏或路径中含中文导致 OpenCV 读取失败。OpenCV 的imread不支持中文路径是历史遗留问题,这在 Windows 系统上特别常见。我的解决办法是判断读取结果是否为None,若是则用PIL读取再转成 OpenCV 格式,或者提示用户当前文件无法读取,并自动跳到下一张。如果你的图片目录里可能有非 ASCII 字符路径,建议做好这个兜底逻辑。
另外,有些很小的图片在标注时会有显示问题,因为 mos 窗口里为了适配界面长度和宽度会被拉伸,导致视觉上物体变形。我建议实际训练时保持数据集图片在 640x640 左右,过小的图可以先用脚本做统一缩放,标注时更不容易看走眼。
4.3 生成的描述文件坐标偶尔出现负数或大于 1
这种情况绝大多数是因为鼠标拖拽时超出了图像边界,或者窗口缩放后没做坐标限制。我在生成描述的函数里已经加了min/max裁剪,但最好在鼠标释放事件里也做一次边界限制。如果你是自己写的类似工具,建议把坐标裁剪和越界提示放在同一个地方,避免多个位置修改后逻辑打架。
还有一点要特别注意:某些模型训练框架会严格检查所有坐标都在[0,1]范围内,一旦出现负数值,训练直接中断,而且报错信息不会告诉你具体是哪个文件。为了快速定位,我建议在保存描述文件时顺便加一个简单的校验函数,把所有越界值打印出来,这样就能立刻找到问题图片。
5. 从工具到生产力的扩展思路
5.1 增加自动保存模式
手动点击保存按钮总归是个高频交互。我在后来的版本里加了一个--auto_save参数,开启后每切换下一张图片时自动保存当前图片的标注结果,这样就减少了一次点击。如果你想实现类似功能,只需要在“下一张”事件里调用保存函数即可,逻辑非常容易集成。
自动保存是一把双刃剑,它提高了效率,但也容易掩盖误操作。比如你标注到一半不小心按了快捷键“下一张”,当前图片没有被完整标注就被保存了。因此在自动保存模式下,我还额外加了一个“当前图片框数量为零则自动跳过保存”的判断,防止生成空描述文件。
5.2 描述文件可视化的快速校验
写完一批标注后,我最怕某张图错乱导致训练半天才发现。后来我写了一个简单的校验脚本:读取图片和对应的 txt 描述,把每个框重新画回图像上,保存成一张check_xxx.jpg。用眼睛扫一遍校验图,就能快速发现漏标、错位的问题。这个脚本也被我顺手放到了仓库的utils/目录下,非常简单,但非常实用。
5.3 加入半自动辅助标注
如果你的场景比较单一(比如始终在检测同一类零件),其实可以引入一个简单的目标检测模型做预标注,然后用 objectmarker 人工修正。这样做不需要引入复杂的标注后台,只需要在代码中加一个--pretrained_model参数,加载模型推理得到的候选框,自动投放到界面上,人工只需检查调整。这套方案我后来在一个小样本项目中用过,效率大约提升了 40%,而且错误率比完全人工标注还低。
半自动标注的前提是你的初始模型在目标场景里已经有一定的可用性,否则投出来的框没有参考价值,反而会影响人工判断。建议先用工具手动标注一两百张正样本,训练一个粗糙的初始模型,再回过来辅助标注剩余数据,这样收益最大。
6. 写在最后的实际体会
我在实际项目里用 objectmarker 标注过上千张工业场景图片,总体感觉是小工具解决大问题。它没有专业标注软件那么华丽,但足够顺手,尤其适合“快速验证一个检测 idea”的场景。从代码层面看,它最大的价值并不是“功能多”,而是结构清楚,任何人都能改。我后来在多个项目里都复用了它的坐标计算和保存逻辑,相当于把最核心、最易错的部分沉淀下来了。
最后再分享一个小技巧:如果你需要多人协作标注,尽量让每个人的类别字典和图片命名规则保持一致,否则合并数据时会出现类别 ID 错乱。别问我怎么知道的,第一次合并团队标注数据的时候,光对齐类别列表就花了一个下午。工具只是辅助,规范流程才是不出错的关键。希望这个小项目能帮你少踩一些标注的坑。
本文还有配套的精品资源,点击获取