简介:这份资源是面向计算机视觉初学者与数据集制作者的objectmarker工具源码包,用于交互式生成训练模型所需的正样本描述。用户通过鼠标在图像上框选目标区域,程序记录边界框坐标并输出为info.txt,格式为图像名加左上角与右下角坐标,可直接供后续物体识别训练使用。压缩包共10个文件,约1.01MB,包含exe可执行程序、cpp源码、dll动态库、txt说明与数据文件、bmp示例图像及db索引文件,兼顾直接运行与二次开发。已有291人学习下载。借助OpenCV的鼠标事件与图像显示能力,该工具能帮助读者快速标注小众类别数据,理解正样本与边界框的组织方式,并在此基础上改进界面、处理多目标图像或结合数据增强提升模型泛化能力。
1. 从一堆截图到训练集:objectmarker 到底解决了哪一步
做 OpenCV 目标检测的人多半经历过这个场景:手头攒了几百张监控截图或者产品照片,想训练一个 Haar 级联或者 LBP 分类器,结果卡在第一步——正样本描述文件怎么生成。手动敲img/001.jpg 1 0 0 24 24这种行,敲到第十行就开始怀疑人生,敲到第一百行基本就废了。objectmarker 就是冲着这个环节来的:它把「框选目标 → 记录坐标 → 输出正样本描述文件」这条链路做成一个能跑的小工具,附带源码,方便你按自己的目录结构改。
它适合两类人:一类是刚接触 OpenCV 识别、正样本概念还没落地的开发者,需要先把positive.txt这种文件跑通;另一类是已经有一批标注数据、但格式和训练脚本对不上,想找个能改的中间层。核心价值不在算法,而在「把标注动作变成训练脚本能吃的文本」。下面按「它输出什么 → 怎么跑起来 → 参数怎么调 → 哪里会翻车」的顺序拆。
2. 正样本描述文件的格式与 objectmarker 的生成逻辑
2.1 OpenCV 训练脚本到底认什么格式
在动手之前得先搞清楚目标格式,否则工具跑完你也不知道对不对。OpenCV 的opencv_createsamples读取的正样本描述文件,常见有两种写法。第一种是每行「图片路径 + 目标个数 + 每个目标的 x y w h」,例如:
pos/001.jpg 1 34 20 48 48 pos/002.jpg 2 10 10 30 30 80 60 30 30第二种是只写图片路径,由-info配合-num参数在生成 vec 文件时统一处理,但这种方式要求每张图里目标位置一致,实际很少用。objectmarker 走的是第一种,因为它要支持一张图里多个目标。这里有个容易忽略的点:坐标是相对原图的像素值,不是归一化值,宽高也是像素。很多人第一次生成完拿去训练,报「unable to read image」或者 vec 文件为空,八成是路径写成了绝对路径或者坐标超界。
2.2 objectmarker 的框选与记录流程
工具本身的逻辑不复杂:加载图片 → 鼠标拖拽画矩形 → 把矩形坐标追加到内存列表 → 切换下一张时写入文本。源码里通常有一个rects列表和一个current_img索引,画框的回调函数负责push_back。我一般会先确认三件事:图片是按文件名排序加载的、坐标存的是(x, y, w, h)而不是(x1, y1, x2, y2)、写文件时用的是追加模式还是覆盖模式。这三件事决定了你后面能不能直接拿文件去训练。
下面是一段常见的核心逻辑,用 Python + OpenCV 复现这个流程,方便你对照源码理解:
import cv2 import os # 图片目录与输出文件 img_dir = "pos_images" out_file = "positive.txt" # 读取目录下所有 jpg,按名称排序保证顺序稳定 files = sorted([f for f in os.listdir(img_dir) if f.endswith(".jpg")]) rects = [] # 当前图片的框列表 records = [] # 所有图片的记录 def on_mouse(event, x, y, flags, param): global rects, ix, iy if event == cv2.EVENT_LBUTTONDOWN: ix, iy = x, y # 记录起点 elif event == cv2.EVENT_LBUTTONUP: # 计算左上角与宽高,兼容从右下往左上拖 x1, y1 = min(ix, x), min(iy, y) w, h = abs(x - ix), abs(y - iy) if w > 0 and h > 0: rects.append((x1, y1, w, h)) for f in files: path = os.path.join(img_dir, f) img = cv2.imread(path) if img is None: continue rects = [] cv2.namedWindow("mark", cv2.WINDOW_NORMAL) cv2.setMouseCallback("mark", on_mouse) while True: cv2.imshow("mark", img) key = cv2.waitKey(1) & 0xFF if key == ord("n"): # 按 n 进入下一张 break elif key == ord("r"): # 按 r 清空当前框 rects = [] # 写入:路径 个数 x y w h ... line = path + " " + str(len(rects)) for (x, y, w, h) in rects: line += " %d %d %d %d" % (x, y, w, h) records.append(line) cv2.destroyAllWindows() with open(out_file, "w") as fp: fp.write("\n".join(records))逻辑说明:on_mouse里用min和abs处理反向拖拽,这是很多人自己写时漏掉的,导致宽高出现负数,训练脚本直接报错。records先攒后写,避免中途崩溃丢数据。参数上,img_dir换成你的图片目录,out_file就是最终给opencv_createsamples用的-info文件。注意路径分隔符在 Windows 下是反斜杠,写进文本时最好统一成正斜杠,否则部分 OpenCV 版本解析会出问题。
2.3 一张图多个目标时怎么记
正样本里经常出现一张图有两个甚至更多目标的情况,比如货架上的多个同类物品。objectmarker 的处理方式是在同一行里把多个x y w h依次追加,个数写在路径后面。这里有个坑:个数必须和后面的坐标组数严格一致,多一个少一个都会让opencv_createsamples在解析时错位,把下一张图的路径当成坐标读。我一般会在写文件前加一句校验:
assert len(rects) == int(line.split()[1]), "框数量与记录不一致: " + path这行断言看着简单,但能挡掉八成「训练时 vec 文件异常」的问题。另外,如果一张图里目标重叠严重,建议拆成两张图分别标,不要在一个框里硬塞,否则生成的样本会互相污染。
3. 把 objectmarker 跑起来:环境、编译与批量处理
3.1 环境准备与依赖版本
objectmarker 这类工具通常依赖 OpenCV 的 highgui 模块做窗口显示,所以编译时必须带上 GUI 支持。用 Python 的话,pip install opencv-python就够,但要注意版本:4.x 和 3.x 在waitKey和窗口行为上有差异,源码里如果用到了cv2.WINDOW_AUTOSIZE之类的常量,4.x 下一般兼容。C++ 版本则需要opencv_highgui和opencv_imgcodecs两个库,CMake 里别漏。
我一般会先跑一个最小验证,确认窗口能弹出来、鼠标事件能触发:
python -c "import cv2; print(cv2.__version__)"如果输出版本号低于 3.4,建议升级,因为老版本对中文路径和 PNG 透明通道的支持有缺陷,标注时图片显示不全,框选坐标会偏。
3.2 编译源码时的常见参数
拿到源码后,如果是 C++ 工程,CMakeLists 里通常有这几项要确认:find_package(OpenCV REQUIRED)的路径是否指向你实际安装的位置、target_link_libraries是否包含${OpenCV_LIBS}、以及是否开启了-std=c++11。我见过有人编译通过但运行时报「undefined reference to cv::imshow」,就是因为链接时漏了 highgui。Python 源码则简单得多,直接python objectmarker.py即可,但要注意工作目录,因为源码里读图片常用相对路径,你在别的目录下执行会找不到文件。
一个稳妥的做法是在源码开头把图片目录改成从命令行参数读取:
import sys img_dir = sys.argv[1] if len(sys.argv) > 1 else "pos_images"这样批量处理不同批次时不用改代码,直接python objectmarker.py batch_01就行。
3.3 批量标注与断点续标
实际项目里图片往往几百上千张,一次标不完。objectmarker 默认是覆盖写,中途退出就全丢了。我的习惯是改成追加模式,并且每标完一张就立刻写一行,同时记录一个进度文件:
with open(out_file, "a") as fp: fp.write(line + "\n") with open("progress.txt", "w") as fp: fp.write(f) # 记录最后处理的文件名下次启动时先读progress.txt,跳过已经标过的图片。这个改动不大,但能省掉重复劳动。参数上,out_file用追加模式时记得第一次运行前清空旧文件,否则新旧记录混在一起,个数对不上。另外,如果图片文件名里有空格,写进描述文件时要用引号包起来,否则解析会断在空格处。常见做法是把文件名里的空格统一替换成下划线,从源头避开这个问题。
4. 避坑与排查:正样本描述文件生成时的五类翻车
4.1 现象:训练时报「unable to read image」
原因通常是描述文件里的路径和实际文件位置对不上。objectmarker 写的是相对路径,但你在另一个目录下执行opencv_createsamples,相对路径的基准就变了。解决方法是统一用相对于描述文件所在目录的路径,或者在生成 vec 时用-info指定绝对路径。我一般会在描述文件生成后跑一遍校验脚本,逐行读路径并os.path.exists检查。
4.2 现象:vec 文件生成成功但样本数为 0
这多半是坐标超界或者宽高为负。鼠标反向拖拽时如果没做min/abs处理,w或h会是负数,opencv_createsamples直接跳过。解决方法是像 2.2 节那样在回调里归一化坐标,并在写文件前加断言检查w > 0 and h > 0。另外,如果框的右下角超出了图片边界,也要裁剪到边界内,否则同样被跳过。
4.3 现象:一张图多个目标时只识别出第一个
原因是描述文件里个数和坐标组数不匹配,解析器读到第一个目标后就按下一行处理了。解决方法是写文件时严格按「个数 = 坐标组数」拼接,并在每行写入前做一次len(rects)校验。如果一张图里目标数量动态变化,建议每标完一个目标就更新一次个数,而不是最后统一算。
4.4 现象:中文路径或文件名导致读取失败
OpenCV 的imread在部分版本下对中文路径支持不好,返回None。解决方法是把图片目录和文件名统一改成英文加数字,或者在 Python 里用cv2.imdecode(np.fromfile(path, dtype=np.uint8), -1)绕过。我一般从源头规避,标注前先把文件名批量重命名成img_0001.jpg这种格式,省得后面训练时再折腾。
4.5 现象:标注顺序和训练时的负样本对不上
正样本描述文件里的图片顺序会影响opencv_createsamples生成的 vec 文件里样本的排列,如果你后面用-num截取前 N 个,顺序错了就会拿到不想要的样本。解决方法是标注前先对文件名排序,并在描述文件里保持同样顺序。我习惯在生成描述文件后打印前五行和后五行,肉眼确认一下顺序是否符合预期。
5. 从描述文件到 vec:验证生成结果与进阶技巧
描述文件生成只是第一步,真正要确认它能不能用,得跑一遍opencv_createsamples并检查输出的 vec 文件。命令通常是这样:
opencv_createsamples -info positive.txt -vec pos.vec -num 500 -w 24 -h 24参数说明:-info指向 objectmarker 生成的描述文件,-vec是输出的 vec 文件,-num是期望生成的样本数(可以大于实际图片数,工具会做旋转、缩放等增强),-w和-h是归一化后的样本尺寸,必须和后续训练时-w -h一致。跑完后终端会打印「Done. Created N samples」,如果 N 明显小于你的图片数,回去查 4.2 和 4.3 两条。
验证 vec 文件内容可以用opencv_createsamples的反向查看功能,或者直接写个小脚本读前几个样本:
import cv2 import numpy as np vec = cv2.FileStorage("pos.vec", cv2.FILE_STORAGE_READ) print("样本数:", vec.getNode("num").real()) vec.release()如果这一步报错,说明 vec 文件本身损坏,多半是生成时中途中断。重新跑一遍,并确保磁盘空间足够。
进阶一点的做法是:把 objectmarker 的输出直接对接一个校验脚本,自动检查路径存在性、坐标边界、个数一致性,三项全过才允许进入opencv_createsamples。我现在的习惯是每次标完一批,先跑校验,再生成 vec,最后用-num比实际图片数大 2 到 3 倍来做增强。这样即使某张图标注略有偏差,增强后的样本也能把影响摊薄。从那以后我每次生成正样本描述文件,都强制走一遍「排序 → 标注 → 校验 → 生成 vec → 抽查前十个样本」的流程,再也没出现过训练到一半发现数据格式不对的情况。希望帮到你。
本文还有配套的精品资源,点击获取