标注工具
简介
使用人工智能解决现实场景中的问题, 数据标注是不可或缺的一环,准确的标注数据都是训练高性能AI模型的基础。MegaPrecVision加一个盛相Hdc相机可以轻松搭建标注工具,可以快速实现轻量级的在线采图和缺陷标注。
演示的标记目标是一批金属零件,表面有脏污、划痕、磕碰等缺陷,通过本案例工具可以实现图像采集和保存、缺陷标注和文件保存,最终用于后续的AI训练。
全程讲解视频
标注工具

上手搭建流程
1.通过 TCP 通讯 接收上位机发送的工件 SN 号,触发拍摄
2.图像采集和将图像保存在本地
3.加入区域模块用于图像显式标注,通过lua脚本实现标注信息文件保存
4.将流程关联到运行界面

详细流程
| 序号 | 模块名称 | 主要作用 |
|---|---|---|
| 1 | 起始 | 流程入口 |
| 2 | 接收数据 | 解析 TCP 数据成变量(含 SN) |
| 3 | 混合相机 | 按当前配置触发拍照,输出图像 |
| 4 | 矩形区域 | 可视化标注区域 |
| 5 | 时间获取 | 获取当前日期时间字符串 |
| 7 | 字符串运算(Beta) | 对 SN / 日期进行第一次字符串处理 |
| 8 | 格式化字符串 | 将时间字符串进一步格式化,生成文件名前缀 |
| 9 | 数据保存 | 使用前缀保存 PNG 图像 |
| 10 | 条件分支 | 根据条件执行不同缺陷脚本 |
| 11 | 1-划伤保存 | 调用 Lua 脚本获取并保存“划伤”标注(JSON) |
| 12 | 2-磕碰保存 | 调用 Lua 脚本获取并保存“磕碰”标注(JSON) |
| 13 | 汇合 | 将分支重新汇合,回到主流程 |
| 14 | 结束 | 流程结束 |
Lua示例脚本
-- 来自脚本模块传入的全局变量:
-- x1, y1, x2, y2 : 实数(矩形框两个角)
-- label : 文本(字符串),例如 "impact_crack" / "internal_crack"
-- time, sn : 文本(字符串,用于文件名)
-- imageHeight : 整数(图像高度)
-- imageWidth : 整数(图像宽度)
--
-- C++ 中已经注册了 json 模块:
-- json.encode / json.decode / json.save / json.load / json.null
------------------------------------------------------------
-- 1. 工具函数:把 sn / time 里的非法字符替换掉,避免 Win 下文件名报错
------------------------------------------------------------
local function sanitize_for_filename(s)
s = tostring(s or "")
-- 只保留字母数字、下划线、短横线,其余全部换成下划线
return (s:gsub("[^%w%-_]", "_"))
end
------------------------------------------------------------
-- 2. 生成文件完整路径:label json + image 路径
------------------------------------------------------------
local sn_str = sanitize_for_filename(sn)
local time_str = sanitize_for_filename(time)
-- 标注 json 保存目录(需要修改)
local label_dir = "C:\\Users\\****\\Desktop\\label\\"
-- 如需自动创建目录,可放开下行(需要系统支持 mkdir)
-- os.execute('mkdir "' .. label_dir .. '" 2>nul')
-- 图像目录(需要修改)
local img_dir = "C:\\Users\\****\\Desktop\\image\\"
-- 基础文件名:sn_time
local base_name = string.format("%s_%s", sn_str, time_str)
-- json 文件名和完整路径
local json_filename = base_name .. ".json"
local json_filepath = label_dir .. json_filename
-- imagePath 按 img_dir/sn_time.png 的形式
local image_filename = base_name .. ".png"
local imagePath = img_dir .. image_filename
------------------------------------------------------------
-- 3. 构造当前标注的 shape 结构
-- 目标格式:
-- {
-- "label": "impact_crack",
-- "points": [ [x1,y1], [x2,y2] ],
-- "group_id": null,
-- "description": "",
-- "shape_type": "rectangle",
-- "flags": {},
-- "mask": null
-- }
------------------------------------------------------------
local x1_num = tonumber(x1) or 0.0
local y1_num = tonumber(y1) or 0.0
local x2_num = tonumber(x2) or 0.0
local y2_num = tonumber(y2) or 0.0
local label_str = tostring(label or "")
local current_shape = {
label = label_str,
points = {
{ x1_num, y1_num },
{ x2_num, y2_num },
},
group_id = json.null, -- 严格输出为 null
description = "",
shape_type = "rectangle",
flags = {},
mask = json.null, -- 严格输出为 null
}
------------------------------------------------------------
-- 4. 从已存在的 json 文件中加载数据(若不存在则创建新的结构)
------------------------------------------------------------
local data, load_err = json.load(json_filepath)
local shapes = nil
if not data then
-- 文件不存在或解析失败:从零开始构建一个结构
data = {}
shapes = {}
data.shapes = shapes
else
-- 文件存在:保证有 shapes 数组
data.shapes = data.shapes or {}
shapes = data.shapes
end
-- imageHeight / imageWidth 从 C++ 传入(整数)
local h = tonumber(imageHeight) or 0
local w = tonumber(imageWidth) or 0
-- 每次都更新图像信息,保持与当前 SN/time 对应
data.imagePath = imagePath
data.imageHeight = h
data.imageWidth = w
------------------------------------------------------------
-- 5. 检查当前 shape 是否已经存在,避免重复写入
------------------------------------------------------------
local function same_point(p1, p2)
return p1[1] == p2[1] and p1[2] == p2[2]
end
local function same_shape(a, b)
if not a or not b then
return false
end
if a.label ~= b.label then
return false
end
if a.shape_type ~= b.shape_type then
return false
end
if not a.points or not b.points then
return false
end
if #a.points ~= 2 or #b.points ~= 2 then
return false
end
-- 只比较 label + shape_type + 两个角点,group_id/mask 不参与去重
return same_point(a.points[1], b.points[1])
and same_point(a.points[2], b.points[2])
end
local already_has_shape = false
for _, s in ipairs(shapes) do
if same_shape(s, current_shape) then
already_has_shape = true
break
end
end
------------------------------------------------------------
-- 6. 若不存在相同 shape,则追加到 shapes 中
------------------------------------------------------------
if not already_has_shape then
table.insert(shapes, current_shape)
end
------------------------------------------------------------
-- 7. 保存回 json 文件
-- json.save(value, path) -> true 或 false, err
------------------------------------------------------------
local ok, save_err = json.save(data, json_filepath)
if not ok then
-- 调试用输出,生产环境需要可以关掉
print("save label json failed:", save_err)
end
-- 无输出,脚本结束
-
脚本逻辑:
-
尝试读取已有
base_name.json;如不存在或解析失败,则创建新的data = { shapes = {} }。 -
始终更新:
data.imagePath = imagePathdata.imageHeight = imageHeightdata.imageWidth = imageWidth
-
在追加前,检查
shapes中是否存在完全相同的shape(比较label、shape_type以及两点坐标);如果已存在则不重复写入。 -
若不存在,则
table.insert(shapes, current_shape)追加一个新标注。 -
使用
dkjson.encode(data, { indent = true })生成带缩进的 JSON 文本,并覆盖写回base_name.json。
-
-
以此实现:
- 同一工件/时间下的多个缺陷框可以追加到同一个 JSON 文件中;
- 多次对同一位置重复点击不会新增重复的
shape记录。
操作流程(运行界面)
下面展示操作员在生产过程中的实际使用步骤,并配套流程图帮助理解整体动作流。
操作流程图
操作步骤说明
运行界面
-
等待上位机发送触发信号 产线控制系统发送:
START,SN12345!系统自动解析
SN12345并进入拍照流程。 -
系统自动拍照并展示图像
左侧显示原图,右侧显示克隆图;矩形区域作为可拖拽标注框。
-
操作员检查工件是否存在缺陷
- 若无缺陷 → 停止操作,等待下一件工件。
- 若发现缺陷 → 进入下一步。
-
调整矩形区域
拖拽矩形框,使其完整覆盖缺陷区域。
-
点击对应缺陷类型按钮
- 划伤 → 点击「划伤保存」
- 磕碰 → 点击「磕碰保存」
-
系统自动记录 JSON 标注 脚本会根据当前:
- SN 号;
- 时间(参与生成
base_name); - label(缺陷类型字符串,例如
"impact_crack"); - 矩形框坐标(x1,y1,x2,y2);
- 图像尺寸(imageHeight、imageWidth);
将内容写入:
本地磁盘:\**\****\******\****\SN_时间.json若文件已存在则在
shapes数组中追加一个新的shape,不会写入重复条目。 -
继续下一件工件 操作员等待新的 START 指令并重复以上流程即可。
同一次拍摄的多次标注
-
上位机 没有再次发送 START 指令前,同一张图像一直有效,操作员可以在这张图上反复标注。
-
典型用法:
- 在第一个缺陷位置调整矩形区域 → 点击相应缺陷按钮(如「划伤保存」),在
shapes中写入第一条shape; - 将矩形区域拖到第二个缺陷位置 → 再次点击对应缺陷按钮,脚本会在同一个
SN_时间.json文件的shapes数组末尾追加第二个shape; - 如此循环,可对同一张图上的多个缺陷进行多次标注,无需重新拍照。
- 在第一个缺陷位置调整矩形区域 → 点击相应缺陷按钮(如「划伤保存」),在
-
标注脚本始终以 同一个 SN + time 生成的
base_name作为文件名键,只要 SN 和时间不变,该工件的所有缺陷都会累积到同一个 JSON 标注文件中。