资讯动态

HALCON目标检测标注保存:.hdict字典设计与坐标校正

发布时间:2026/10/26 3:50:27 来源:尧图企业网站定制
1. 标注保存这件事到底在保存什么标注做到八百多张的时候我踩过一个很蠢的坑图全画完了框也都在屏幕上结果 HDevelop 崩了一次三个小时的活全没了——因为我把标注全攒在内存的一个 tuple 里一张都没写过盘。从那天起任何目标检测项目开工前我第一件事就是把“halcon 目标检测标注保存”这件事定死格式、路径、落盘时机全部敲定然后才允许自己动笔画第一个框。这篇写的就是我目前在用的一套做法用字典组装样本、用 .hdict 落盘、用相对路径索引图像、落盘前强制校正坐标、落盘后自动跑一遍类别分布统计最后还能顺手导出一份通用 JSON 给别的流程用。先把范围说清楚。这里的“标注保存”不是指把画好的框截个图存下来而是把“哪张图、里面有什么类别的目标、每个目标在图像中的精确像素位置”这三件事变成一份机器可读、可复现、可增量维护的文件。它服务于下游的检测网络训练——网络不认识你的鼠标操作只认识一串坐标和类别编号。所以保存格式一旦定错后面训练报的错会千奇百怪而且极难反查因为你根本不知道是模型的问题还是标注数据的问题。适合谁看手上有一批工业图像、要自己标框喂给 HALCON 深度学习检测流程的人已经会用draw_rectangle1画框、但不知道怎么把框规规矩矩存下来的人以及那些标注文件已经被自己改乱、想推倒重来整理一遍的人。代码给的是 HDevelop 里的原生写法逻辑搬到 .NET 或 Python 外壳里同样成立只是语法要换。我会把每一步“为什么这么干”讲透因为这套东西的坑基本都藏在细节约定里而不是藏在某一行代码上。1.1 一张图进来会被拆成三份信息很多新手把标注想成一件事其实它是三件事而且这三件事的生命周期完全不同。第一份是图像索引这张图叫什么、放在哪、宽高是多少。它几乎不变一旦定了就固定下来。第二份是类别语义图里那个东西属于哪一类这一份对应的是全局的类别表是整个数据集的“字典”改动它等于改动全库。第三份是几何坐标框在图像里的位置这一份是唯一会随着标注进度不断新增的内容。把这三份分开存放是我这套方案的核心判断。我见过太多人把类别名直接写进每个样本里比如每张图的标注都写一遍bolt等到发现有一个类名拼错、想把bolt改成hex_bolt的时候你得遍历几千个文件重写。而如果把类别放在一张独立的映射表里样本里只存一个整数0改名就是改一行。同样地图像宽高也别指望“从图里现读”。标注文件里存一份宽高落盘时顺手写进去好处是后面做坐标校验、做数据增强、做批处理的时候不需要为了知道尺寸把图全读一遍。几千张图全读一遍的开销在 SSD 上可能就几十秒但当你要反复调试脚本、一天跑几十次的时候这几十秒会变成压死耐心的最后一根稻草。1.2 为什么我坚持先定格式再动手画顺序反了代价很大。我试过一种很随意的开局先写个脚本能画框就行存的时候随手用write_string拼了一行文本格式大概是x1,y1,x2,y2类别信息靠文件名后缀区分。前两百张很爽因为不用想结构。到第四百张的时候问题来了一张图里有三个目标且类别不同一行文本装不下想加个“标注人”字段老文件全要重写想把框和图对上号得靠文件名猜。所以后来我的做法固定成两条格式先冻结字段留冗余。冻结的意思是前一百张标完之前不许改字段名和坐标系定义留冗余的意思是除了必填的坐标和类别额外塞进去几个“现在用不上但迟早用得上”的字段比如图像宽高、标注批次号、是否已复核。冗余字段几乎不占空间但能省掉一次全库重写。注意坐标系定义必须写死在文档里而且只允许一种。行在前、列在后左上角起点右下角终点单位是像素。这四条里只要有一条在不同脚本里不一致你的框就会在训练时整片偏到左上角而且报错信息完全不会告诉你原因。2. 一套能撑住几千张图的标注存储设计标注规模在两三百张以内的时候其实怎么存都能活因为出问题你手工就能核对。真正开始难受是从四位数往上尤其在你要分批判、要复核、要回滚的时候。我给自己定的目标是任何一张图从标注文件里能反查到它什么时候标的、被谁改过、当前状态是初标还是复核过反过来任何一条标注能直接定位到源图上对应的框。下面这套结构就是围绕这两个目标设计的。2.1 目录结构与命名约定先上结构这是我用过最省心的版本proj_det/ images/ 原图只读永远不改不覆盖 0001.png 0002.png dataset/ dataset_all.hdict 主标注文件 dataset_all_bak.hdict 上一次的备份 export/ annotations.json 导出给外部流程 logs/ label_log.csv 追加式操作日志几个细节值得说。图像目录只读这件事听起来是废话但我真的见过有人在标注中途用别的脚本批量重存图像压缩格式从 PNG 换成 JPG结果所有框整体偏移了几个像素——因为尺寸变了。所以定规矩images/目录一旦开工谁都不许动。命名用固定位数的纯数字前缀0001、0002不要用img_1、img_10这种否则排序的时候img_10会排在img_2前面你按顺序标注的时候会莫名其妙地跳来跳去。位数定够比如预计五千张就定五位中途不够位数会很难受。路径全部用相对路径相对于数据集根目录。这一点极其重要。我早期写法图省事把image_file_name塞成了D:/work/proj/images/0001.png这种绝对路径在自己机器上跑得飞起换一台机器、或者把数据集拷给同事所有样本全部读不到图报错还是一堆找不到文件的提示。改成images/0001.png数据集根目录在代码里单独给一个变量迁移就变成改一行。提示整条路径上不要出现空格和中文。HALCON 读图对非 ASCII 路径的支持在不同版本上表现不一致我遇到过路径带中文时read_image静默返回空图像的情况排查了很久才发现是路径问题不是标注问题。2.2 内存里的数据结构怎么摆整体结构就是三层字典嵌套跟你脑子里的模型完全对应顶层是数据集字典挂两个东西类别表、样本列表。类别表是子字典里面两条类别编号元组、类别名元组两者按下标一一对应。样本列表是一个元组里面每个元素是一张图的样本字典装着图名、宽高、这图里所有框的坐标元组和类别编号元组。为什么用字典而不是几十个平行 tuple因为平行 tuple 一旦顺序错位就是灾难比如Rows1有 5 个元素而Labels有 4 个你在循环里根本发现不了最后就是某张图的框和类别整体错位一格。字典用字符串键取值取错键会直接报错这是好事让错误尽早暴露比让它悄悄流过强一百倍。再一个好处是可扩展。哪天要加“标注人”字段往样本字典里塞一个键就行老的读取代码完全不受影响因为它只取自己认识的键。这在我后来做多人分工标注的时候救了命。2.3 增量保存与全量保存的分工这里是我那次血亏之后重新设计的。全量保存就是每次把所有样本重写一遍主文件好处是文件永远自洽坏处是样本多了以后写盘越来越慢——几千个样本、每张图平均三四个框一次全量写在普通机器上大概几百毫秒到一秒多每标一张就全量写一次鼠标会明显卡顿。我的做法是中间态在内存、定时全量落盘、关键时刻强制落盘。具体是标完一张图样本字典加进内存列表不立刻写盘每标满 20 张或者操作员按下“保存”按钮执行一次全量写盘写盘前先把现有主文件copy_file备份成_bak写完再覆盖。这样最坏情况丢 19 张可接受而且卡顿是每 20 张出现一次不打断节奏。另一个技巧是写临时文件再覆盖。直接往主文件上写万一写到一半程序出问题主文件就废了而_bak可能是上一轮的旧数据。所以我写成dataset_all.tmp写完确认文件存在、大小正常再覆盖主文件。多两行代码省掉一次灾难。copy_file (dataset/dataset_all.hdict, dataset/dataset_all_bak.hdict) write_dict (DataDict, dataset/dataset_all.tmp, [], []) copy_file (dataset/dataset_all.tmp, dataset/dataset_all.hdict)3. 用字典把标注写成 .hdict核心代码逐段拆到这一步结构想清楚了剩下的就是把结构填进 HALCON 的字典里。选 .hdict 而不是 CSV 或者自己拼的 txt理由很实在它是 HALCON 原生支持的二进制字典格式读写一行就够不需要自己解析支持嵌套字典和不同长度的元组正好匹配“一张图 N 个框”这种变长结构而且read_dict读回来之后你拿到的就是可以直接get_dict_tuple的结构不用再做一层解析。相比之下CSV 每行一个框、需要额外的聚合逻辑类别表还得另开一个文件。3.1 类别表整个数据集的地基类别表一定要单独建而且要在标第一张之前建好。代码* 只在新建标注库时执行一次 create_dict (ClassDict) set_dict_tuple (ClassDict, id, [0, 1, 2]) set_dict_tuple (ClassDict, class_names, [defect_a, defect_b, defect_c]) create_dict (DataDict) set_dict_tuple (DataDict, class_ids, ClassDict)三个要点。第一编号从 0 开始连续递增不要跳号、不要有洞。我习惯从 0 开始也有同行从 1 开始哪种都行关键是全库统一别出现一半图里缺陷 A 是 0、另一半是 1 这种事——那种数据喂进网络模型会学到两个类其实是一个类精度怎么调都上不去。第二类名只用小写字母、数字和下划线别用中文、别用空格、别用减号。中文类名在导出 JSON 时会遇到编码问题空格在拼接路径时会出问题减号在某些配置解析里会被当成运算符。第三类别表一旦开工就不要插队加类。如果中途发现漏了一个类别正确做法是追加到末尾绝不能插在中间否则已有样本里的编号含义会整体位移那批数据就废了。注意类别数量是网络输出层的一个参数。中途改类别表意味着之前训的权重全部作废得重训。所以我现在的习惯是在开工前先拿二十张图粗略过一遍把可能出现的类别列个清单宁可多留两个空类位也不要边标边加。3.2 单张图的样本字典怎么组装每标完一张图就组装一个样本字典。这里给一段完整可跑的写法假设图是images/0001.png尺寸 1280×1024图里两个框分别是类别 0 和类别 2read_image (Image, images/0001.png) get_image_size (Image, Width, Height) create_dict (SampleDict) set_dict_tuple (SampleDict, image_id, 0) set_dict_tuple (SampleDict, image_file_name, images/0001.png) set_dict_tuple (SampleDict, width, Width) set_dict_tuple (SampleDict, height, Height) * 坐标按 行1/列1/行2/列2 存放四个元组长度必须相等 set_dict_tuple (SampleDict, bbox_row1, [120.0, 300.0]) set_dict_tuple (SampleDict, bbox_col1, [ 80.0, 210.0]) set_dict_tuple (SampleDict, bbox_row2, [220.0, 400.0]) set_dict_tuple (SampleDict, bbox_col2, [190.0, 330.0]) set_dict_tuple (SampleDict, bbox_label_id, [0, 2])image_id我坚持用全局递增整数而不是用文件名。原因很简单排序、去重、核对数量的时候整数比字符串可靠得多而且从日志里看到样本 137 出问题比看到一长串文件名更容易定位。四个坐标分别存成四个元组而不是每个框存一个[r1,c1,r2,c2]的小元组是因为 HALCON 的检测数据集约定就是按这个维度组织的后续接read_dl_dataset_*一类的流程时不用转换。代价是四个元组的长度必须严格一致所以加框的时候一定要四条一起set_dict_tuple别分开加否则很容易出现某个元组少一个元素的情况。3.3 坐标规范与落盘前的强制校正这是我认为最值得单独拎出来讲的一节因为百分之八十的“框错位”“训练报错”都出在这里。HALCON 的坐标顺序是行在前、列在后也就是(Row, Column)行对应 y 方向列对应 x 方向。这与很多人的直觉先 x 后 y相反也是新手最常犯的错。表现出来就是标注看起来正常训练时框全跑到别的方向上去或者干脆整体转置。判断方法很直接——如果你画的框在图里明明偏右下存下来的bbox_row1却比bbox_col1小很多那就是心里想的是 x,y 而手里写的是行,列。第二个规范是左上角必须小于右下角即row1 row2且col1 col2。draw_rectangle1本身返回的顺序是规范的但只要你允许手改、允许外部导入、允许从别的格式转过来就一定会出现反的情况。所以我强制在落盘前跑一段校正R1 : max2 ([0.0, min2 (Row1, Row2)]) C1 : max2 ([0.0, min2 (Col1, Col2)]) R2 : min2 ([Height - 1.0, max2 (Row1, Row2)]) C2 : min2 ([Width - 1.0, max2 (Col1, Col2)]) if (R2 - R1 3 and C2 - C1 3) * 有效框收进样本 else * 小于 3 像素的框当误点丢弃 endif这段做了三件事交换顺序修正、裁剪到图像范围内、丢掉退化框。三像素这个阈值不是随便定的。检测网络在下采样几次之后一个 2×2 像素的目标在网络感受野里基本等于噪音留着只会污染损失函数而且这么小的框绝大多数是鼠标误双击产生的不是真实目标。我用过 2 和 5 做对比2 的效果和 3 差不多5 开始丢掉一些真实的小缺陷所以定在 3。提示裁剪这一步别省。真的会出现标注时把图放大、框画到边缘外的情况坐标变成负数或者超过宽高。这种样本在训练时轻则被悄悄丢弃重则直接抛异常中断整个训练任务而错误信息只说“无效边界框”不会告诉你哪张图。3.4 写盘、读回与内存中的字典管理样本攒够了就塞进数据集字典。注意数据集字典里的样本是一个元组所以要么全程维护一个元组变量往里加要么每次重设整个元组。我选前者逻辑更干净Samples : [SampleDict] set_dict_tuple (DataDict, samples, Samples) * 追加第二张 Samples : [Samples, SampleDict2] set_dict_tuple (DataDict, samples, Samples) * 落盘 write_dict (DataDict, dataset/dataset_all.hdict, [], [])读回来验证同样重要而且我是每次落盘后立刻读一遍read_dict (dataset/dataset_all.hdict, [], [], CheckDict) get_dict_tuple (CheckDict, samples, CheckSamples) get_dict_tuple (CheckSamples[0], bbox_row1, R)为什么要立刻读回来因为写盘成功和写对是两回事。我遇到过一次磁盘空间不足write_dict没报错但文件被截断第二天的读操作直接失败。从那以后我养成习惯写完读一遍检查样本数量对不对、第一条样本的坐标能不能取出来。多花几十毫秒避免隔夜损失。内存管理上还有个隐藏坑HALCON 的字典句柄需要显式释放循环里不停create_dict而不clear_obj对字典是clear_dict一类的清理逻辑长时间运行会持续吃内存。我在做几千张批量导入的时候遇到过内存一路涨到几个 G 然后卡死。解决办法是批量导入按五百张一批每批处理完清理中间字典只保留最终的数据集字典。4. 标注交互层的实操细节格式定好了接下来是画框这件事本身。这部分没有标准答案每个人手感不一样但有几个点我认为是共通的一是别让操作员在坐标系之间来回换算二是别让手滑毁掉已完成的活三是别让人标到一半不知道标到哪了。4.1 画框流程与鼠标键位分工我在 HDevelop 里的交互设计非常朴素只用鼠标三个键左键画框并确认右键撤销刚画的那个框中键存盘并切下一张。为什么不搞快捷键因为 HDevelop 环境下拿键盘输入不如鼠标直接而且标注是高频重复动作键位越多越容易误触。三个键的分工覆盖了“加、减、前进”这三种最基础的操作学习成本几乎为零就算临时找人来帮忙标也不用培训。主循环大概长这样Index : 0 while (Index |ImageFiles|) read_image (Image, ImageFiles[Index]) get_image_size (Image, Width, Height) dev_resize_window_fit_size (0, 0, Width, Height, -1, -1) dev_display (Image) * 画一个框返回后继续画下一个右键则退出本图 draw_rectangle1 (WindowHandle, Row1, Column1, Row2, Column2) ... endwhile实际操作中我会在画框前先把这张图的已有标注叠加上去重绘一遍这样续标的时候心里有数不会重复框同一个目标。叠加用gen_rectangle1生成区域再dev_display就行颜色按类别区分五六个类别基本够用再多颜色就分不清了。注意draw_rectangle1是阻塞的它会一直等你画完才返回。这意味着一件事——在它返回之前任何“自动切图”的逻辑都不会执行。所以别指望在画框过程中做定时保存保存逻辑只能放在两次draw_rectangle1之间。4.2 显示缩放与真实坐标的映射这一节专门讲缩放因为它是最隐蔽的偏移来源。标注时为了看清小目标一定会放大图像。放大之后窗口显示的是局部但你要存的永远是原图坐标系下的绝对像素位置。好消息是如果用dev_set_part来做缩放draw_rectangle1返回的坐标自动就是原图坐标不需要你换算。这是我一直用dev_set_part而不用自己写图像缩放的原因——少一层换算就少一类 bug。有人习惯zoom_image_factor把图放大再显示这时所有坐标都得手动除回缩放倍数一旦忘了除或者除错倍数框就会整体缩小到左上角看起来就是“框全挤在一起”。我的自检办法很土但有效标一个贴到图像右下角的目标看存下来的bbox_row2和bbox_col2是不是接近图像的宽高。如果存下来数值只有宽高的几分之一那就是缩放没换算如果 x、y 反了那就是行列顺序错了。这个动作三十秒能提前发现九成的坐标问题。4.3 断点续标、防丢与多人协作断点续标的核心是能知道标到哪了。我的做法是读取已有标注文件取出所有image_id做成一个已标集合遍历图像列表时跳过已存在的。这样哪怕中途下班关机第二天打开继续标脚本自己会从下一张未标的图开始不用人记。同时屏幕上会显示“已完成 327 / 1200”进度感对持续作业很重要。多人协作的坑更大。两个人同时标同一个 .hdict 文件后保存的一定覆盖先保存的而且不会有任何提示——因为文件是整体覆盖写。我现在的做法是分人分文件每人一个自己的 hdict字段结构完全一致汇总的时候按image_id做去重合并冲突的样本打上标记由第三个人复核。合并脚本必须支持“同一 image_id 两条记录”的检测不能默默取一条否则会丢标注。日志也是必须的。追加式 CSV一行一条记时间、操作类型、image_id、框数量。这东西平时不看出了争议的时候是唯一的凭据。我吃过一次亏某批样本框数量莫名少了一半因为两个人先后覆盖写最后靠日志才确认是哪次覆盖导致的不然只能全批重标。5. 落盘之后自检、统计与格式导出标注文件写完不等于能训练。我在每次批量标注结束、每次合并之后都会跑一遍固定的自检流程大概两分钟能挡掉大部分低级问题。这套流程是纯脚本的不用眼睛一个个看。5.1 五个必查项按顺序跑任一项不过就不往下走检查项判断依据不过时怎么办样本数量等于图像目录里的图数量找出漏标的图补标image_id 唯一性无重复编号去重保留框多的那条坐标合法性全部满足 0≤r1r2height、0≤c1c2width跑一次 3.3 的裁剪逻辑重存类别编号范围全部落在类别表 id 范围内多半是类别表被改过回溯空样本统计无框的图数量可接受确认是不是真的没有目标第三项特别值得展开。坐标合法性检查很容易写遍历所有样本把四个坐标元组取出来跟宽高比一比把越界的样本image_id打出来。我一般会直接打印前十个越界样本方便定位。多数越界是负坐标来源是画框时鼠标在图像边界外侧松开draw_rectangle1会返回负值。第五项容易被忽视。有些图确实没有目标这是负样本本身有价值但比例不能失控。我曾经有一批数据里 40% 是空图因为采集时相机空跑了一段时间标的时候也没注意。训出来的模型召回率低得离谱查了很久才发现是负样本把模型带偏了。现在我会在自检里直接算出空图占比超过 20% 就要停下来看看。5.2 类别分布与框尺寸统计这两个统计是我判断“这批标注能不能用”的主要依据。类别分布失衡是检测任务最常见的硬伤比如某类只有十来个样本训出来的模型对这一类基本等于瞎猜。get_dict_tuple (DataDict, samples, Samples) ClassCount : [0, 0, 0] BoxTotal : 0 for i : 0 to |Samples| - 1 by 1 get_dict_tuple (Samples[i], bbox_label_id, Labels) if (|Labels| 0) for k : 0 to |Labels| - 1 by 1 ClassCount[Labels[k]] : ClassCount[Labels[k]] 1 endfor BoxTotal : BoxTotal |Labels| endif endfor跑完看三个数总框数、每类框数、单图平均框数。如果某类的框数少于总框数的 5%且绝对数量低于 100我会考虑要么补标要么把这个类合并到相近的类里要么直接删掉这个类不训——三种选择都比硬训一个不靠谱的类要好。框尺寸统计同样简单粗暴把每个框的(r2-r1)和(c2-c1)算出来看最小值和最大值。如果最小框只有三四个像素说明有一批极小的目标这时要么提高图像分辨率要么在训练配置里调整最小目标尺寸否则这批标注等于白标。如果最大框接近整图就要确认是不是误把背景框进去了。5.3 导出 JSON 与训练集划分标好的 hdict 在自己这台机器上够用但总有需要跟外部流程对接的时候比如要给别的团队一份通用格式或者要把标注可视化给别人看。这时导出 JSON 就是刚需。HALCON 的write_dict在新版本里支持直接写.json后缀但我不会完全依赖它* 先试原生导出小样本验证一下结构对不对 write_dict (DataDict, export/annotations_export.json, [], [])导出之后一定打开看一眼确认结构是你能接受的那种。如果对格式有严格要求比如要符合某种通用标注规范那就老老实实手拼字符串open_file (export/annotations.json, output, FileHandle) fwrite_string (FileHandle, {images:[) * 循环拼每个 image 的 file_name、width、height 和 bbox 数组 fwrite_string (FileHandle, ],categories:[...]}) close_file (FileHandle)手拼的时候有一条铁律类名只用 ASCII 字符。因为 JSON 里出现中文需要转义而手拼字符串做转义很容易漏一旦漏了整个文件在别的工具里就解析失败你还得逐字符去找到底是哪个引号没闭合。训练集划分我建议单独存一个文件不要塞进主标注文件里。里面就一行一条image_id, split取值为train或val。这样划分策略随时可以改——今天按 8:2 随机分明天想改成按批次分层抽样改这个文件就行不用动主标注。而且划分结果可复现不会出现每次重新跑脚本划分结果都不一样、导致指标没法比较的问题。6. 常见问题排查实录下面这些是我自己踩过、或者帮别人排查过的按“症状 → 原因 → 处理”整理遇到问题先在这里找一遍比从头读代码快。6.1 症状对照表症状最常见原因处理重新打开标注框整体偏移显示层用了缩放图但没换算回原图坐标改用dev_set_part或统一除以缩放倍数框全部挤在左上角行列写反或坐标未除以缩放倍数抽查右下角目标的坐标是否接近宽高读回来的标注是空的写盘时字典句柄已被清理或路径不对写完立刻read_dict验证一次换台机器读不到图image_file_name存了绝对路径统一改成相对路径类别全部变成同一类类别表被改过或样本里存了字符串而非整数核对类别表 id 与样本bbox_label_id类型训练报无效边界框出现负坐标或 r2r1落盘前强制裁剪与排序保存越来越慢每张图都全量重写改成每 20 张保存一次样本数量对不上多人覆盖写或中途合并去重丢数据分人分文件合并时检测重复image_id6.2 几个真踩过的坑第一个坑是图省事复用样本字典。早期我在循环里用一个固定的SampleDict变量反复set_dict_tuple覆盖想着反正每次都重设所有键。问题出在框数量变化的时候上一张图有五个框这一张只有一个框我重设了bbox_row1却没重设bbox_col2结果这一张图就有五个bbox_row1和两个bbox_col2长度不匹配。这种数据在训练时表现极其诡异部分框位置完全随机。正确做法是每张图重新create_dict别复用。第二个坑是清理句柄的时机。我在批量导入脚本里为了省内存每处理完一批就清掉了批次中间的字典结果把还没写盘的样本也一起清掉了。表现是导入完成后样本数少了几十张而且不报错。后来把“清理”和“落盘”的顺序强制调成先落盘后清理就没再出过。第三个坑是忽略浮点与整数的差异。draw_rectangle1返回的是实数值坐标带有小数部分导出流程需要整数直接强转会导致一像素的偏移。单看一像素不算什么但如果你要在训练前后反复做“导出 → 再导入”的往返误差会累积框会慢慢缩小。现在的做法是只在最终导出那一步统一取整中间环节全部保持实数。第四个坑跟文件覆盖有关。我一度把主文件和备份放在同一个目录某次误操作把备份也覆盖了两条命一起没了。现在备份直接落在同级但独立的目录而且命名带日期比如dataset_all_20240612.hdict保留最近三份。硬盘上多占几兆换的是出事之后能退回昨天的状态。最后分享一个我从别的同行那里学来、用下来很舒服的小习惯每批标注开工前先标五张跑一遍完整流程包括保存、读回、可视化叠加、导出。五分钟能把格式、路径、坐标这三类问题全暴露出来。比标到第八百张才发现格式错要划算太多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑