☰
免环境YOLO训练工具:自动标注、多版本训练与ONNX导出避坑
2026/10/28 13:37:15 网站建设 项目流程

简介:这份Yolo系列免环境训练工具包面向需要快速开展目标检测实践的算法学习者和开发者,重点解决本地配置深度学习环境繁琐、标注与训练流程割裂的问题。内容围绕免搭建环境使用展开,支持Yolo3、Yolo4及Yolo8多版本,其中Yolo8需使用N卡显卡;可处理cfg、weights、bin、param、pt等模型文件,并覆盖yolo8n/s/m/l/x等预训练权重,便于模型转换、训练与验证。资源包含自动标注、自动截图、V3/V4模型GPU转换和V8免环境训练等实用功能,适合课程实验、项目原型验证与算法入门练习。压缩包共14个文件,以txt说明、html页面、jpg图示和doc文档为主,约908KB,内容轻量,便于按说明快速查阅与上手。已有1140人学习下载,可作为梳理Yolo标注、转换与训练流程的参考工具包。

1. 免环境 YOLO 训练工具:装不上显卡驱动的那台机器,也能跑完标注到导出

上周帮人处理一台只装了显卡驱动的机器,系统里没有任何 Python 痕迹,连 pip 都用不了。任务却很完整:一批工业相机拍的图片要标注、要训练、要导成 onnx 交给上层业务。以前这套流程光配环境就得半天,版本对不上还得重来。这套 YOLO 免环境训练工具把解释器、推理与训练依赖、几代常用权重一起塞进一个自包含目录,打开就能做自动标注、模型转换和训练,v5、v8、v11 几代网络结构在同一个界面里切换。它适合三类人:手上有数据但不想折腾算法环境的人,想快速验证一批图值不值得投入标注的人,以及要把 pt 转成通用格式交付的人。省掉的是配置,判断力一点没省,下面按实际链路拆。

2. 免环境不等于黑盒:启动链路、缓存目录与三个先验证的点

免环境这三个字最容易让人放松警惕。真实的含义是:工具自带了一份运行时,启动器在拉起进程前把PATH、PYTHONHOME、CUDA_VISIBLE_DEVICES这些变量临时指过去,用完不污染系统。理解这一点很关键,因为后面九成的“点了没反应”“训练跑一半退出”,都能在这一层找到答案。

2.1 三层结构:启动器、任务调度、算法执行

把工具当成三层看,排障效率完全不一样。最外层是启动器,它只做一件事:把自带运行时的路径拼好,设置环境变量,然后拉起内部进程。中间层是任务调度,负责把界面上点的“自动标注”“开始训练”“导出模型”翻译成一份配置加一条内部命令。最内层才是算法执行,加载某一代的权重,跑推理或反向传播。

出错时按层定位就行。界面点了没反应、窗口一闪而过,问题在启动器那一层,看安装目录里的启动日志,通常是运行时路径被移动或杀毒软件拦了。界面有进度但报错信息里有 parameter、unexpected keyword,问题在调度层,配置项和当前版本不匹配。进度条走到一半崩掉、显存报错,才是算法层的问题。

这套分层的实际价值在版本混用的时候最明显。工具里同时存在 v5 和 v8 的权重,两者的加载器和后处理都不一样,调度层靠配置里的版本字段走不同分支。所以权重文件千万别自己改名字试图“骗”过去,改了名但版本字段没改,报的错会非常难懂,比如缺某个 key、张量维度对不上,看着像精度问题,其实是加载器选错了。

2.2 首次启动的三项自检:显卡、后端、路径

装完第一次打开之前,我习惯先做三项检查。免环境不等于免驱动,显卡驱动这一层工具是包不进去的。

# 1) 显卡与驱动是否可见:免环境工具仍然依赖系统驱动这一层 nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv # 2) 安装路径是否含中文、空格、特殊符号:这类路径最容易让内部进程起不来 $PWD.Path # 3) 自带运行时目录结构是否完整,缺目录说明解压不完整 Get-ChildItem .\runtime -Depth 1 | Select-Object -First 20 Name

第一条命令返回显卡型号、显存、驱动版本,三者缺一就先去装驱动,别在工具里找原因。第二条看的是当前目录,路径里出现中文、空格、感叹号,内部进程的启动参数会被截断,表现是双击没反应,日志里只有半条命令。第三条确认 runtime 目录下的解释器和推理后端文件都在,解压中途中断是常见事故,尤其是压缩包放在网络盘上直接双击打开的情况。

后端选择建议先保持auto。工具会先尝试 GPU 后端,失败回落 CPU。CPU 后端跑自动标注可以接受,几百张图也就多等一会儿;但 CPU 上训练基本没意义,一张 640 的图前向就要几秒,跑完一百轮是天方夜谭。所以一旦发现训练时后端是 CPU,先回去查驱动,不要调参。

2.3 缓存与权重目录:多版本共存最容易乱的地方

多版本共存的工具,目录约定就是纪律。权重、数据集、产物三者的位置必须分开,而且都不放在安装目录里。安装目录随时可能被整体替换升级,把权重放里面等于给自己埋雷。

# 工具级配置:放在安装根目录,不要放进 weights 子目录 paths: weights_cache: D:/yolo_tool/weights # 所有权重集中存放,全英文短路径 dataset_root: D:/datasets # 数据集与工具目录分离,便于备份 output_root: D:/runs # 训练与导出产物统一出口,按时间戳建子目录 runtime: backend: auto # auto 先试 GPU,失败回落 CPU threads: 8 # 只对 CPU 后端生效,超过物理核数没有收益 cache_dir: D:/yolo_tool/.cache # 不写 C 盘用户目录,避免权限和清理问题

weights_cache集中放权重的好处是导出、标注、训练三处引用同一个文件,避免出现“标注用的 v8s、训练用的 v8m”这种低级错配,这类错配在指标上表现为标注质量莫名其妙地差。cache_dir单独指到非系统盘,是因为工具的临时缓存会存放大尺寸中间图和导出的中间张量,写进用户目录既占系统盘又容易触发权限问题。

还有一条血泪经验:权重文件名里带上版本和任务类型,比如v8s_det_0612.pt、v11s_seg_0612.pt。工具不限制命名,但人脑会记错。等到同时维护三个项目的模型时,光看best.pt根本分不清哪个是哪个。

3. 自动标注不是全自动:置信度、IoU 与复核阈值怎么定

自动标注这个功能,宣传语和实际体验之间通常有落差。它的本质是用已有权重做预标注,再由人做复核。理解这一点,后面所有参数的选择逻辑就顺了:你不是在追求“标得全”,而是在追求“标得准且复核成本低”。

3.1 预标注的能力边界:什么该交给算法,什么必须人看

预标注擅长的是常见类别、中大目标、清晰光照。工业场景里如果类别就是那么三五类、样本来源固定,第一轮预标注的可用率能到七八成。反过来,小目标、密集堆叠、严重遮挡、长尾类别这四种情况,算法给出的框要么漏,要么框得偏,复核成本比手工标还高。

我一般的做法是分两轮走。第一轮用比较保守的阈值跑一遍,只采信高置信度的框,这批框直接当标签用;低置信度的框单独存一份,进复核队列,人工只需要判断“留还是删”,比从零画框快得多。第二轮拿第一轮的标签训一版模型,再用新模型回头标注那些原来漏掉的图,这叫伪标签迭代。

伪标签迭代有个必须防的坑:确认偏差。模型第一轮漏掉的目标,第二轮大概率还是漏,因为训练数据里就没有这类样本。所以每一轮迭代都要抽 5% 到 10% 的图做全人工检查,专门看漏标,发现系统性漏标就补样本重训,而不是继续迭代。迭代两三轮之后收益就很小了,别指望它自己收敛到一个完美数据集。

还有一个容易被忽略的点:预标注用的权重和后面训练的版本最好一致或同代。用 v5 的权重给 v8 的训练集打标签本身没问题,因为标签格式是通用的 txt,但两代模型对小目标的偏好不同,混着用会让数据集分布带上模型的偏好,训练时表现为某些类别收敛得特别慢。

3.2 参数配置:conf、iou、max_det、imgsz 的取值逻辑

自动标注的参数和推理参数不是一回事。推理时你希望召回高一点,宁可多几个误检让人筛;打标签时你希望精度高一点,删框比改框便宜。

autolabel: weights: D:/yolo_tool/weights/v11s_det_0612.pt imgsz: 1280 # 与后续训练尺寸对齐,小目标多时不要用 640 conf: 0.35 # 预标注偏高,宁可少标不要错标 iou: 0.55 # NMS 阈值,密集场景上调到 0.6~0.65 max_det: 300 # 单图上限,大量触顶说明阈值设松了 classes: [0, 2] # 类别白名单,只标需要的类,减少无关干扰 save_low_conf: true # 低置信度框单独存一份,复核时优先看 format: yolo # yolo / voc / coco,按下游需求选

conf从 0.25 提到 0.35 是我在多数项目里的默认动作。0.25 是推理场景的常见起点,用来打标签会带进大量边缘框,复核时删框的工作量比补框还大。提高到 0.35 到 0.45 之间,漏标会增加,但漏标在复核阶段更容易被发现,因为人看一张图有没有目标很快,判断一个框准不准反而慢。

iou控制的是同一目标被重复框出的抑制强度。密集场景里目标挨得近,阈值太低会把相邻目标压掉,表现为整排零件只剩两个框;阈值太高又会留下大量重叠框。我的经验是先用 0.55 跑十张图看看,出现“该有的没框上”就往上调,出现“一个目标好几个框”就往下调。

imgsz这个参数最容易被随手填 640。如果目标是画面里的小物体,640 缩下去目标只剩十几个像素,预标注必然漏。宁可标注慢一点用 1280,也不要在 640 上标完再训练时改回 1280,两次尺寸不一致,框的精度会打折。

max_det触顶是个信号。跑完一批统计一下每张图的框数,如果有相当比例的图正好卡在 300,说明阈值太松或者画面里确实密密麻麻,前者调阈值,后者要考虑切图分块处理。

3.3 复核、清洗与切分:脚本化处理标注产物

复核完的标注不能直接拿去训练,先过一遍校验。下面这几项是 txt 标注最常见的脏数据:

校验项典型症状处理方式
坐标越界值小于 0 或大于 1裁剪到 [0,1],宽高为 0 的直接删行
宽高为零训练时 loss 变 NaN删行并记录图片名,回头补标
类别越界训练启动报索引错误与 data.yaml 的 names 数量比对
重复行同一目标被算两次按类别加坐标去重
空标签文件背景图被当正样本保留少量作为负样本,其余剔除

比清洗更容易出问题的是切分。随机按 8:1:1 分 train/val/test 看着很标准,但如果数据来自同一段视频抽帧或者同一场景连拍,随机切分会让几乎相同的图同时出现在训练集和验证集里,验证指标虚高,上线就翻车。正确做法是按来源分组切分。

import hashlib, shutil from pathlib import Path SRC_IMG = Path("D:/datasets/raw/images") SRC_LBL = Path("D:/datasets/raw/labels") OUT = Path("D:/datasets/split") RATIO = (0.8, 0.1, 0.1) def group_key(p: Path) -> str: # 同一段视频抽帧、同一场景连拍,文件名前缀通常相同,用前缀分组 # 命名无规律时退化为整体哈希,保证同组不跨集合 return p.stem.split("_")[0] buckets = {"train": [], "val": [], "test": []} for img in sorted(SRC_IMG.glob("*.jpg")): # 对分组键做哈希再取模:同一组永远落到同一个集合,且结果可复现 h = int(hashlib.md5(group_key(img).encode()).hexdigest(), 16) r = (h % 100) / 100 split = "train" if r < RATIO[0] else ("val" if r < RATIO[0] + RATIO[1] else "test") buckets[split].append(img) for split, items in buckets.items(): (OUT / split / "images").mkdir(parents=True, exist_ok=True) (OUT / split / "labels").mkdir(parents=True, exist_ok=True) for img in items: lbl = SRC_LBL / f"{img.stem}.txt" shutil.copy2(img, OUT / split / "images" / img.name) if lbl.exists(): shutil.copy2(lbl, OUT / split / "labels" / lbl.name) print(split, len(items))

这段脚本的核心是group_key和哈希取模两处。group_key决定什么算“同源”,命名有规律时用前缀,没规律时改成按文件修改时间分桶也行,本质是让相关样本待在一起。哈希取模保证同一组永远进同一个集合,而且重复运行结果一致,这对复现实验很重要。比例不要卡死 0.8,小数据集(几百张)留 0.15 给验证集更稳,测试集可以干脆不切,直接从验证集里抽。

4. 训练与导出:多版本差异、参数联动和 pt 转 onnx 的对齐细节

训练这一环,工具替你挡住了环境,挡不住参数之间的相互牵连。同一个学习率在 640 上能用,换到 1280 就需要重调;batch 从 16 降到 8,等效学习率也跟着变。这一章按“参数联动、版本差异、导出”三块讲,最后一块是转换后最容易出错的地方。

4.1 训练参数联动:imgsz、batch、workers 与显存的三角关系

先给一份我常用的起步配置,再逐项说为什么。

train: data: D:/datasets/split/data.yaml weights: D:/yolo_tool/weights/v11s_det_0612.pt epochs: 120 imgsz: 640 batch: 16 # 显存不足先降这里,别急着降 imgsz workers: 2 # Windows 上建议 0~2,卡死多半是这里 device: 0 amp: true # 无 Tensor Core 的老卡建议关掉 cache: ram # 数据集小于内存 1/3 时开,否则用 disk 或 false patience: 30 close_mosaic: 10 # 最后 10 轮关闭 mosaic,收敛更稳

batch和imgsz都吃显存,但优先级不同。OOM 的时候先降 batch,再考虑降 imgsz,因为 imgsz 直接改变了算法看到的目标尺度,本来能认出来的小目标缩下去就没了。我见过为了塞进显存把 640 降到 416 的训练,指标掉得莫名其妙,最后发现是目标尺度不匹配,跟模型能力无关。

workers是 Windows 平台的重灾区。多进程数据加载在 Windows 上要靠 spawn 方式启动,worker 数设太高会卡在启动阶段或者跑到第几个 epoch 突然挂掉。稳妥值是 0 到 2,宁可数据加载慢一点。判断方法很简单:训练启动后看 GPU 利用率,如果一直低于 40% 且 CPU 某几个核满载,说明卡在数据加载,这时候加 worker 有意义;如果 GPU 利用率 90% 以上,加 worker 纯粹是浪费内存。

amp混合精度能省显存提速,但在没有 Tensor Core 的老卡上容易出现梯度变成 NaN,表现是训练前几轮 loss 就变成 nan 或者直接不收敛。这类卡上老老实实关掉 AMP,慢一点但稳。cache: ram适合小数据集,几百张图直接全塞内存,epoch 之间不用重复读盘;数据集超过内存三分之一就改用 disk,否则系统开始换页,训练速度反而断崖下跌。

close_mosaic这个参数常被忽略。mosaic 增强把四张图拼一张,能显著提升小目标和复杂背景的鲁棒性,但它也让训练分布和真实推理分布不一致。最后几轮关掉它,让模型在接近真实分布的数据上收尾,指标通常能再涨一两个点。轮数按总 epoch 的一成左右给,120 轮给 10 轮比较合适。

4.2 多版本差异:v5 与 v8/v11 在配置、输出头和解码上的不同

工具支持多版本,最容易翻车的地方不在训练,而在导出后的后处理。同一份 onnx,用 v5 的解码逻辑处理 v8 的输出,结果会乱得毫无规律。

对比项v5 系列v8 / v11 系列
检测头带 obj 置信度分支无 obj,类别分数直接给
输出形状(bs, 25200, 5+nc)(bs, 4+nc, 8400)
是否含 anchor有,配置文件里写死无,anchor-free
配置方式hyp.yaml + data.yaml 分离训练参数合并进一份 args
后处理先乘 obj 再取类别最大值直接取类别最大值

输出形状这一行是核心。v5 的通道维在最后,v8 的通道维在中间,很多自写的解析代码默认按 v5 写,换到 v8 就出现“框全在左上角”或者“分数全是同一个值”的现象。判断方法:导出后打印输出张量的 shape,第二维等于 4+类别数就是 v8/v11 系,最后一维等于 5+类别数就是 v5 系。

配置文件的结构差异也值得说一句。v5 把数据配置和超参配置分开,改学习率要去 hyp 文件;v8/v11 把训练参数统一到一处,学习率直接写在训练配置里。工具界面上看着都是几个输入框,但底层读的文件不一样。切换版本后如果发现学习率没生效,先去确认改的是不是当前版本真正读取的那个文件。

还有一个隐蔽的差异是标签的类别索引。两代都从 0 开始,这点一致,但如果数据集是从别的标注工具导过来的,有的工具默认从 1 开始,导入后类别整体偏移一位,训练时表现为 loss 正常下降但 mAP 接近 0,因为模型学的是“把 A 类当 B 类”。这种问题看指标曲线看不出来,只能抽查几张图的标签和预测。

4.3 导出与转换:opset、dynamic、NMS 归属与输入形状

导出这一环,参数不多但每个都影响下游能不能用。

参数建议值影响
opset12 或 17太低缺算子,太高下游推理框架不支持
dynamic交付侧 batch 不固定时开开了之后部分框架推理变慢
simplify开去掉冗余算子,提升兼容性
half边缘设备部署时开fp16 精度损失需单独验证
内置 NMS交付侧不想实现后处理时开开了输出变成最终框,灵活性下降
输入尺寸与训练一致,别随手改改了框会整体偏移

内置 NMS 这个选项要重点说。开了以后 onnx 直接输出检测框,上层业务拿过去就能用,省掉后处理代码;代价是阈值被固化在模型里,业务侧想调置信度就得重新导出。我的做法是:交付给不熟悉算法的团队时开,自己项目里关掉,把 NMS 留在业务侧,方便调。

导出后第一件事不是拿去做推理,而是检查图的输入输出形状。

import onnx m = onnx.load("D:/runs/export/best.onnx") for t in m.graph.input: dims = [d.dim_value or d.dim_param for d in t.type.tensor_type.shape.dim] print("input:", t.name, dims) for t in m.graph.output: dims = [d.dim_value or d.dim_param for d in t.type.tensor_type.shape.dim] print("output:", t.name, dims) print("opset:", [o.version for o in m.opset_import])

输入应该是[1, 3, H, W]或带动态维的['batch', 3, H, W],输出形状对照上表判断是哪一代。如果输出第二维是 4+类别数,说明是 v8/v11 系,后处理必须按转置后取类别分数的方式写。opset 版本也在这里确认,下游框架报“不支持的算子”时,八成是 opset 太高。

导出尺寸必须和训练一致。训练 640 导出 640,别因为部署侧想省算力改成 512。改了之后不是不能用,而是所有框都会按比例偏移,偏移量跟 letterbox 的填充有关,肉眼看着像“框整体往右下角飘了十几像素”,很难联想到是导出尺寸的问题。

5. 避坑排查:五条从现象倒推原因的记录

这一章全是踩过的坑,按“现象、原因、解决”写,能对号入座最好。

5.1 自动标注框整体偏移几十像素

现象:一批图标注完,框的位置统一往某个方向偏,偏移量看着和图片尺寸有关,小图偏得少,大图偏得多。

原因:预标注的输入尺寸和坐标还原时的缩放比例不同步。工具内部会做 letterbox 填充到正方形,推理完要把框映射回原图坐标,映射时用的缩放比必须和填充时的缩放比是同一个。中途改过imgsz但没重新加载配置,或者用了别人导出的中间结果,比例就对不上。

解决:确认标注任务的imgsz和实际加载的权重训练尺寸一致;改完参数后重启任务而不是只点“开始”;抽查时不要只看框在不在目标上,拿一张已知尺寸的图量一下框的像素坐标。

5.2 训练 loss 正常下降但 mAP 一直是 0

现象:训练日志里分类和框回归的 loss 都在降,验证集 mAP 始终是 0.000,偶尔有 0.001。

原因:类别索引错位。数据集从别的标注工具导出,标签里的类别从 1 开始,而训练配置里 names 从 0 开始数,模型学到的类别和验证时计算的类别完全对不上。另一种可能是 data.yaml 里 names 的数量和标签里的最大类别号不一致。

解决:随机抽十个标签文件,看类别列的最小值和最大值;对照 data.yaml 的 names 长度;如果是整体偏移一位,用脚本把所有标签的类别号减一,别手工改。

5.3 训练跑到一半进程消失,日志没有报错

现象:前几个 epoch 正常,跑到十几个 epoch 后窗口直接退出,日志最后一行是正常的进度输出,没有任何异常堆栈。

原因:最常见的是workers太高导致的数据加载进程崩溃,Windows 上尤其明显;其次是内存不够,cache: ram开着但数据集超过物理内存,系统杀进程时不会留日志。

解决:先把workers降到 0 复跑一轮,能跑通就说明是加载进程的问题;再把cache改成false,排除内存因素;同时看一眼系统事件日志里有没有进程被终止的记录。

5.4 验证集指标很好,换一批图就崩

现象:验证集 mAP 0.85 以上,实际拿新拍的图一测,漏检一大片。

原因:数据泄漏。数据集来自同一段视频抽帧或者同一场景连拍,随机切分后训练集和验证集里几乎是同一批图的不同帧,模型等于在背答案。新图的光照、角度、背景一变,性能立刻现原形。

解决:改成按来源分组切分,切完检查训练集和验证集的图片文件名前缀是否有重叠;验证集里至少要有一部分来自完全独立的采集批次,哪怕只有几十张,也比虚高的指标有用。

5.5 导出 onnx 后推理结果全乱

现象:pt 模型推理正常,转成 onnx 后框的位置、数量、类别全不对,有的框跑到图像外面。

原因:后处理代码按 v5 的输出格式写的,实际导出的是 v8/v11 的模型。v5 输出是(bs, 25200, 5+nc),需要先乘 obj 再取类别最大;v8 是(bs, 4+nc, 8400),通道在前,要转置后直接取类别分数。格式对不上,解码出来的数值全是错的。

解决:导出后先打印输出形状,对照 4.2 节的表格判断代际,再选对应的解码分支;如果工具支持内置 NMS,交付场景下直接开,把后处理固化进模型,省掉这一层判断。

6. 上线前的一致性验证:把 onnx 和原始输出差多少算清楚

导出这一步做完,很多人直接交付。我吃过一次亏:模型在本地跑得好好的,到对方环境里框普遍偏小一圈,查了两天才发现是对方做了额外的缩放。从那以后,每次导出我都强制跑一遍数值对齐,把差异量化出来,而不是靠肉眼看“差不多”。

对齐的思路很直接:同一张图,分别走原始权重和 onnx,比较最高分框的类别、分数、坐标。下面这段脚本用 onnxruntime 做推理,letterbox 和坐标还原都自己写,避免依赖工具内部实现。

import numpy as np, onnxruntime as ort, cv2 def letterbox(img, size=640, color=114): # 保持长宽比缩放后居中填充,填充色 114 是通行约定,换色会影响边缘精度 h, w = img.shape[:2] r = min(size / h, size / w) nh, nw = int(round(h * r)), int(round(w * r)) out = np.full((size, size, 3), color, dtype=np.uint8) top, left = (size - nh) // 2, (size - nw) // 2 out[top:top + nh, left:left + nw] = cv2.resize(img, (nw, nh)) return out, r, left, top img = cv2.imread("D:/datasets/split/val/images/0001.jpg") lb, r, dx, dy = letterbox(img, 640) # BGR 转 RGB、HWC 转 CHW、归一化到 0~1,三步顺序不能乱 x = lb[:, :, ::-1].transpose(2, 0, 1)[None].astype(np.float32) / 255.0 sess = ort.InferenceSession("D:/runs/export/best.onnx", providers=["CPUExecutionProvider"]) out = sess.run(None, {sess.get_inputs()[0].name: x})[0] print("输出形状:", out.shape) # v8/v11 通常是 (1, 4+nc, 8400) pred = out[0].T # 转置成 (8400, 4+nc) cls_scores = pred[:, 4:] best = int(cls_scores.max(axis=1).argmax()) score = float(cls_scores[best].max()) cx, cy, bw, bh = pred[best, :4] # 先把填充偏移减掉,再除以缩放比,顺序反了框会整体位移 box = [(cx - bw / 2 - dx) / r, (cy - bh / 2 - dy) / r, (cx + bw / 2 - dx) / r, (cy + bh / 2 - dy) / r] print("类别:", int(cls_scores[best].argmax()), "分数:", round(score, 4), "框:", [round(float(v), 1) for v in box])

跑完拿到四个数:类别、分数、框坐标。再让工具用原始权重跑同一张图,把结果抄出来对比。判定标准我一般卡这么几条:

检查项fp32 导出fp16 导出
最高分类别一致必须必须
置信度绝对误差小于 1e-3小于 1e-2
框坐标误差小于 1 像素小于 3 像素
框数量差异单图不超过 2 个单图不超过 3 个

类别不一致直接判定导出失败,回去查是不是拿错了权重或者后处理分支选错。分数和坐标在容差内就算通过,fp16 的误差放宽是因为半精度本身有损。框数量偶尔差一两个通常是边缘目标在 NMS 阈值附近抖动,把阈值的边界情况记下来,交付时写进说明里。

小数据集上还有一件事值得做:导出前后各跑一遍验证集,比 mAP。掉点在 0.5 个百分点以内可以接受,超过 1 个点就要查,多半是后处理里的 NMS 参数和训练时验证用的不一致,或者 letterbox 的填充色在两端不同。

这套流程走完再交付,心里是有数的。从那以后我每次导出模型,不管对方催得多急,都强制把这张对齐表填一遍再发出去,哪怕只是 fp32 到 fp32 的同格式重导。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询