腰果缺陷检测YOLO数据集全攻略:从标签规范到可视化体检
简介YOLO腰果缺陷检测数据集面向目标检测学习者和工业质检开发人员已按YOLOv5标准目录划分训练集、验证集与测试集图片和标签txt一一对应下载后可直接用于YOLO系列模型训练省去手动整理标注的麻烦。训练集3186张、验证集304张、测试集150张标注采用yolo相对坐标格式覆盖Broken、Defect、SplitDown、SplitUp、Whole五类缺陷。资源包共2000个文件以1999个txt标注文件和1个Python可视化脚本为主以7z压缩包形式提供整体大小约101.94MB。可视化脚本支持随机读取图片绘制边界框并保存无需修改即可运行便于标注质量抽查和结果展示。目前已有65人学习适合作为腰果表面缺陷检测的基线训练数据能有效节省数据整理时间快速验证模型效果。1. 腰果缺陷检测怎么做一套划分好的 YOLO 数据集能帮你省掉什么做腰果缺陷检测的 YOLO 项目最花时间的往往不是模型调参而是数据准备。如果手头正好有一套划分好的 5 类腰果数据集还附带 class 文件和数据可视化脚本从拿到数据到跑通第一次训练可能只需要半天。这套组织方式的优点在于标注格式统一、train/val/test 已经切好、类别文件可以直接喂给 YOLOv8省掉了最琐碎的格式转换和目录整理。这篇内容适合刚入坑 YOLO 目标检测的算法工程师、做坚果分选产线视觉方案的朋友以及想快速验证 YOLOv8 在自己数据上效果的学生。如果你手里正缺一份结构干净的工业检测数据集这份思路可以直接照着搭。重点会落在五类标签怎么定义、数据集目录怎么用、可视化脚本怎么跑以及最容易被忽略的几个数据坑上。2. 五类缺陷的标签体系从工业质检标准到 class 文件到 txt 标注2.1 五类缺陷怎么定义先定标准再开标腰果从原料到成品检测点一般放在脱壳、分选、包装这三段。脱壳后最容易混进来的缺陷是破碎仁和空壳仁分选线上则关注黑斑和霉变。常见做法是把缺陷分成 5 类good完好、broken破碎、black_spot黑斑、mold霉变、immature空壳/干瘪。注意这里5 类指的是缺陷类别数不是图片数每一类都可能出现在同一张图里。标注标准直接决定模型上限。我一般建议先写成一份三行的标注规范只框缺陷本身不框整颗腰果同一颗腰果同时有黑斑和霉变时按最高风险类记为 mold破碎仁以果仁断裂面积超过 1/3 为界小于这个尺度的碎粒不标。这个规范听着简单但在几百张图上保持一致比想象中费劲。标注员如果拿不到清晰边界很容易把同一批数据标出两种风格模型学到的就是平均标注风格。顺带提一句标注工时一个熟练的标注员一天大概能标 200~300 张腰果图前提是规范清晰、不需要反复返工。如果你是自己标建议先标 50 张跑一遍可视化脚本看看规范有没有可操作性再批量推进。数据质量的上限其实在标注规范里就定死了模型只是把规范学回来。2.2 YOLO 标注格式class 文件与 txt 标签怎么对应YOLO 系列包括 YOLOv5/v8的标签格式是一致的每个图片对应一个同名 txt 文件放在 labels 目录下每一行代表一个目标框。一行五个值类 id、归一化中心点 x、归一化中心点 y、归一化宽、归一化高。示例来一条0 0.5123 0.4467 0.1852 0.1234 2 0.7311 0.5109 0.1522 0.0987第一行的 0对应 classes.txt 里的第一行类名第二行的 2对应第三行。这里最容易踩的坑是classes.txt 的类名顺序和标注时的类 id 不一致。比如标注软件里你按 good、mold、broken 排序但 class 文件里写成 good、broken、mold模型训练时不会报错只会把 broken 和 mold 整个错位。拿到数据集第一件事就是打开 class 文件对着标签文件里的 id 检查一遍。还有一个和 class 文件相关的细节容易被忽略类名不要带空格和中文。标注工具生成的类名如果是black spot导出的 txt 里类 id 还是 0~4但后续你把 names 写进 yaml 时带空格的字符串在部分解析器里会被拆开轻则警告重则 names 错位。我一律在 class 文件里用下划线命名black_spot、mold 这种形式最省心。class 文件本身是给人和工具同时看的保持一行一个类名、行号就是 id是最不容易出错的约定。2.3 标注质量三查目标太小、边界模糊、遮挡腰果数据集有三个老大难。第一个是碎粒问题破碎仁会裂成很多小块有的只有十几个像素标注时容易漏漏了模型就把它当背景。第二个是边界模糊果仁发白带一点浅黄和透明输送带的颜色很接近肉眼看着都费劲画框时只能框在缺陷边缘最清楚的那一侧。第三个是堆叠遮挡多颗腰果叠在一起后边的只露出一个小角这种一般只标可见部分不靠脑补。这三项如果没查训练完就会看到 mAP 虚高但实际产线用不起来因为你的验证集和测试集也跟着一起漏标了。拿到划分好的数据集之后我强烈建议先跑一遍可视化脚本抽查而不是直接训练这个流程在第 4 章展开。另外拿到划分好的数据集后别急着看模型先确认三个集合里的类别分布差不多。做法很简单对 train、val、test 各跑一次第 4 章的统计脚本比对打印出来的占比。如果 val 里 mold 只有 1 个框说明划分没有做分层这种数据集要么重新划分要么把 val 换掉否则后面所有验证指标都没参考价值。3. 划分好的数据集怎么用目录结构、data.yaml 与第一次训练3.1 目录结构images/labels 分离别把 txt 和 jpg 放一起拿到手先看目录。正常的一套 YOLO 数据集会这样组织cashew/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── classes.txt ├── cashew.yaml └── vis_script/ ├── visualize_boxes.py └── analyze_dataset.py图片和标签分开的好处训练时按 images 目录读图、按同名 txt 读标签如果混在一个目录Ultralytics 的数据加载逻辑有时会把图片也当标签扫虽然能跑但排查问题很费劲。还有一点是文件名必须一一对应img_001.jpg 配 img_001.txt后缀不同没关系主名要完全一致。我见过不少数据集图片是 jpg、标签是 txt 但主名多了个 _label跑起来一堆 warning。另外有些老数据集会用 train.txt、val.txt 这种清单文件每行写一个图片路径这时候 yaml 里的 train 直接指到 txt 文件。两种组织方式 YOLOv8 都认但我更推荐目录式目录天然分层清单式还得维护路径一旦你移动了数据集根目录txt 里的相对路径就全废了。3.2 用 cashew.yaml 和 YOLOv8 跑通第一次训练把 class 文件里的类名写进 cashew.yamlpath: /data/cashew train: images/train val: images/val test: images/test nc: 5 names: 0: good 1: broken 2: black_spot 3: mold 4: immature注意 path 用绝对路径最稳相对路径以你执行 yolo 命令的工作目录为基准容易因目录不同翻车。接着用 YOLOv8 训练自己的数据集yolo detect train datacashew.yaml modelyolov8n.pt \ epochs100 imgsz640 batch16 patience30 \ projectruns/cashew nameexp1这条命令里三个参数要按你的数据去改。epochs100 是上限配合 patience30 做早停数据量小的时候一般 40~60 轮就收敛了imgsz640 是默认值如果碎粒小目标多建议提到 832batch16 在单卡 16G 显存左右能跑显存小就降到 8。第一次跑别急着上 yolov8s 或 m先用 n 把流程打通验证数据没毒再换大模型。第一次训练建议只选 n 规模。在数据量只有几百张的腰果缺陷场景模型容量大反而更容易把背景噪声背下来先用小模型验证数据管线确认没问题再放大。另外如果手头只有 CPU这个训练基本跑不动至少需要一张 6GB 显存以上的卡8GB 跑 batch8 的 640 分辨率比较稳。3.3 划分比例与随机种子为什么 80/10/10 比 90/5/5 稳划分好的数据集一般按 80/10/10 或 90/5/5 切。数据量在 1000 张以下时我建议 80/10/10验证集太少早停和调参看的指标噪声太大5% 的验证集可能一次 batch 就过完了mAP 抖得没法判断。90/5/5 适合上万张的大数据集验证集 500 张也够用。更关键的是怎么切。按文件名排个序、取前 80% 当 train是最常见的错误做法工业数据的采集是按时间顺序存的前 80% 可能全是白天光线val/test 全是夜间或暗光缺陷分布也跟着偏。正确姿势是随机分层划分把图片按批次或者光线条件分组每组内部按比例抽到三个集合里同时固定随机种子保证可复现。还有一种情况是数据集只给了 train 和 val没有 test。我的做法是把 val 按 6/4 切成新的 val 和 test或者直接对 val 做 5 折交叉验证。ultralytics 也支持 k-fold但对腰果这种千张级数据来说简单留出法加固定种子就够了。固定种子的好处是别人复现你的实验时不会因为划分漂移而得到完全不同的指标。注意先划分、再训练。不要先洗牌再按顺序数文件也不要直接拿 train 当 test 用。验证集泄露在目标检测里会让你的线上精度虚高 5~10 个点这属于典型的假阳性成就感。4. 数据可视化脚本把归一化坐标还原成框训练前先体检4.1 为什么可视化脚本是数据集必带的组件划分好的数据集只给图片和标签其实只完成了一半。YOLO 的标签是归一化坐标肉眼看不到你无法判断标注质量。可视化脚本把 txt 里的五个数值还原成图上的矩形才算真正看到数据。这也是我拿到任何 YOLO 数据集后第一件做的事先抽样画 50 张再决定要不要训练。一套合格的数据可视化脚本至少包含两部分单图标注可视化用于检查框的位置和类别对不对数据集统计可视化用于检查类别分布、目标尺寸分布。前者暴露标注的质的问题后者暴露标注的量的问题。下面两段代码可以直接抄。4.2 visualize_boxes.py画框与类名脚本逻辑不复杂读 txt解析出类 id 和归一化坐标把 x_center、y_center、w、h 换算回像素坐标用 OpenCV 画矩形和类名标签。核心代码如下import argparse import cv2 from pathlib import Path CLASSES [good, broken, black_spot, mold, immature] COLORS [(0, 255, 0), (0, 128, 255), (128, 0, 255), (0, 0, 255), (200, 200, 0)] def draw_one(img_path: Path, txt_path: Path, save_dir: Path): img cv2.imread(str(img_path)) if img is None: print(f[error] cannot read {img_path}) return h, w img.shape[:2] if not txt_path.exists(): print(f[skip] {txt_path.name} not found) return for line in txt_path.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: continue # 非法行直接跳过训练时也可能遇到 cls_id int(parts[0]) xc, yc, bw, bh map(float, parts[1:]) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) color COLORS[cls_id % len(COLORS)] cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, CLASSES[cls_id], (x1, max(0, y1 - 8)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) out save_dir / img_path.name cv2.imwrite(str(out), img) print(f[ok] {img_path.name}) if __name__ __main__: ap argparse.ArgumentParser() ap.add_argument(--images, requiredTrue, helpimages/train 目录) ap.add_argument(--labels, requiredTrue, helplabels/train 目录) ap.add_argument(--out, defaultvis_out, help输出目录) args ap.parse_args() out_dir Path(args.out) out_dir.mkdir(parentsTrue, exist_okTrue) for img_file in sorted(Path(args.images).glob(*.jpg)): label_file Path(args.labels) / (img_file.stem .txt) draw_one(img_file, label_file, out_dir)逻辑分三步。第一步是读图和判空图片损坏时直接打 error避免后续一堆黑图干扰检查第二步是逐行解析一行一个框非法行跳过但要知道它存在——训练端遇到非法行会报数据集错误脚本先暴露出来是好事第三步是坐标还原xc - bw / 2 得到左上角 x 的归一化值乘宽得到像素坐标。这个乘法的单位关系搞错就是后面避坑章里最经典的满屏乱框。运行方式python vis_script/visualize_boxes.py \ --images data/cashew/images/train \ --labels data/cashew/labels/train \ --out vis_out参数说明--images 和 --labels 必须是对应的 train 对 train如果你把 train 的图配 val 的标签画出来会发现框的位置整体对不上--out 会自动创建建议每轮抽查单独建目录别覆盖上一轮结果。4.3 analyze_dataset.py类别分布与框尺寸统计画框解决标得对不对统计脚本解决标得够不够、有没有失衡。我会在同一个 vis_script 里放一个 analyze_dataset.pyimport argparse import glob from collections import Counter from pathlib import Path import matplotlib.pyplot as plt CLASSES [good, broken, black_spot, mold, immature] def main(label_dir: str, out_png: str): counter Counter() widths, heights [], [] labeled_imgs 0 for txt in sorted(glob.glob(str(Path(label_dir) / *.txt))): lines Path(txt).read_text().strip().splitlines() if lines: labeled_imgs 1 for line in lines: parts line.split() if len(parts) ! 5: continue cls_id int(parts[0]) counter[cls_id] 1 widths.append(float(parts[3])) heights.append(float(parts[4])) total sum(counter.values()) print(flabel imgs: {labeled_imgs}, boxes: {total}) for cid in range(len(CLASSES)): ratio counter[cid] / max(total, 1) print(f{CLASSES[cid]:10s}: {counter[cid]:5d} ({ratio:.1%})) fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].bar(range(len(CLASSES)), [counter[i] for i in range(len(CLASSES))]) axes[0].set_xticks(range(len(CLASSES))) axes[0].set_xticklabels(CLASSES, rotation30) axes[0].set_title(box count per class) axes[1].scatter(widths, heights, s1, alpha0.4) axes[1].set_xlabel(norm width) axes[1].set_ylabel(norm height) axes[1].set_title(box size distribution) plt.tight_layout() plt.savefig(out_png, dpi150) print(fsaved {out_png}) if __name__ __main__: ap argparse.ArgumentParser() ap.add_argument(--labels, requiredTrue, helplabels/train 目录) ap.add_argument(--out, defaultdataset_stats.png) args ap.parse_args() main(args.labels, args.out)输出里重点看两个东西。第一是类别数量如果 broken 有 3000 个框、immature 只有 80 个框那 immature 这类基本学不出来后面要考虑加权重或补样本。第二是框尺寸散点如果大量点集中在 0.05 以下说明小目标占比极高imgsz640 可能不够要往上调或者考虑切图训练。把这两张图存成 png每次切完数据都重新生成一份和上次对比。我一般会把 dataset_stats.png 当数据集版本的封面图标注改了、样本补了统计图一对比就看得出来。这种小习惯在项目交接时特别好用新同事不用翻原始标注一张图就明白数据长什么样。提示统计口径是框数不是图片数一张图里可能有 5 个 broken。报告里不要把这两个数字混用。5. 腰果缺陷数据集避坑排查5 条血泪经验5.1 现象loss 正常下降、mAP 上不去loss 曲线一路降到 0.0x看着挺漂亮val mAP 却只有 0.2 上下。原因往往是背景样本缺位数据集中大多数框都精准框着腰果原料上出现的石子、碎壳、输送带杂质没有任何负样本标注。YOLO 本质是在框里 vs 框外之间做区分没有背景负样本模型就只能学会把整张图全当目标。解决第一抽 200 张完全没有目标的图作为空标签样本放进 traintxt 文件为空即可第二检查是不是 train 里全是单一种类的图防止模型把类别和背景绑定。生成空标签文件的做法也简单复制图片文件名、把后缀改成 .txt 就好for f in images/train/*.jpg; do base$(basename $f .jpg); touch labels/train/$base.txt; done。注意空 txt 会被 YOLO 当背景样本但别把 val/test 也塞空标签验证集要保留真实标签。很多腰果数据集的清洗流程都忽视了空标签图这是 yolo 损失函数曲线最会骗人的地方。5.2 现象训练一启动就报错class 数量对不上报错信息类似 number of classes does not match 或者 labels 里出现超出 nc 范围的 id。原因基本就那两种一是 classes.txt 和标签里的类 id 错位标注时排的顺序和 class 文件不一致二是标签文件里混进了非 5 类数据比如别的项目的标签忘删了。解决先把 data.yaml 的 nc 改成 6 跑一版看多出来的是哪类确认后把多余行删掉。快速检查最大类 id 可以用一条命令awk {print $1} labels/train/*.txt | sort -n | tail -1。如果输出 4说明 id 范围正常输出 5 以上就去翻对应文件。再用 4.3 的统计脚本把每个类 id 的框数量打出来重点看有没有 id5 的条目。这种问题用脚本排查 5 分钟靠眼睛翻 txt 能翻一小时。5.3 现象可视化脚本画出来满屏乱框图上的框跑出边界、框的尺寸比目标大好几倍或者集中在某个角。最直接的原因是标签里的 w/h 没有被归一化标注工具导出的是像素宽高你按归一化坐标去还原自然错位。也有可能是 x_center 写成了左上角 xw 写成了右下角 x整个框的几何关系都不对。解决检查标签是否归一化用一条命令看 w 列的最大值awk {print $4} labels/train/*.txt | sort -rn | head -1。如果最大 w 超过 1.0说明标签是像素坐标需要在脚本里除以图片宽高。如果 w 都在 1 附近说明是归一化坐标但中心点写成了左上角。这种错位看着吓人其实只是一行计算的事关键是别拿错位数据去训练——数据管线会把错误的框当 truth模型学到的就是框要画在角落。5.4 现象val 精度高、test 翻车val mAP 0.85换成 test 只有 0.55。原因绝大多数出在划分方式上最常见的错误是按文件名字典序切前 80% 做 train。腰果采集是一批货一拍的文件名前缀往往就是批次号前 80% 和后 20% 的光照、品种、缺陷形态差异很大模型在 val 上看到的都是见过的批次一到 test 就露馅。解决划分前把图片按批次或拍摄时间分组用 group 级别做分层抽样保证 train/val/test 里每批都有比例。分层划分的脚本思路不复杂按图片文件名前缀作为批次键每个批次内部用随机种子抽 80/10/10抽完检查 val/test 每类框数如果某一类少于 20 个框就重新抽。另一个实用做法是划分时记录每个集合的类别数量如果 val 里 mold 只有个位数这个划分就不合格。数据划分这件事没有随机种子和分组信息的数据集建议自己重新划一遍。5.5 现象某一类 AP 特别低其他类正常比如 black_spot 的 AP 只有 0.1其他四类都在 0.7 以上。原因优先查两类问题一是这一类框数太少占比不足 5%模型基本没机会学二是这一类标注边界不一致同一个黑斑有人框得紧有人连果仁边一起框进去模型学到的目标框忽大忽小AP 自然上不去。解决先用 4.3 的统计脚本看数量占比低于 10% 的先补样本或做数据增强。CopyPaste 增强的具体做法先把 immature 这类框抠出来随机粘贴到其他图上粘贴时注意目标尺度要缩放得和原图接近别把一颗腰果放大成筐那么大。ultralytics 的 YOLOv8 没有内置 CopyPaste常见做法是用 albumentations 的 CopyPaste 类写进自定义 pipeline或者训练前先用脚本离线复制一份样本。再抽查这一类 30 张图的标注框把框得过大、过小的统一修正。类别不平衡在 YOLO 里没有天然解只能靠数据层面处理weight 参数只是后悔药不是根治。6. 进阶技巧把可视化脚本变成训练前体检流程一次省 3 小时6.1 体检看板一单图抽查抽样策略别随机抽按类抽。每个类别至少抽 20 张带框的图再抽 20 张完全没框的图确认是不是真没框而不是漏标。把 visualize_boxes.py 跑出来统一看三件事框是否紧贴缺陷边缘、类别名是否和视觉一致、有没有把多颗腰果框在一起。如果某类抽出来的 20 张里有超过 3 张框明显不贴合说明标注规范执行不到位需要先修正再训练。6.2 体检看板二统计分布与调参决策dataset_stats.png 是直接调参的依据。类别占比悬殊先解决数据再谈模型框宽高集中在小值提升 imgsz 到 832 或 1024某一类框特别小还特别少除了补样本还要考虑把这类样本多做几份增强副本。这些决策最好在跑第一个 epoch 之前做掉而不是等三小时训练完再回头看数据。6.3 体检看板三预测对照与产线延伸拿一个初步训练的模型跑测试集把预测结果和真值框打在同一张图上重点看漏检和误检集中在哪类。这一步能把数据集问题和模型问题分开如果漏检全集中在 immature说明数据太少如果误检全是把黑斑当霉变说明两类特征太接近需要在标注规范里加更细的判定标准。等检测稳定后再往产线上接深度相机或做多模态 AI 分析对果仁做颜色、纹理的二次判定那是另一个项目了。我自己后来养成一个习惯任何 YOLO 数据集先跑一遍体检再训练体检不过绝不点训练按钮。这习惯是从一次翻车里长出来的——有一回我拿到所谓划分好的数据集直接开训等了三个小时出来一看 mAP 0.08回头体检发现 200 张图 txt 全是空文件白白耗了一个晚上。数据集的坑只能用脚本提前踩平希望帮到你。本文还有配套的精品资源点击获取