简介:面向食品工业智能质检与曲奇饼干缺陷检测场景,这份资源提供了一套可直接用于图像分类算法训练的数据集配套说明。数据集包含1000张真实产线采集的曲奇饼干图片,按Defect_Color、Defect_No、Defect_Object、Defect_Shape四类缺陷整理为对应分类文件夹,标注质量高,可直接接入YOLOCLS/YOLO11cls等图像分类流程。随附的PDF中介绍了数据集基本情况、类别分布及百度网盘获取方式,并通过缩略图直观展示四类缺陷的典型样本与目录组织方式;同时额外提供YOLO11cls一键训练脚本和博主训练结果日志,便于快速跑通训练、对照调参。资源包共1个文件,为5.87MB的PDF文档,轻量易下载;已有29人学习,适合食品质检算法初学者或需要补充通用食品缺陷样本的开发者,作为曲奇饼干缺陷分类项目的前期数据准备与模型起步参考。
1. 目标分类:曲奇饼干缺陷检测数据集,为什么只用 YOLO11cls 就够了
做曲奇饼干缺陷检测,很多人第一反应是上目标检测,画框、出坐标。但手头只有 1000 张图的时候,我一般会直接转到“目标分类”这条路:用 YOLO11cls 做整图级别的图像分类,把缺陷检测拆成“这张图是正常,还是某一类缺陷”。这不是绕路,而是数据量决定的取舍——1000 张图分给 5 个类别,每类平均只有 200 张,目标检测要同时学“框的位置”和“框的内容”,参数空间比分类大得多,很容易在这么小的数据量上翻车。
这篇笔记适合两类人:一类是产线质检场景里想快速验证“AI 能不能分拣缺陷饼干”的工程师,另一类是刚接触图像分类、手里有一批杂乱图片但不知道如何组织成数据集的初学者。标题里“对应分类文件夹整理”和“YOLO11cls 一键训练脚本”是整套方案的两条腿:前者解决数据和标签怎么摆,后者解决怎么用最少命令跑出一个可用模型。后面我会把这些拆成能直接抄作业的步骤,覆盖目录结构、类别定义、训练参数和几个我踩过的坑。
2. 把 1000 张饼干图整理成 YOLO11cls 能吃的目录结构:自检脚本与分类文件夹约定
2.1 先搞清楚 YOLO11cls 的“cls”到底要什么格式
YOLO11cls 是 Ultralytics YOLO11 系列里的图像分类分支,后缀 cls 是 classification。它不像 YOLO11 detect 那样要 txt 标注框,也不像 segment 要多边形坐标。它吃的就是最朴素的 ImageFolder 结构:按类别建文件夹,文件夹里放对应类别的图片,训练时它自动用文件夹名当标签。
很多人第一次用的时候拿“目标检测格式”的思维去套,结果把标签文件放进去,训练直接报错。我一般会先把目录结构摆成下面这样,再往后走:
cookie_ds/ ├── train/ │ ├── normal/ # 正常饼干 │ │ ├── 0001.jpg │ │ └── 0002.jpg │ ├── crack/ # 裂纹饼干 │ │ └── ... │ ├── chipped/ # 缺角饼干 │ └── burnt/ # 焦斑/过烤 ├── val/ │ ├── normal/ │ ├── crack/ │ ├── chipped/ │ └── burnt/ └── test/ # 可选,纯推理用 ├── normal/ └── crack/train 和 val 下面各自再挂类别文件夹,这是 YOLO11cls 的默认约定。它的 Train 参数只接收 data 指向“有 train 和 val 子目录”的根目录。如果你只有一个平铺的文件夹,里面所有图片混在一起,那必须先做一次划分,不能直接把平铺目录塞进去。
2.2 用脚本检查图片能不能用:损坏文件、非图片文件、类别数量统计
整理 1000 张图时最容易翻车的不是目录少了,而是图片本身有问题。有些是下载来的缩略图损坏,有些是拍照时生成的空文件,甚至还有隐藏的 .DS_Store 混在文件夹里。YOLO 训练时一旦读到无法解码的图片,轻则跳过,重则中断训练进程。我习惯先跑下面这个自检脚本:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 曲奇饼干缺陷数据集自检与统计脚本 用法: python check_dataset.py /path/to/dataset """ import os import sys from collections import Counter from PIL import Image def verify_dataset(root): allowed_ext = {'.jpg', '.jpeg', '.png', '.bmp', '.webp'} counter = Counter() broken = [] total = 0 for cls_name in os.listdir(root): cls_dir = os.path.join(root, cls_name) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): ext = os.path.splitext(fname)[1].lower() if ext not in allowed_ext: print(f"[skip] 非图片文件: {cls_name}/{fname}") continue path = os.path.join(cls_dir, fname) total += 1 counter[cls_name] += 1 try: with Image.open(path) as im: im.verify() # 只校验文件头,不加载完整像素 except Exception: broken.append(path) print("类别统计:", dict(counter)) print(f"总图片数: {total}, 损坏数: {len(broken)}") for p in broken: print("损坏文件:", p) if __name__ == "__main__": verify_dataset(sys.argv[1] if len(sys.argv) > 1 else ".")这段脚本的逻辑很直白:遍历根目录下的每一层,把文件夹名当类别名。im.verify()是 PIL 里开销很小的校验方式,它只检查图片文件头是否符合 JPEG/PNG 格式,不会把整张图解码成像素数组,所以 1000 张图跑完也就几秒钟。损坏的文件不要留着,直接删掉或移出数据集目录,否则后面训练时会在数据加载阶段莫名其妙卡住。
参数说明:allowed_ext白名单防止把配置文件、临时文件混进数据集;counter用collections.Counter统计每个类别张数,方便你一眼看出类别是否失衡。如果某个类别只有几十张而其他类有三百张,后文的数据增强和类别权重就要提前做准备,而不是等训练完再后悔。
2.3 训练集和验证集怎么切:按“炉次”切,不要按“图片”随机切
划分 train/val 是这类整理里最玄学的一步。很多人图省事,把所有图片随机打乱,按 8:2 比例分。我踩过的坑是:曲奇饼干通常是一炉一炉烤的,同一炉的照片光线、烤箱温度、冷却时间完全一致,随机切分会把同一炉的图同时分进训练集和验证集。模型看着“验证集准确率 98%”,实际只是记住了炉子编号,换一条产线直接垮掉。
常见做法是按拍照批次或炉次分组。整理时文件夹里每批命名加前缀,比如batch01_0001.jpg、batch01_0002.jpg,脚本里先取前缀分组,再整组划入 train 或 val。这样验证集是真没见过的批次,结果才可信。划分比例上,1000 张图我一般用 8:2,验证集至少 200 张;如果类别多,会额外抽 100 张做 test 集,只在最终验证时用一次。
3. 缺陷类别怎么定才不白标:从“正常/裂纹/缺角/焦斑”到类别映射脚本
3.1 缺陷分类粒度:宁可粗分五类,不要细碎分十五类
曲奇饼干缺陷和轴承缺陷检测、试管缺陷检测这类工业场景有个共同点:缺陷花样多,但可用的标注数据少。标题里明确说的是“图像分类数据集”,不是目标检测,所以类别定义直接决定了模型能不能训出来。常见的分类方案是五类:normal(正常)、crack(裂纹)、chipped(缺角)、burnt(焦斑/过烤)、foreign(异物/杂质)。
为什么不把焦斑细分成“表面焦斑”“边缘焦斑”?因为 1000 张图分到十几个类,每类不到 100 张,分类头根本学不到稳定的视觉特征。分类任务的类别设计第一条原则是:类间差异要大,类内差异可以大但必须有共同视觉指纹。焦斑不管在边缘还是表面,视觉指纹都是“深褐色区域”,归成一类没问题。裂纹的形态差异大,但核心指纹是“细长的深色线状结构”,也可以归为一类。
下面的脚本解决的是另一个实际问题:从现场收集的图片,文件夹命名往往乱七八糟,有中文、有英文缩写、有大小写混用。先把它们全部映射到统一类别,再生成 YOLO 需要的目录结构:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 统一类别映射与数据集目录生成脚本 用法: python build_dataset.py /path/to/raw_images /path/to/output """ import os import sys import shutil CLASS_MAP = { "good": "normal", "normal": "normal", "ok": "normal", "合格": "normal", "crack": "crack", "cracked": "crack", "裂纹": "crack", "裂": "crack", "chipped": "chipped", "缺角": "chipped", "break": "chipped", "burnt": "burnt", "burn": "burnt", "焦": "burnt", "过烤": "burnt", "foreign": "foreign", "异物": "foreign", "杂质": "foreign", } def map_and_copy(raw_root, out_root, split_ratio=0.8): os.makedirs(out_root, exist_ok=True) for cls_name in os.listdir(raw_root): cls_dir = os.path.join(raw_root, cls_name) if not os.path.isdir(cls_dir): continue unified = CLASS_MAP.get(cls_name.lower()) if unified is None: print(f"[warn] 未映射的类别: {cls_name},跳过") continue for split in ["train", "val"]: os.makedirs(os.path.join(out_root, split, unified), exist_ok=True) files = [f for f in os.listdir(cls_dir) if os.path.splitext(f)[1].lower() in {'.jpg', '.jpeg', '.png'}] for i, fname in enumerate(files): dst_split = "train" if i < len(files) * split_ratio else "val" shutil.copy2(os.path.join(cls_dir, fname), os.path.join(out_root, dst_split, unified, fname)) if __name__ == "__main__": map_and_copy(sys.argv[1], sys.argv[2])这段脚本的价值在于把“脏名字”统一成干净类别。CLASS_MAP里我预先做了大小写归一,cls_name.lower()保证 “Crack” 和 “crack” 不会被拆成两个类。没有映射到的名字会打 warn 并跳过,而不是直接静默忽略,这样你能发现哪些文件夹命名超出了预设。
脚本里的划分是按单个类内文件顺序前 80% 进 train。这只适合已经完全打乱顺序的原始目录;如果要按炉次划分,改成“取文件名前缀分组”即可。shutil.copy2保留文件元数据,如果磁盘吃紧可以改成shutil.move,但移动操作不可逆,我一般先复制后人工检查产物。
3.2 样本不平衡:1000 张图里的“二八定律”怎么处理
真实产线收集的缺陷数据永远是不平衡的:正常饼干占 60%,裂纹占 20%,缺角占 10%,焦斑和异物各占 5%。如果不做处理,模型会走向“偷懒策略”——把所有图都判成 normal,因为这样准确率已经有 60%。这不是模型坏了,是损失函数在数据不平衡下自然收敛到这个状态。
常见做法有三个层级。第一层:训练时把weighted=True打开,Ultralytics 的分类训练接口支持类别权重,少数类样本在损失函数里会被放大;第二层:对 minority 类做离线增强,比如对焦斑类做旋转、亮度抖动、随机裁剪,把 50 张扩到 150 张;第三层:如果懒得写增强代码,就用augment=True让 YOLO 内置的 HSV、翻转、缩放策略在训练时生效。我一般三层同时上,但第一层是底线,哪怕只开weighted也能明显改善焦斑和异物这两类的召回率。
4. 用 YOLO11cls 一键训练脚本跑通分类模型:命令、参数与训练日志解读
4.1 一条命令启动训练,但“一键脚本”不是只写一行 yolo
网上到处是“一键训练脚本”,但很多脚本只是把yolo classify train一行命令包了层皮,没有任何容错。我的一键脚本一般做三件事:检查数据集目录存在、创建日志目录、设置好相对路径后执行训练。这样做的好处是下次换机器、换路径时不用一条条手改命令,也避免因为当前目录不对导致 data 路径解析失败。
#!/usr/bin/env bash # 一键训练脚本 train.sh: YOLO11cls 曲奇饼干缺陷图像分类 set -e # 任何一行失败立即退出,不要带病继续训 cd "$(dirname "$0")" DATA=data/cookie_ds # 前面整理好的数据集根目录 PROJECT=runs/classify # 训练输出目录 NAME=cookie_run_$(date +%Y%m%d_%H%M%S) # 检查数据集目录 if [ ! -d "$DATA/train" ] || [ ! -d "$DATA/val" ]; then echo "数据集目录不完整, 需要包含 train/ 和 val/ 子目录" exit 1 fi mkdir -p "$PROJECT" yolo classify train \ data="$DATA" \ model=yolo11cls.pt \ epochs=50 \ imgsz=224 \ batch=32 \ lr0=0.001 \ patience=15 \ cache=False \ project="$PROJECT" \ name="$NAME" \ pretrained=True \ augment=True echo "训练完成, 输出目录: $PROJECT/$NAME"脚本里set -e很多人会忽略,但命令行工具一旦报错还会继续往下跑,会把错误的中间产物当成成功结果。数据目录检查是防止你在data=...传错路径时花 20 分钟等一个必然失败的训练。$(date +%Y%m%d_%H%M%S)给每次训练一个独立名称,这样多次实验不会互相覆盖,回看结果时也能分清是哪一次跑的。
4.2 关键参数怎么调:imgsz、batch、epochs、cache 的取舍
| 参数 | 我的默认值 | 什么时候改 |
|---|---|---|
| imgsz | 224 | 缺陷面积小(如细裂纹)可试 320,再大收益有限且显存暴涨 |
| batch | 32 | 显存不足报 OOM 时降到 16 或 8 |
| epochs | 50 | 验证集精度还有上升趋势时加到 80,但超过 100 要小心里 |
| patience | 15 | 验证集连续 15 轮不提升就早停,省时间也防过拟合 |
| cache | False | 数据集存在内存盘/SSD 时可开 |
| lr0 | 0.001 | 用预训练模型时保持,从零训练时可放大到 0.01 |
数据层面再说一次:1000 张图对图像分类来说是小样本,所以pretrained=True是我必开的选项。yolo11cls.pt 是 YOLO11 在 ImageNet 上预训练好的分类权重,backbone 已经懂得纹理、边缘、颜色这些基础特征,我们要做的只是把最后一层分类头换成 5 类再微调。这是小样本图像分类里性价比最高的一条路,比从随机初始化硬训省下大量时间和数据。lr0=0.001是微调的保守值,如果发现模型学得很慢,可以先看准确率曲线再决定是否调大,不要一上来就 0.01。
4.3 训练日志里真正要盯的三个信号
训练过程中,fit 信息每轮都会刷一行。我会重点盯三个信号:train loss 是否在稳定下降、val accuracy 是否随之上升、两者之间的差距是否越来越大。
第一个信号说明模型在正常拟合。第二个信号说明学习率没设废。第三个信号是过拟合的早期预警——如果 train accuracy 到了 98% 但 val 只有 82%,说明模型开始背训练集了。YOLO 分类训练默认会在 output 目录里存下 results.png,里面有 loss 曲线和 accuracy 曲线,不用自己记数字。早停只说 true 时,实际跑多少轮取决于 patience 是否触发,这比固定 epoch 更稳妥。
5. YOLO11cls 训练曲奇缺陷分类的四个必踩坑:标签错位、缓存报错、过拟合与假收敛
5.1 坑一:验证集准确率异常高,换新图直接现原形
现象:训练时 val accuracy 冲到 96%,看起来可以直接上产线,结果拿手机现场拍 20 张曲奇,正常和裂纹分得稀里糊涂。
原因:数据集划分时偷懒用了全随机打散。同一炉、同一光线条件下的图片随机分到了 train 和 val,模型实际是在“认炉子”而不是“认缺陷”。这是图像分类数据集整理中最常见的系统性错误,不是模型问题。
解决:回到第 2.3 节的做法,按炉次/批次分组划分数据集。划分前先看图片文件名,如果命名里有批次号就必须整组切。没有批次号的,看图片 EXIF 时间戳,按拍照时间段分组。
5.2 坑二:训练中突然报内存错误,进程退出一半
现象:训练跑到第 20 轮,终端报错RuntimeError: CUDA out of memory,或者卡死不动,进度条停在某个百分比。
原因:多数是cache=True把所有图片预加载进内存。1000 张 224x224 的图本身不大,但 YOLO 内部做完整的数据增强流水线,缓存占的是 RAM 和显存双重空间。如果同时开 batch=64 和 cache=True,小显存机器很容易在增强阶段爆掉。
解决:把cache=False,让每轮实时读图。如果读图成为瓶颈,先把 imgsz 降到 160 试跑,确认稳定后再升到 224。数据集放在机械硬盘上时,可以把图片转成 LMDB 格式或直接放 SSD,比纠结 cache 参数更有效。
5.3 坑三:模型把所有图都判成 normal,准确率却显示 60%
现象:训练完成,查看混淆矩阵发现 crack、burnt 等缺陷类几乎全是 0 预测,但总准确率 60% 看起来“还行”。
原因:类别不平衡加上损失函数默认加权方式不对。之前说过 normal 占大头,模型学到的决策边界是“只要不确定就猜 normal”,因为这样损失最小。准确率这个指标在这种场景下严重失真,不能作为唯一验收标准。
解决:第一,训练时加weighted=True,对少数类做损失加权;第二,看验证结果时用yolo classify val输出的每个类别 precision/recall,而不是只看总 accuracy;第三,给少数类做离线增强,把焦斑和异物各扩到 100 张以上。这三步做完,缺陷类的召回率通常能从接近 0 拉到 70% 以上。
5.4 坑四:loss 降得很稳,准确率却像心电图一样忽高忽低
现象:train loss 每轮都在降,说明模型在学;但 val accuracy 一会儿 90% 一会儿 70%,波动非常大。
原因:常见是验证集太小。1000 张图分出 20% 验证集是 200 张,平均每类 40 张,随机采样的方差本来就大。另一个可能原因是验证集里某个类只有个位数样本,那几张图质量差一点,准确率就剧烈抖动。
解决:扩大验证集到 30%,确保每个类至少有 30 张验证图。如果原始数据不够,先做离线增强补足少数类再划分,而不是先划分再增强。波动还在的话,开patience并在训练结束后取 best.pt——Ultralytics 默认保存最优权重,不要拿最后一轮的结果当最终模型。
6. 训练完不等于能用:用 CAM 热图、1-shot 对比和分类头微调验证模型
模型训完,先别急着拷到产线。我会做三件额外的验证,每一件都只花十几分钟,但能筛掉大部分“看着指标挺好、实际不能用的模型”。
第一件事是看 CAM 热图。YOLO11cls 是卷积网络,可以用 Grad-CAM 检查模型的注意力区域。正常饼干的热图应该集中在饼干主体;裂纹类热图应该聚焦在裂纹线条附近;如果热图永远在背景或角落,说明模型学到的是环境特征而不是饼干缺陷本质。写个几十行的 PyTorch 脚本,注册最后一个卷积层的 forward hook 就能出热图,不用改模型结构。
第二件事是做 1-shot/5-shot 小样本评估。从没参与训练的图片里,每类抽出 1 张和 5 张,分别测试模型的 top-1 准确率变化。1-shot 准说明类别边界清晰,模型主干特征提取得好;如果 1-shot 全乱、5-shot 正常,说明类别边界依赖训练数据里的特殊纹理,泛化仍不足。这个测试不需要重新训练,只是把固定分类头换成临时线性层做评估。
第三件事是回答那个常见问题:用 ViT 做评估时分类头需要调整吗?需要。不管是 ViT 还是 YOLO11cls,预训练模型的分类头都对应 ImageNet 的 1000 类,输出维度和你 5 类不匹配。常见做法是冻结 backbone、只换一个 Lineare 分类头并进行 linear probing 评估;如果样本充足,再解冻部分层微调。换头但不微调,直接拿预训练头输出做评估,是我见过最多的误用。我自己的习惯是:每次换数据集都先跑一次 CAM 热图,再谈准确率。没有热图佐证的准确率,我心里始终打问号。这几个检查做完,模型能不能上产线才算有底气。希望帮到你。
本文还有配套的精品资源,点击获取