简介:这套资源提供 objectmarker 工具的完整源码与可执行程序,面向需要自制物体识别数据集的计算机视觉初、中级开发者。它基于 OpenCV 实现交互式正样本标注:用户通过鼠标在图像上框选目标区域,程序会将多个边界框信息写入 info.txt,方便后续训练检测或分类模型。压缩包共 10 个文件,包含 cpp 源码、exe 可执行文件、dll 运行库、txt 说明文档及示例位图,整体体积仅 1.01MB,结构紧凑、便于直接运行或二次修改。已有 291 人学习下载。借助该工具,读者可快速理解正样本描述格式(图像名加边界框坐标),并掌握用 OpenCV 处理鼠标事件、显示图像和保存标注结果的实现思路;对于公开数据集难以覆盖的小众类别,也能自行批量标注图像,构建贴合业务需求的训练集。附带的 readme 与 info 示例文件有助于快速上手,适合用于物体识别项目的前期数据准备和算法验证。
1. ObjectMarker:给 OpenCV 识别批量生成正样本描述的工具
提到 OpenCV 识别,从零训练级联分类器是最常见的一条路,而这条路的第一步就是准备正样本。正样本不是把图片丢进文件夹就完事,OpenCV 要求一个记录目标位置的描述文件,格式稍有偏差,后面训练直接崩。很多项目卡在图片有几百张、目标又小又多,手工数坐标能让心态崩掉。ObjectMarker(附源码)就是用来解决这个问题的:打开图片,鼠标框出目标,自动生成 OpenCV 训练要用的正样本描述文本。适合训练人脸、车牌、缺陷检测这类目标区域明确的识别场景,也适合第一次接触 opencv_traincascade 的初学者。
2. 正样本描述文件的格式边界:先看懂 info.txt 再开始标
用级联分类器训练前,OpenCV 要求把所有正样本的标注写进一个文本。这个文件的行格式直接决定后续 opencv_createsamples 能否正确裁出正样本。很多人第一次训练失败,不是标注得不准,而是格式不对。所以先用一节把它掰开揉碎。
2.1 每种 OpenCV 训练工具吃的格式:一行一个逻辑样本
opencv_traincascade 的 -info 参数接受的文件,每一行由三部分组成:图像路径、目标个数、目标矩形列表。标准写法是:
relative/path/to/image.jpg 1 x y w h其中 x、y 是矩形左上角坐标,w、h 是宽度和高度,单位是像素。所有坐标必须以图片左上角为原点,向右向下为正。一张图里有多个目标,就把数量改成 2,后面跟两组 x y w h:
relative/path/to/image.jpg 3 10 15 80 90 120 130 60 70 200 40 55 65这里有个新手常踩的误区:以为 w、h 是右下角坐标。实际上 opencv_createsamples 在读取时是按宽度和高度来理解,你填成右下角坐标,它会把样本裁成一个宽高等于右下角减左上角的巨大区域,后面训练直接报废。我一般会写一个小脚本先校验 w、h 都大于 0 且不超过图像尺寸。
这里还要区分路径的写法。OpenCV 按相对路径读取图片,而相对路径是相对于你执行训练命令时的当前目录。如果 info.txt 写在 train_data 目录下,图片在 train_data/positive_images 里,路径就应该写成positive_images/001.jpg,而不是带一串绝对路径。否则换一台机器跑,或者从别的目录启动训练,全部路径找不到。
2.2 为什么手动维护坐标清单容易出低级错误
当图片数量上百张,每张里有两三个小目标时,手动把坐标敲进文本很难保证不出错。我把常见错误按出现频率排了个序:
- 框没有完整框住目标,目标边缘被切掉一部分。
- 一张图里多个目标,眼睛数漏了,数量字段和实际矩形个数对不上。
- 复制粘贴路径时大小写不一致,Windows 下没问题,Linux 下直接找不到文件。
- 坐标重叠或者负数,训练中途才弹 assert failed。
这些错误不会在训练刚开始就暴露,往往在跑了一两个小时后弹出异常,或者训练出的模型误检率高得离谱。因此用工具生成描述文件,本质上是在把“人肉计算坐标”换成“鼠标拖框 + 自动换算”,从源头减少数字出错。
2.3 ObjectMarker 的标注核心:鼠标回调与矩形框管理
ObjectMarker 的源码核心其实不复杂,基于 OpenCV 的窗口鼠标回调来记录矩形。我拆过类似的工具,最关键是三块:鼠标按下开始记录,鼠标移动时更新临时矩形,鼠标松开时把合法矩形存进列表。下面这段代码就是核心循环:
import cv2 boxes = [] # 已确认的矩形列表 start = None # 按下鼠标时的坐标 temp = None # 移动中的临时矩形 def on_mouse(event, x, y, flags, param): global start, temp if event == cv2.EVENT_LBUTTONDOWN: start = (x, y) temp = (x, y, x, y) elif event == cv2.EVENT_MOUSEMOVE: if start: temp = (start[0], start[1], x, y) elif event == cv2.EVENT_LBUTTONUP: if start and x > start[0] and y > start[1]: boxes.append((start[0], start[1], x, y)) start = None cv2.namedWindow("marker") cv2.setMouseCallback("marker", on_mouse)逻辑说明:回调函数根据鼠标事件维护三个变量,boxes存放已确认的矩形,start记录按下瞬间坐标,temp用于实时显示。当左键松开时,判断x > start[0]且y > start[1],作用是排除反向拖拽产生的负宽高矩形。存进boxes的是图片显示坐标系下的绝对坐标,写入文件前不需要再换算。
参数说明:cv2.setMouseCallback里的窗口名必须和namedWindow保持一致。回调里的x, y是窗口显示坐标系的像素值。如果图片在窗口中被缩放显示,那么原图坐标需要做一次除法:原图坐标 = 显示坐标 / 缩放比例。大多数复刻源码里会保留这个缩放因子,否则框出来的位置会偏移。
这块也是你拿到源码后最值得改的地方。我建议在显示窗口上方加一个类似状态栏的区域,实时显示当前图片路径、已框目标数量、当前矩形宽高。这个小改动在标注大量小目标时非常救命,每框一个目标就 +1,减少漏标率。如果你的目标物体边缘不明显,先用图像增强把对比度拉开再标,避免框太小或者框偏移。
2.4 程序里如何管理图像目录与自动保存
ObjectMarker 一般有个简单的文件队列,加载目录下所有 jpg/png,按文件名排序,逐张展示。标注完一张切到下一张前,把该图所有矩形写入输出文件,再清空boxes。这样即使中途关掉程序,已标注的图片也不会丢。
你如果拿到源码想二次开发,主要改这几处:
- 文件后缀过滤:目前只认
.jpg和.png,如果你的数据集是.bmp,要手动加后缀。 - 输出路径:默认生成在运行目录下,建议改成绝对路径,避免不同终端启动时写入位置不一致。
- 图片缩放显示:原图分辨率很大时,窗口可能放不下,需要按比例缩小显示,但坐标要还原回原图尺度。
3. 从图片目录到输出文件:ObjectMarker 标注实操流程
3.1 环境准备与源码运行方式
拿到 ObjectMarker 源码后,先确认 OpenCV 环境。如果源码是 Python 版本,安装 opencv-python 就够了;如果源码是 C++ 版本,需要编译 OpenCV 并配置 CMake。一个比较省心的做法是,先建一个干净的虚拟环境跑通脚本,再逐步加依赖。
python -m venv venv source venv/bin/activate pip install opencv-python numpy参数说明:虚拟环境避免把全局 Python 环境搞乱。opencv-python 自带常用图像处理模块,numpy 用于图像数组操作。如果你的源码还用到了 tqdm 做进度条,也一并装上。
逻辑说明:先激活环境再装依赖,避免装到系统目录里。后面的运行指令都在这个虚拟环境下执行,退出后不影响其他项目。
如果源码是 C++ 版本,核心依赖是 opencv 库和 CMake。编译过程相比 Python 会多一步,但运行效率更高,适合大批量图片。不过大多数场景下,Python 版本已经够用,毕竟标注操作是交互式的,瓶颈在人的手速,不在程序速度。
3.2 命令行参数与操作流程
很多复刻的 ObjectMarker 工具会提供三个参数:图片目录、输出文件路径、显示窗口尺寸。常见调用如下:
python objectmarker.py --img-dir ./positive_images --output info.txt --win-width 800 --win-height 600这里--img-dir指定存放正样本图片的目录,--output指定生成的描述文件名,--win-width和--win-height是显示窗口的尺寸,不是训练尺寸。这两个参数只影响画框时的窗口缩放比例,不影响最终坐标精度。如果你把显示窗口设得比原图小,内部会按比例缩放,坐标换算到原图时依然是准确像素。
然后按下面的步骤操作:
- 运行命令后,窗口出现第一张图。
- 按住鼠标左键拖出矩形框住目标,松开左键确认一个框。
- 同一张图有多个目标就继续框,直到框完所有目标。
- 按空格键保存本图所有矩形到输出文件,并跳到下一张。
- 全部完成后,按 q 退出。
需要特别提醒的是,这里的“保存”是增量写入,也就是说跳下一张之前把当前图片的矩形写进文件,而不是等全部标注完统一写。这样程序意外崩溃时,已标注的图片不会白标。
3.3 目录组织建议与输出文件检查
建议把正样本图片放在一个独立目录,生成文件放在上一级。比如:
train_data/ positive_images/ 001.jpg 002.jpg info.txt这样在 info.txt 里写positive_images/001.jpg,训练时只要在 train_data 目录下启动命令,就能找到所有图片。避免写绝对路径,否则换一台机器路径全变。
保存后打开 info.txt 检查前三行。如果看到类似这样的内容:
positive_images/001.jpg 2 10 15 80 90 120 130 60 70 positive_images/002.jpg 1 30 40 110 95说明每行格式基本对。再检查矩形是否超出图像边界,以及数量字段和实际坐标组数是否一致。我见过最阴间的错误是某一行数量写成 2,但后面跟了 3 组坐标,OpenCV 读取时按数量截取,第三组被当成下一行的开头,后面全部错位。所以这个检查不能省。
3.4 批量预处理:把图片裁好再标,省得训练时踩坑
标注之前,建议对图片做一次统一预处理。常见做法是用脚本把图片统一命名成 6 位数字,比如000001.jpg,再按需缩放。这样 info.txt 里的文件名是整齐的,后续写数据增强脚本也方便。
下面是一个简单的前置处理脚本,把一张大图按滑动窗口切成小图,并跳过面积小于阈值的子图:
import cv2 import os def preprocess(image_path, output_dir, patch_size=256, overlap=64): img = cv2.imread(image_path) if img is None: print(f"fail to read: {image_path}") return h, w = img.shape[:2] idx = 0 for y in range(0, h - patch_size + 1, patch_size - overlap): for x in range(0, w - patch_size + 1, patch_size - overlap): patch = img[y:y + patch_size, x:x + patch_size] out_path = os.path.join(output_dir, f"{idx:06d}.jpg") cv2.imwrite(out_path, patch) idx += 1 preprocess("raw.jpg", "positive_images")参数说明:patch_size是滑窗尺寸,overlap是相邻窗口重叠区域,这两个值决定你从一张大图能得到多少张子图。如果你的目标物体不会正好跨边界,可以不用重叠,减少重复样本。
逻辑说明:脚本按patch_size - overlap作为步长滑动切割,避免目标刚好被切碎。所有子图统一命名,生成的正样本描述文件里路径也会整齐很多。这里有个取舍:滑窗切出来的子图可能大量不包含目标,你需要后续用 ObjectMarker 把包含目标的子图挑出来,其余删掉,否则负样本混进正样本里,模型会学得很困惑。
4. 标注与训练衔接的常见问题排查:五个最容易翻车的坑
4.1 训练时提示找不到图片文件
现象:opencv_traincascade 启动后报错Can not open file或者Failed to read image。
原因:info.txt 里的路径是相对路径,但命令运行的当前目录不在 train_data 下,或者路径里的子目录名写错。
解决:先确认工作目录,cd到 train_data 再执行训练命令。在 info.txt 中统一使用斜杠/,不要混用 Windows 的反斜杠。如果你用 Windows,建议在标注工具里直接把路径替换成标准斜杠,避免跨平台时出问题。
4.2 生成的样本全是全黑或全白
现象:训练出的 .vec 里样本无法看,标记的物体没有出现在裁片中。
原因:坐标 w、h 被误填成右下角坐标,导致目标偏移或裁到背景;或者图像通道处理错误,BGR 和 RGB 混用。
解决:在源码输出前强制检查 w、h 为正数,并且x + w、y + h不超过图像宽高。可加代码把坐标限制在合法范围内:
def clamp_rect(x, y, w, h, img_w, img_h): x = max(0, min(x, img_w - 1)) y = max(0, min(y, img_h - 1)) w = min(w, img_w - x) h = min(h, img_h - y) return x, y, w, h参数说明:img_w和img_h是原图宽高,传入前从cv2.imread读取。clamp_rect的作用是把越界坐标拉回图像范围内,避免生成无效裁剪区域。
逻辑说明:先 clamp 左上角坐标,再根据剩余空间收缩宽高。这里不直接修改原图,只是保证写入 info.txt 的坐标合法。建议在保存前对每一条矩形都调用一次。
4.3 多目标图只检测出来一部分
现象:训练完成后检测率很低,特别是那些包含多个目标的图片。
原因:标注的时候漏框,某些目标被当成背景,模型学到的特征不完整。
解决:保存前显示当前图片上已经画好的所有框,确认数量一致再跳下一张。如果图片数量大,可以每 20 张停一次,把本轮标注结果打印到终端。批量标注最容易出现“标着标着忘了这张图有几个目标”的情况,所以工具里显示已框数量不是小事。
4.4 坐标正常但训练一直报错
现象:提示Invalid rectangle或者训练中途崩溃。
原因:某些图片尺寸不一致,而框坐标是从上一张图片继承下来没有清空。
解决:每次切换图片时强制清空boxes数组。如果图片尺寸相差大,最好在标注前统一缩放或填充,保证所有图片的宽高范围一致。否则即使坐标合法,不同尺寸的样本混在一起训练,也会导致归一化时出现问题。
4.5 负样本数量足够,但正样本描述文件行首数字是 0
现象:info.txt 里某行是path.jpg 0,opencv_createsamples 直接跳过这行。
原因:按下空格时boxes列表为空,说明没有框任何目标。
解决:工具里加判断,如果boxes为空就不写入该行,同时提示当前图片没有标记,让你决定是重新标还是跳过。不要写个 0 混过去,这一行不会报错,但会浪费一次样本机会,并且让你以为这批数据没问题。
5. 接进 opencv_createsamples:用一次可视化校验收尾
描述文件生成后,最终要喂给 opencv_createsamples 生成 .vec 文件,这一步是训练前最后一道关卡。命令格式如下:
opencv_createsamples -info info.txt -num 2000 -w 24 -h 24 -vec positives.vec参数说明:-info指定 ObjectMarker 生成的正样本描述文件;-num是抽取的样本数,不要超过描述文件中的目标总数,否则会报错;-w和-h是统一缩放尺寸,建议保持 24 或 20,不要设太大,否则训练时间剧增;-vec是输出的样本向量文件。
逻辑说明:-num的值代表最终参与训练的正样本数量。如果你的描述文件里一共有 1500 个目标,就填 1500。填多了会报错,填少了浪费已有数据。
生成 vec 后,我习惯做一次可视化校验,不急着开训。最直接的方式是用 opencv_createsamples 自带的展示功能:
opencv_createsamples -vec positives.vec -show 10这个命令会弹出窗口,一屏展示前 10 个正样本。你可以按空格翻页,按 ESC 退出。只需要花两分钟,就能确认每个样本里目标是否完整、有无被裁掉一半的情况。
我以前跳过这步直接开训,结果训练了两小时模型识别不到目标,回头查才发现框选时所有框都偏了半个身位。从那以后我每次标注完,都强制走一遍-show检查,确认框内是完整目标再启动训练。希望帮到你。
本文还有配套的精品资源,点击获取