资讯动态

YOLO训练翻车先查数据集:从VOC/COCO格式转换到mAP50排查指南

发布时间:2026/10/25 23:34:33 来源:尧图企业网站定制
简介面向目标检测学习与实战的YOLO机油泄露数据集覆盖VOC、COCO、YOLO三种格式标签基于LabelImg标注的真实场景图片制作适合需要快速搭建泄漏检测模型的开发者或课程实训。压缩包共2000个文件以1986个xml标签、6个html教程、5个txt列表和3个python划分脚本为主整体约616MBxml为VOC标注文件txt文件含YOLO标注与train_list等列表python脚本用于划分训练、验证和测试集html教程则覆盖Linux与Windows下的YOLO环境搭建及自定义数据集训练流程。资源附带可直接运行的划分脚本以及配合案例修改的训练教程能帮助用户从环境配置、标签格式转换到模型训练一气呵成并可按需生成训练集、验证集、测试集减少手动整理成本。目前已有547人学习/下载适合刚接触YOLO或需快速落地机油泄露检测方案的学习者。1. 机油泄露目标检测模型翻车先查数据集而不是改网络产线上机油滴落、渗油的图片拍回来一万张标签也有VOC、COCO、YOLO 三种格式都给齐了按理说丢进 Ultralytics 就能训。可实际跑起来前几个 epoch 损失降得漂亮val 集 mAP50 却死活上不去甚至出现把反光当油污的低级误检。我后来复盘问题几乎都不在模型结构上而在数据集本身格式转换丢框、归一化坐标越界、类别编号错位、划分时数据泄漏这几类坑能把任何网络拖垮。下面按实跑流程把这类数据集从解压到训练拆开讲覆盖三种标签格式差异、转换脚本、训练配置以及最容易让人返工的几个典型问题。适合刚拿到数据集准备训 YOLO 的新手也适合被 val 指标反复折磨的熟手对照自查。2. 解压后先分清三种标签VOC、COCO、YOLO 各管什么2.1 目录结构怎么搭解开 RAR 先确认图片与标签一一对应这类压缩包常见的布局是 images 目录放 JPGAnnotations 或 labels 目录放三种格式的标注。第一步不是写训练代码而是先把目录解开、看清文件命名规则。很多标签错位问题在解压后五分钟内就能发现根本不用等到训练时报错。解压 RAR 在 Linux 下常用unrarWindows 下用 WinRAR 或 7-Zip 直接解即可mkdir -p oil_dataset cd oil_dataset unrar x ../oil_leak.rar # 如果没装 unrar改走 bsdtar # bsdtar -xf ../oil_leak.rar解压后立刻做两件事。第一数一下图片数量和标签数量是否对得上第二抽三张图打开对应标签看看坐标范围是否落在 [0,1] 区间内。数量对不上说明有漏标或漏导坐标越界说明转换环节有问题这两个问题越早暴露成本越低# 统计图片数按 jpg、png 各数一遍别只数一种扩展名 find . -type f \( -name *.jpg -o -name *.png \) | wc -l # 统计 yolo 格式标签数 find . -type f -name *.txt | wc -l这里有个细节容易被忽略图片可能是.JPG大写扩展名而划分脚本默认找.jpg一查数量差了上千张。常见做法是先统一后缀再往下走rename s/\.JPG$/.jpg/ *.JPGWindows 下用批量重命名工具统一成小写。标签与图片的对应关系靠文件名 stem 关联扩展名不一致直接导致stem .txt找不到文件训练时报no labels found。2.2 三种格式坐标体系对比像素坐标与归一化坐标别混用VOC、COCO、YOLO 三套格式的核心差异不在文件后缀而在坐标表示方式。VOC 的 XML 里存的是绝对像素坐标的左上角和右下角COCO 的 JSON 里存的是绝对像素坐标的左上角和宽高而 YOLO 的 TXT 里存的是相对图片宽高的归一化中心点坐标。三者混用是新手最容易翻车的地方把 VOC 的像素值直接写进 YOLO 的 txt训练出来的框全部偏移。格式文件类型坐标含义每行/每对象内容VOC.xml绝对像素xmin, ymin, xmax, ymax对象名、bndbox、图片尺寸COCO.json绝对像素x, y, width, heightcategory_id、bbox、area、iscrowdYOLO.txt归一化中心点 x, y 与宽高 w, h类别号 四个 0~1 小数同一张 1920x1080 的图标注一个机油滴落区域三种格式写出来差别很大。假设滴落区域左上角在 (320, 240)、右下角在 (640, 720)VOC 直接写这四个像素值COCO 写 x320, y240, w320, h480YOLO 则要算中心点 x(320640)/2/19200.25中心点 y(240720)/2/1080≈0.4444宽 w320/1920≈0.1667高 h480/1080≈0.4444。这里最直观的检查方法YOLO 格式里任何坐标超过 1 就是错的等于 1 也要警惕说明标注边界紧贴图片边缘转换脚本没做保护。2.3 先锁死类别映射表再动手names 顺序就是训练时的类别号YOLO 训练时类别号只认数字不看名字。数据集里如果标注了oil_leak和oil_drip两类转换脚本里类别映射表的顺序就决定了训练时 0 和 1 分别代表谁。这个映射表一旦定下来后续所有脚本、data.yaml、验证代码都要沿用同一份中途改顺序等于让模型重新学一遍。# 类别映射表顺序一旦确定不要中途调整 class_map { oil_leak: 0, # 大面积漏油 oil_drip: 1, # 滴落状油污 }实际项目中我一般会额外留一份classes.txt放在数据集根目录内容就是两行oil_leak、oil_drip。这样无论谁接手这份数据第一件事就是看这个文件而不是翻转换脚本去猜编号。压缩包附带训练教程的里面写的 nc 参数和 names 顺序应当与这份文件一致如果不一致以 classes.txt 为准因为它是标注阶段锁定的真实语义。类别数量不多还好说如果机油泄漏场景里还要细分渗油、滴落、油雾类别达到五六个映射表顺序写错会让 val 指标看起来不错但实际全部分类错位。3. 把 VOC 转成 COCO 与 YOLO转换脚本与四个边界坑3.1 图片尺寸从哪里读VOC 的 size 节点是唯一真源转换坐标时绕不开一个问题归一化需要图片宽高这个宽高从哪里来有人用 PIL 打开图片现读有人直接从 XML 的size节点取。两类做法各有适用场景但我建议优先读取 XML 里的 size 节点因为标注工具在写 XML 时已经记录了当时图片的真实尺寸且 VOC 转 YOLO 时这个值通常与文件实际尺寸一致。如果图片在标注后被压缩过XML 里的尺寸反而更接近标注时的坐标系。import xml.etree.ElementTree as ET tree ET.parse(oil_001.xml) root tree.getroot() size root.find(size) w_img int(size.find(width).text) h_img int(size.find(height).text)用 size 节点有个前提标注时没有对图片做裁剪或缩放。如果数据来自多个摄像头各摄像头分辨率不一致XML 的 size 是各自图片的原始尺寸这没问题怕的是有人统一缩放图片但没有同步改 XML那转换出来的 YOLO 坐标会整体偏移。保险做法是转换脚本里加一个断言assert w_img 0 and h_img 0并在转换完成后抽查几张图把 YOLO 坐标转回像素值画框验证。3.2 VOC 转 YOLO一个脚本跑通的最小实现VOC 转 YOLO 是最常用的转换逻辑也很直接遍历 XML解析每个 object 的 name 和 bndbox按类别映射表换成数字再除以图片宽高得到归一化坐标。下面这段脚本可以直接保存为voc2yolo.py使用import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_txt, class_map): 把单个 VOC XML 转为 YOLO txt 文件。 tree ET.parse(xml_path) root tree.getroot() size root.find(size) w_img int(size.find(width).text) h_img int(size.find(height).text) with open(out_txt, w, encodingutf-8) as f: for obj in root.iter(object): name obj.find(name).text if name not in class_map: continue # 不在映射表里的类直接跳过 bndbox obj.find(bndbox) x_min float(bndbox.find(xmin).text) y_min float(bndbox.find(ymin).text) x_max float(bndbox.find(xmax).text) y_max float(bndbox.find(ymax).text) # 非法框保护宽高小于等于 0 的框是脏数据 if x_max x_min or y_max y_min: print(fskip invalid bbox in {xml_path}) continue x_center (x_min x_max) / 2.0 / w_img y_center (y_min y_max) / 2.0 / h_img bw (x_max - x_min) / w_img bh (y_max - y_min) / h_img # 坐标裁剪到 [0, 1]防止越界 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) bw min(max(bw, 0.0), 1.0) bh min(max(bh, 0.0), 1.0) f.write(f{class_map[name]} {x_center:.6f} f{y_center:.6f} {bw:.6f} {bh:.6f}\n) if __name__ __main__: class_map {oil_leak: 0, oil_drip: 1} xml_dir Path(Annotations) out_dir Path(labels) out_dir.mkdir(exist_okTrue) for xml_path in xml_dir.glob(*.xml): voc_to_yolo(xml_path, out_dir / f{xml_path.stem}.txt, class_map)这段脚本里有两个参数值得说明。第一是min(max(x_center, 0.0), 1.0)这个裁剪操作它把越界坐标强制拉回合法区间避免训练时出现 NaN但它也会掩盖标注错误所以建议裁剪前先打印一条警告。第二是class_map字典脚本里写死了两类如果数据集还有第三类必须先在 class_map 里加条目否则那个类会被静默丢弃。压缩包附带的 VOC 标签如果类别名带空格或大小写不一致比如oil_leak和Oil_Leak在映射表里要严格按实际值写。3.3 VOC 转 COCOjson 里的 bbox 用的是 x, y, w, h 像素值COCO 格式比 YOLO 复杂要维护一个大的 json 结构包含 images 和 annotations 两个数组还要给每个标注分配全局唯一的 annotation id。转的时候注意 bbox 是[x_min, y_min, width, height]不是中心点表示面积 area 也要单独算。下面这段代码是累积 annotations 的核心片段import json import xml.etree.ElementTree as ET from pathlib import Path def voc_to_coco(xml_files, class_map, output_path): images, annotations [], [] ann_id 1 for img_id, xml_path in enumerate(xml_files): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w_img int(size.find(width).text) h_img int(size.find(height).text) filename root.find(filename).text images.append({ id: img_id, file_name: filename, width: w_img, height: h_img, }) for obj in root.iter(object): name obj.find(name).text if name not in class_map: continue bndbox obj.find(bndbox) x_min float(bndbox.find(xmin).text) y_min float(bndbox.find(ymin).text) x_max float(bndbox.find(xmax).text) y_max float(bndbox.find(ymax).text) w x_max - x_min h y_max - y_min if w 0 or h 0: continue annotations.append({ id: ann_id, image_id: img_id, category_id: class_map[name], bbox: [x_min, y_min, w, h], area: w * h, iscrowd: 0, }) ann_id 1 coco_data { images: images, annotations: annotations, categories: [ {id: class_map[name], name: name} for name in class_map ], } with open(output_path, w, encodingutf-8) as f: json.dump(coco_data, f, ensure_asciiFalse, indent2)这段代码里ann_id的递增逻辑是 COCO 格式最容易写错的地方。每个标注必须有一个全数据集唯一的 id如果从 0 开始还是从 1 开始影响不大但中间不能有重复。另外 categories 数组要和 class_map 顺序一致Ultralytics 读 COCO 时对类别 id 很敏感id 跳号会导致训练类别数与实际不符。值得提醒的是很多框架支持直接用 COCO 格式训练但 Ultralytics 内部仍然会先转成 YOLO 格式再训所以如果你的最终目标是 YOLO其实可以跳过 COCO 转换直接拿到 YOLO 标签就开训。3.4 转换路上最常见的四个边界坑转换脚本本身不难坑都藏在边界情况里。第一是坐标相等导致的退化框比如 xmin640、xmax640这种框宽为 0训练时 loss 直接 NaN脚本里必须用x_max x_min过滤掉。第二是类别名不在映射表里这类对象会被静默丢弃转换完发现 txt 行数比 XML 里 object 数少别急着怀疑脚本先检查有没有类名拼写不同。第三是坐标等于图片宽高比如 xmax1920 而图片宽也是 1920归一化后得到 1.0严格来说没有越界但对后续数据增强可能产生边缘像素问题常见做法是转换时减一个极小值。第四个坑最隐蔽文件名 stem 相同但来自不同子目录的图片转换后 txt 互相覆盖。比如drill/oil_001.jpg和surface/oil_001.jpg都对应oil_001.txt放在同一个输出目录里必然覆盖。我一般会在输出名里加上子目录前缀或者先规划好统一的目录树再转换。这类问题在压缩包数据里并不少见特别是数据来自多个采集批次时建议解压后先检查是否有同名文件find . -name *.xml | sed s/.*\/// | sort | uniq -d。4. 用 Ultralytics 在本地跑通机油泄露训练划分脚本与超参数4.1 按 8:1:1 划分 train/val/test脚本要防数据泄漏拿到干净的 YOLO 标签后下一步是把数据划分成训练、验证、测试三份。常见比例是 8:1:1划分时除了随机性还要考虑数据泄漏问题。如果同一场景连拍的相邻帧被同时分进 train 和 val验证指标会虚高现场换一个角度立刻露馅。简单做法是直接随机划分严格做法是给图片编号加前缀让同一个摄像头或同一次采集的图片只进一个集合from pathlib import Path import random import shutil def split_dataset(image_dir, label_dir, out_dir, ratios(0.8, 0.1, 0.1), seed42): 按比例随机划分图片与对应标签输出 Ultralytics 目录结构。 random.seed(seed) # 收集图片jpg 和 png 都算别漏了扩展名 images list(Path(image_dir).glob(*.jpg)) \ list(Path(image_dir).glob(*.png)) random.shuffle(images) n len(images) n_train int(n * ratios[0]) n_val int(n * ratios[1]) splits { train: images[:n_train], val: images[n_train:n_train n_val], test: images[n_train n_val:], } for split_name, imgs in splits.items(): (out_dir / split_name / images).mkdir(parentsTrue, exist_okTrue) (out_dir / split_name / labels).mkdir(parentsTrue, exist_okTrue) for img in imgs: shutil.copy(img, out_dir / split_name / images / img.name) label Path(label_dir) / (img.stem .txt) if label.exists(): shutil.copy(label, out_dir / split_name / labels / label.name) return {k: len(v) for k, v in splits.items()} if __name__ __main__: print(split_dataset(images, labels, oil_dataset))这个脚本有三个参数值得关注。seed决定随机序列固定为 42 能保证每次运行结果一致方便复现ratios是三元组前两项为 train 和 val 占比剩下全归 testlabel.exists()的判断很关键图片存在但标签缺失时宁可把这张图从划分结果里剔掉也不要带着无标签图片进训练集否则 Ultralytics 会在加载时报警。划分完打印出来的数量分布应该接近 8:1:1如果 val 集合过大或过小检查是不是图片扩展名没匹配全。4.2 写 data.yamlpath、nc、names 三者对不上必炸Ultralytics 训练时读的不是标签目录而是一个 YAML 配置文件。里面三个字段最容易出错path是数据集根目录train和val是相对 path 的子目录nc是类别数names是类别名列表。很多人只改了 path 忘记改 nc或者 names 顺序和转换时的 class_map 不一致训练时 loss 照常下降但 val 指标和实际语义对不上。# oil_leak.yaml path: /home/user/oil_dataset # 改成你自己的绝对路径 train: train/images val: val/images test: test/images nc: 2 names: 0: oil_leak 1: oil_drip我习惯让test也写上虽然训练时用不到但后期验证时可以直接用同一个 yaml 跑测试集。names必须写成列表形式并保持顺序不要写字典Ultralytics 按索引读类别名。如果压缩包里的类别只有一类机油泄露nc 改成 1names 只留一个 oil_leak。路径里不要带中文字符和空格YOLO 对这类路径处理容易出幺蛾子。4.3 训练命令与三个必调的超参数Ultralytics 的训练入口分命令行和 Python API 两种。命令行适合快速跑通Python API 适合需要回调的场景。这里以 Python API 为例因为可以顺带打印更多运行信息。无论用哪种方式三个超参数建议先定下来imgsz输入图片尺寸、batch批大小、lr0初始学习率。from ultralytics import YOLO # 如果用的是 YOLOv8 就换成 yolov8n.ptYOLO11 就换 yolo11n.pt model YOLO(yolo11n.pt) model.train( dataoil_leak.yaml, epochs100, imgsz640, batch16, lr00.01, patience20, projectruns/oil_leak, nameexp1, valTrue, )imgsz640是速度和精度的折中。机油滴落目标通常占画面比例很小如果想提小目标召回可以考虑 imgsz1280但对显存要求翻倍。batch16按显存调12G 显存跑 640 分辨率大概只能放 824G 可以放 16显存不够时先降 batch不要硬凑。lr00.01对预训练权重是合理的起点从零开始训练建议降到 0.001。patience20表示验证指标连续 20 个 epoch 不提升就早停防止无谓的算力浪费。4.4 先跑 50 张小样本再全量用最少时间证明数据线是通的我每次拿到新数据集都不直接全量训练。先挑 50 张图片、对应 50 个标签跑 20 个 epoch 的小实验。这个实验的目标不是指标是验证数据链路data.yaml 路径写对没有、标签有没有被正确加载、loss 有没有下降。如果小样本跑到第 10 个 epoch 损失还在原地踏步说明数据或配置有硬伤这时候排查成本最低。# 快速建立一个小样本子集train 目录下取前 50 张 mkdir -p mini/train/images mini/train/labels mini/val/images mini/val/labels head -50 dataset/train/images/*.jpg | xargs -I{} cp {} mini/train/images/ # 对应标签也拷过去stem 对应 for img in mini/train/images/*.jpg; do cp dataset/train/labels/$(basename ${img%.jpg}).txt mini/train/labels/ done小样本训练 20 个 epoch 通常只需要几分钟重点看两个信号第一训练日志里all loss有没有下降趋势第二val 的 mAP50 有没有从 0 变成非 0。如果 loss 在降但 mAP 一直是 0优先检查类别编号和 names 顺序。小样本跑通了再启动全量训练基本不会翻车。这一招帮我省掉过无数次无效的长时间训练值得每次都做。5. 训练期踩坑记录标签错位、坐标越界、类别错乱与数据泄漏5.1 训练时提示 no labels found图片标签数量对不上现象启动训练后日志里频繁出现WARNING no labels found in train/images/xxx.jpgval 的 mAP50 恒为 0训练过程看起来在跑实际上模型什么都没学到。原因标签文件与图片文件名不匹配。最典型的是图片是.JPG大写代码用.jpg去找同名 txt其次是 VOC 转 YOLO 时输出的目录层级不对Ultralytics 期待同目录下标签放在labels子目录里而转换脚本把 txt 放在了别的路径。解决先跑一个对照脚本把实际存在的图片名和标签名列出来比对# 找出没有对应标签的图片 for img in train/images/*; do base$(basename $img | sed s/\.[A-Za-z]*$//) [ ! -f train/labels/$base.txt ] echo $img done | head -20输出结果如果是大写扩展名导致的批量重命名即可如果是文件缺失回到第 2 章的计数步骤重新核对。这个坑的典型特征是只有部分图片报错因为混合大小写扩展名的数据里小写那部分能正常匹配。5.2 训练 Loss 下降但 mAP50 卡在 0检测框全偏到图外现象训练过程 loss 从 0.1 降到 0.02看起来收敛了但 val 的 mAP50 始终是 0跑出来的框全在图外或者缩在一个角落。原因标签的归一化坐标越界。常见于 VOC 转 YOLO 时没有读 XML 的 size 节点而是用代码里写死的 1024 作为图片宽高但实际图片是 1920x1080导致所有坐标被缩小了一半框全部偏到左上角区域。解决直接用第 3 章的脚本重转一遍转完跑一次标签检查函数把越界样本打出来from pathlib import Path def check_labels(label_dir, class_num2): 检查 YOLO 标签的合法性与一致性返回问题列表。 problems [] for txt in sorted(Path(label_dir).glob(*.txt)): for i, line in enumerate(txt.read_text().strip().splitlines()): parts line.split() if len(parts) ! 5: problems.append((txt.name, i, 列数不为 5)) continue c, cx, cy, bw, bh map(float, parts) if c class_num: problems.append((txt.name, i, f类别号 {c} 越界)) if cx 0 or cx 1 or cy 0 or cy 1: problems.append((txt.name, i, 中心点越界)) if bw 0 or bh 0 or bw 1 or bh 1: problems.append((txt.name, i, 宽高异常)) return problems print(check_labels(oil_dataset/train/labels))坐标越界这类问题重转标签比手动改 txt 可靠得多。手动改一两个文件还好一百个文件里遍布越界值手改必出错。5.3 类别编号错位模型把油滴识别成油污现象训练完成后测试集表现不错换到现场新图就发现类别完全错乱油滴被识别成大片漏油。原因类别映射表的顺序在转换阶段和训练阶段不一致。比如转换脚本里oil_leak0, oil_drip1训练时 data.yaml 的 names 是[oil_drip, oil_leak]模型学到的 0 类其实是油滴但语义上 0 被当成了油污。解决在转换脚本里把类别映射表写死成一等公民data.yaml 的 names 从这份映射表自动生成不要手工抄写。代码里用一个公共的 classes.py 维护映射表转换和训练都 import 它从源头杜绝顺序分叉。如果模型已经训完另一种补救是重新映射预测结果的类别号再评估但代价比重训还高不如直接修好配置重来一遍。5.4 数据泄漏导致 val 指标虚高现场测试性能暴跌现象训练日志里 val 的 mAP50 到 0.95看起来非常完美。拿到现场新采集的图片一测mAP 直接跌到 0.5 以下。原因训练集和验证集划分时随机打乱了所有图片而数据采集通常是连续拍摄的同一场景的相邻帧在画面内容上高度相似这些相似帧被同时分进了 train 和 val。模型等于在验证时遇到了“见过”的场景指标虚高是必然的。解决划分前先按图片文件名前缀分组。如果图片命名是cam01_0001.jpg、cam01_0002.jpg这种带摄像头编号的按前缀分组后整组划分保证同一个摄像头的数据只进一个集合。如果命名没有规律看图片的拍摄时间戳或 EXIF 信息按时间段切片。严格来说测试集最好用不同时间、不同角度采的图片val 集只能用于调参不能证明模型真的泛化。5.5 batch 调大后 Loss 变 NaN训练中断现象小 batch 训练正常为了加快速度把 batch 从 8 调到 32训练到第 3 个 epoch 时 loss 变成 NaN日志开始刷RuntimeError。原因两个常见因素叠加。第一部分标签里有面积为 0 的退化框小 batch 时影响被稀释大 batch 时异常样本的梯度累积把参数推向发散第二batch 增大后学习率没有相应调整0.01 的初始学习率在大 batch 下容易震荡出 NaN。解决先用第 5.2 节的检查脚本剔除所有非法标签保证数据干净然后按lr0 0.01 * batch / 16粗调学习率batch 从 16 提到 32 时学习率也升到 0.02避免相对学习率骤降导致的振荡。如果数据干净、学习率也调了仍然 NaN把model.train里的ampTrue关掉试一次某些老版本在混合精度下对边界框回归的梯度处理有坑。6. 训练完怎么验证看 mAP 与 PR 曲线的现场判读技巧6.1 验证命令与指标在哪看模型训练完Ultralytics 会在runs/oil_leak/exp1/下生成 weights 目录里面best.pt和last.pt分别是验证指标最优和最后一轮的权重。验证时用 best.pt不要用 last.pt因为早停可能让 last 停在指标开始下降的位置附近yolo val modelruns/oil_leak/exp1/weights/best.pt dataoil_leak.yaml conf0.25输出里重点看三个指标mAP50是 IoU 阈值 0.5 下的平均精度工业落地场景主要看它mAP50-95是多个 IoU 阈值的平均更严苛但是学术对比用的Precision和Recall分别代表查准率和查全率。机油泄漏检测里漏报漏油比误报更危险所以我的习惯是优先保证 Recall 别太低再通过调置信度阈值把 Precision 拉上来。6.2 用 PR 曲线定置信度阈值训练目录下的PR_curve.png是一张 Precision-Recall 曲线它比单个 mAP 数字信息量大得多。曲线越靠近右上角越好但实际使用时看的是曲线上的“拐点”Precision 高而 Recall 还没掉的位置对应的置信度阈值就是现场部署时建议的 conf 值。默认 conf0.25 可能不是最优用验证脚本对不同 conf 跑一遍yolo val modelruns/oil_leak/exp1/weights/best.pt dataoil_leak.yaml conf0.5 yolo val modelruns/oil_leak/exp1/weights/best.pt dataoil_leak.yaml conf0.7对比两个 conf 下的 Precision 和 Recall选一个“漏报率可接受、误报率最低”的点。这个选点的过程没有标准公式完全取决于现场是人工复核还是全自动告警全自动告警就偏向高 Precision人盯着屏幕可以多报一点偏向 Recall。训练完的权重算是个黑匣子PR 曲线是少数能把黑匣子打开一条缝的工具建议每次都看而不是只盯着 mAP。我现在养成的习惯是每次训练完先跑小样本验证、看一眼 PR 曲线拐点再决定是否全量部署这个习惯让我避开了好几次“指标好看、现场翻车”的尴尬。机油泄露这类脏污场景数据里全是油渍、反光、阴影模型选型和调参只能解决一半问题另一半永远在数据准备这一环。希望这些踩坑记录能帮你少走几趟弯路。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑