简介:这是一份面向目标检测初学者、课程设计及苹果品质分拣场景的YOLO缺陷检测数据集资源。图片来自真实果园与产线环境,光照、角度、成熟度差异明显,场景覆盖度好;所有图像均使用LabelImg人工标注,边界框贴合缺陷区域,质量较高。标签同时提供VOC(xml)、COCO(json)、YOLO(txt)三种格式,分别存放于独立文件夹,无需转换即可接入YOLOv5、YOLOv8等主流框架。压缩包内含2000个文件,主要由1000个xml标注文件、990个txt标签文件、3个Python划分脚本、1个yaml配置及6个图文教程组成,目录按格式分门别类,整体仅14.2MB,便于快速下载与解压。随包附赠YOLO环境搭建教程(含Linux/Windows版本)和训练案例教程,并配有三个数据集划分脚本,可自由生成训练集、验证集、测试集。目前已有869人学习下载,适合作为毕业设计、实训项目或企业预研的快速验证数据集。
1. 苹果缺陷检测为什么不能拿通用数据集硬训
在果园分选、包装线和质检设备上,苹果的碰伤、腐烂、果锈和划痕是最常见的几类缺陷,而这类检测任务和行人、车辆检测有一个本质区别:苹果表面缺陷的类间差异小、类内差异大,同一个「碰伤」在不同品种、不同光照下拍出来可能完全不像同一个东西。如果直接拿 COCO 上预训练好的权重去跑,几乎没有实用价值——背景太杂、目标太小、缺陷形态太细碎,模型很难收敛。
这份「YOLO苹果缺陷目标检测数据集(含1000张图片)+对应voc、coco和yolo三种格式标签+划分脚本+训练教程.rar」解决的就是这个断档:1000张已经标注好的苹果缺陷图,三种主流标注格式都给你备齐,划分脚本和训练步骤也一并给你。它的价值在于省掉了从采集到清洗、标注、格式转换这一周起步的脏活,适合刚接触目标检测的入门者,也适合产线上需要快速验证「苹果缺陷能不能用YOLO检出」的工程师。接下来我从数据结构、划分逻辑、训练参数和踩坑记录四个方面,把这套方案完整拆开讲。
2. 先看清手里有什么:1000张苹果图的标注格式与目录结构
2.1 三种标注格式的长相差异:VOC、COCO、YOLO分别存了什么
拿到压缩包后,别急着跑训练,先花十分钟把目录结构理清楚。常见做法是把images、labels、annotations分开放,里面再按train、val分子目录。如果你打开后看到的是VOC2007风格目录(JPEGImages+Annotations),那说明作者保留了最早的 Pascal VOC 组织方式。
三种格式的标签,差的不是一星半点:
| 格式 | 存储方式 | 坐标表示 | 适合的框架 |
|---|---|---|---|
| VOC | XML 文件,每个图片对应一个.xml | xmin, ymin, xmax, ymax绝对像素坐标 | 老牌检测框架、SSD、Faster R-CNN |
| COCO | 单个 JSON 文件,所有图片标注汇总 | 绝对像素坐标,带segmentation多边形 | Detectron2、MMDetection |
| YOLO | 每个图片对应一个.txt,一行一个目标 | 归一化的cx, cy, w, h,取值 0~1 | YOLOv5/v8、Ultralytics 全家桶 |
VOC 和 COCO 是同一种坐标哲学的两种载体,读起来直观;YOLO 格式则牺牲了可读性换效率,它不存类别名,只存类别 ID 加四个归一化小数,所以训练时需要一个单独的classes.txt或 YAML 文件把 ID 映射回名称。
拿一张图实际打开看最有体感。比如apple_001.jpg对应的三个文件,第一行内容长这样。
VOC 的 XML 里,关键区块是:
<object> <name>bruise</name> <bndbox> <xmin>142</xmin> <ymin>210</ymin> <xmax>238</xmax> <ymax>296</ymax> </bndbox> </object>COCO 的 JSON 里,这张图会被拆成images、annotations、categories三段,标注框通过image_id关联:
{ "images": [{"id": 1, "file_name": "apple_001.jpg", "width": 640, "height": 480}], "annotations": [{"id": 1, "image_id": 1, "category_id": 1, "bbox": [142, 210, 96, 86]}], "categories": [{"id": 1, "name": "bruise"}] }YOLO 的 TXT 里,只有一行:
0 0.296875 0.526042 0.15 0.179167注意 COCO 的bbox存的是[x, y, width, height],左上角坐标加宽高;VOC 存的是两个对角点。YOLO 的cx, cy是中心点归一化坐标,分母是图片宽和高。这三个格式之间的转换,是后续一切工作的地基。
2.2 打开格式文件核对数据的3个命令
不要相信压缩包自带的所有文件都是对的——标注软件导出、脚本批量转换的过程中经常产生比例错误和越界坐标。我拿到数据集的第一件事永远是抽样核验,而不是直接开训。
用 Python 快速检查一张图的标注是否落在画面内:
# check_annotation.py import cv2 import numpy as np img = cv2.imread("images/train/apple_001.jpg") h, w = img.shape[:2] # 读 YOLO 格式标签 with open("labels/train/apple_001.txt", "r") as f: lines = f.read().strip().splitlines() for line in lines: cls, cx, cy, bw, bh = map(float, line.split()) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) print(f"class={cls} box=({x1},{y1})-({x2},{y2}) 越界={x1<0 or y1<0 or x2>w or y2>h}")这段脚本把归一化坐标还原成像素坐标,然后检查越界。cx加bw / 2超过图片宽度就是标注越界,常见于标注框贴着边缘时四舍五入出了 1.0。如果越界样本超过 5%,建议放弃这份标注重新标,或者用脚本把所有越界框裁剪回边界——损失一点精度,但比全部返工快。
再抽查一下类别分布,避免某个类别只有十几张图,模型根本学不到特征:
# 统计所有标签文件的类别频次 cat labels/train/*.txt | awk '{print $1}' | sort | uniq -c | sort -rnawk '{print $1}'取每行第一个字段(类别 ID),sort | uniq -c完成计数。看到频次差距超过一个数量级,就要考虑类别重采样,否则小样本类别会被大样本类别吃掉。1000 张图的缺陷数据集,正常情况单类别应该有两三百个实例,如果某个类只有 20 个实例,后续训练时它的 mAP 会惨不忍睹。
3. 划分脚本怎么用:训练/验证/测试集划分与格式联动
3.1 划分脚本的核心逻辑:按比例拆目录还是拆标注
压缩包里的划分脚本,核心作用是把数据拆成train / val / test三个互不重叠的集合。这里有一个关键决策:是按 7:2:1 还是 8:2 划分?对 1000 张的小数据集,我建议保留测试集,但比例不要太大——训练集 700、验证集 150、测试集 150 是稳妥的起点。验证集负责调参和早停判断,测试集只在全部训练结束后用来做最终评估,这个分隔不能混淆。
另一个容易犯的错是只分图片不分标签。划分脚本必须同时移动图片和对应标注文件,否则训练到一半报FileNotFoundError。好的划分脚本还会把三个集合写成三个 TXT 清单文件,Ultralytics YOLO 直接读清单就能定位图片,不需要目录级硬编码。
划分前一定要确认一件事:这个压缩包里 VOC、COCO、YOLO 三种格式是否各自维护一套train/val/test结构。如果是,直接用其中一套就行;如果只有一套全量标注然后靠脚本划分,那脚本会先转换格式再划分,跑一次脚本等于同时完成两件事。跑之前看一眼脚本开头的参数区即可。
3.2 动手跑一遍划分:命令行参数与产物检查
一个典型的划分脚本会接受输入目录、输出目录、比例三个参数,在命令行里这么调:
python split_dataset.py \ --image_dir datasets/images \ --label_dir datasets/labels \ --output_dir datasets/split \ --train_ratio 0.7 \ --val_ratio 0.15 \ --test_ratio 0.15 \ --seed 42--seed 42很关键。目标检测的划分虽然是随机抽样,但如果不固定随机种子,每次跑出来的集合都不一样,复现实验结果时你会疯掉——模型精度差 0.5 个点,你根本说不清是数据划分变了吗还是模型变了。固定随机种子是能省掉大量返工的一个好习惯。
脚本内部的大致步骤是:读所有图片文件名列表,用random.shuffle打乱,按比例切三段,然后把每张图的标签文件跟随图片移动到对应子目录。脚本跑完后,检查一下产物:
# 检查划分后的数据完整性 for split in train val test; do img_count=$(ls datasets/split/images/$split/*.jpg 2>/dev/null | wc -l) lbl_count=$(ls datasets/split/labels/$split/*.txt 2>/dev/null | wc -l) echo "$split: $img_count images, $lbl_count labels" done图片数和标签数必须完全一致,差一个都说明有图片漏标或标签没配对。另一个检查维度是看三个集合的类别分布是否接近整体分布——如果train里碰伤占 40%,test里碰伤突然只占 10%,那模型的评估结果就没有参考价值了。遇到分布偏移,把划分种子换几个值重新跑,选分布最接近的一次。
1000 张图的数据集,划分结果大约就是 700 / 150 / 150 这个量级。如果发现test集里某类样本数小于 10,建议把它并入val,评估时单独说明。小数据集上追求比例完美不如保证每个类别在三个集合里都出现。
4. 用 YOLOv8 训练苹果缺陷检测模型:最小可用流程
4.1 数据配置文件与目录结构的匹配
训练脚本通常由三个文件构成:data.yaml、yolov8n.pt预训练权重、训练入口脚本。这三样对应了「数据是什么、从哪开始学、怎么学」三个问题。
把压缩包里的标签转成 Ultralytics 结构后,最核心的配置文件data.yaml长这样:
# data.yaml path: /home/user/apple_defect # 数据集根目录 train: images/train # 训练图片目录(相对 path) val: images/val # 验证图片目录 test: images/test # 测试图片目录 nc: 4 # 类别数量 names: ['bruise', 'rot', 'russet', 'scratch'] # 类别名按ID顺序nc必须和names的长度一致,YOLO 格式标签里的类别 ID 直接索引names列表。这里最容易翻车的是类别顺序——训练时用的names顺序必须和标注时的类别 ID 顺序完全一致,否则模型学到的映射就是错的,推理时class 0变成了另一个类。如果你不确定原始标注的 ID 顺序,先读一份标签文件,把类别 ID 和图片内容对照一遍。
path建议写绝对路径。相对路径在本地实验时没问题,一旦换机器跑,相对路径的根目录变了就会报image not found。我一般会用pwd先确认路径再填进 YAML。
4.2 预训练权重怎么选、训练命令怎么敲
YOLOv8 的预训练权重需要单独下载,压缩包里通常不内置。V100 这类显卡上可以直接用官方预训练权重,下来后先确认模型的输入尺寸和类别数是否匹配你的数据。下载预训练权重这个环节,建议直接走官方渠道,文件名带yolov8n.pt的是 nano 版本,yolov8s.pt是 small 版本,yolov8m.pt是 medium。
对于 1000 张图、4 个类别的缺陷检测,nano 和 small 是甜点区——模型小、训练快、不容易过拟合。medium 以上在这个数据量级几乎必然过拟合。训练命令:
yolo detect train \ model=yolov8n.pt \ data=apple_defect.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=15 \ project=apple_defect_runs \ name=exp_bruise_v1判读这些参数:epochs=100是训练轮数上限,配合patience=15实现早停——连续 15 轮验证集指标不提升就自动停,能省掉大量无效训练时间。imgsz=640是输入分辨率,苹果缺陷这种小目标,如果原始图片里缺陷只占几十个像素,建议尝试imgsz=960甚至1280,代价是训练时间翻倍。batch=16受显存约束,V100 16GB 显存跑 nano 用 16 没问题,8GB 显存就降到 8。lr0=0.01是初始学习率,预训练微调场景下 0.01 是安全值,如果训练曲线震荡,降到 0.005。
数据量小的时候,加数据增强有奇效:
# 在训练配置中追加 augment: true hsv_h: 0.015 # 色调扰动 hsv_s: 0.7 # 饱和度扰动 hsv_v: 0.4 # 明度扰动 degrees: 15 # 旋转 fliplr: 0.5 # 水平翻转苹果缺陷检测尤其吃光照泛化——产线上的打光是固定的,但不同批次苹果的颜色深浅差异大,饱和度扰动往 0.7 调能明显提升鲁棒性。
4.3 训练后看哪几个指标
训练结束后,runs/detect/exp_bruise_v1目录下会生成results.csv、confusion_matrix.png、PR_curve.png等文件。我每次训练完会先看三个数:mAP@0.5、mAP@0.5:0.95、precision。对苹果缺陷检测,mAP@0.5能上 0.85 就算可用,mAP@0.5:0.95在 0.6 左右是正常的——缺陷框小、边缘模糊,高 IoU 阈值下得分下滑是必然。
results.csv里看val/box_loss和val/cls_loss两条曲线。两条曲线在训练后期不应该有剧烈上翘,如果出现「训练 loss 下降但验证 loss 抬头」的剪刀差,就是过拟合信号,回退到验证 loss 最低的那个 epoch 的权重文件。
5. 苹果缺陷数据集训练避坑:从标注翻车到训练崩坏
5.1 类别名带中文或特殊字符导致解析失败
现象:训练启动时报unable to parse label,定位到某个.txt文件时直接终止训练。
原因:部分标注工具导出的类别名带中文或下划线前缀,比如碰伤_1,YOLO 解析器按空格分词后拿到非数字字段就崩了。VOC 转 YOLO 时经常保留原始 XML 里的中文类别名,这个坑在国产标注工具里尤其常见。
解决:统一把类别名改成纯英文小写加下划线。写个脚本读取所有标签文件,把非数字字符全部替换成对应的英文映射。更彻底的做法是全部重新编号,类别名只存在于data.yaml的names列表里,标签文件里只允许出现0到nc-1的整数。改完后再跑一遍 2.2 节的类别频次统计,确认没有奇怪的类别 ID 混进来。
5.2 归一化坐标超出 0~1 导致训练 loss 异常
现象:训练正常启动,但 loss 在初期就剧烈抖动,部分批次直接出现nan。
原因:YOLO 格式的坐标必须是 [0, 1] 区间的小数,但 VOC 转 YOLO 时如果图片尺寸读取错误(比如把缩略图的宽高当作原图宽高),转换出来的cx或bw会大于 1。训练时模型预测的边界是 0~1 的 sigmoid 输出,标签却给了 1.3,损失函数直接爆炸。
解决:在训练前跑一次全量标签扫描:
# check_yolo_format.py import os for split in ["train", "val", "test"]: label_dir = f"datasets/split/labels/{split}" for f in os.listdir(label_dir): with open(os.path.join(label_dir, f)) as fp: for line in fp: parts = list(map(float, line.split())) if len(parts) != 5: print(f"{f}: 字段数异常 {len(parts)}") elif not all(0 <= v <= 1 for v in parts[1:]): print(f"{f}: 坐标越界 {parts}")如果查出大量越界坐标,回头检查 VOC 转 YOLO 脚本里读取图片尺寸的部分——大概率是用cv2.imread读错了文件路径,拿到了默认尺寸的占位图。这个脚本必须在划分之后、训练之前跑,是训练前最后一道保险。
5.3 训练中 BN 崩溃:loss 突然变成 NaN
现象:训练跑到第 30~50 轮时,loss 突然输出nan,日志里出现RuntimeError: CUDA error: device-side assert triggered。
原因:这是目标检测训练里一个有名的崩坏模式。要么是标签里出现了nc之外的类别 ID,要么是某个类别的样本在 batch 里全部被增强策略裁掉了,导致该类别梯度异常。前 20 轮没事、后面才炸,通常是某个 batch 抽样恰好把某个罕见类别全部聚到一起,BN 统计量瞬间失控。
解决:先降学习率重试,看是否延后出现。如果降学习率无效,检查训练集里是否存在类别 ID 大于nc-1的标签行。还有一种情况是图片损坏(解码失败),Ultralytics 会返回一张全黑图,但标注框还在,相当于拿错误数据去拟合。排查办法是把训练集逐个用cv2.imread读一遍,出错的文件直接移出训练集。
5.4 混淆矩阵总和不为 1 的困惑
现象:训练完看confusion_matrix.png,发现矩阵里所有格子的数值加起来不等于 1,开始怀疑计算有 bug。
原因:YOLO 的混淆矩阵是「按实际类别归一化」的,每行的和是 1,不是整个矩阵的和是 1。每一行代表一个真实类别,行内数值表示该类别被预测成了哪些目标;列代表预测类别。某一行全是背景background,说明这个类别一个都没检出,这是比和不为 1 更需要关注的信号。
解决:看混淆矩阵时,按行读。看某个真实类有多少比例被判成了别的类,那才是欠拟合或类别混淆的线索。比如碰伤bruise有 30% 被预测成果锈russet,说明两个类别在视觉上高度重叠,要么增加标注精度,要么考虑合并类别。
5.5 小数据集上过早过拟合
现象:训练 loss 一直在降,验证集的 mAP 在 40 轮之后开始缓慢下降,但验证 loss 曲线没有明显抬头。
原因:1000 张图对 YOLO 来说属于小数据,模型容量有余但数据不足,模型开始记住训练集里的纹理细节和背景噪声。这是小数据集上最普遍的现象,不是 bug。
解决:三件事同时做。一、把patience从默认值调到 15,让它自动停在验证集指标最高点。二、增强程度拉满:degrees=30旋转、scale=0.5缩放随机、mosaic=1.0开马赛克增强,让模型看不到「原封不动的同一张图」。三、换更小的模型,yolov8n比yolov8s过拟合得更慢,对小数据集更友好。
6. 验证与进阶:从 mAP 数字到产线可用的最后一步
训练完的模型,先在测试集上做一次彻底评估,别直接拿去跑视频。yolo detect val跑完后,用混淆矩阵判断哪些类别互相打架,然后翻几十张测试集的可视化预测图,看漏检和误检的具体形态——这一步的体感比任何指标都直接。
苹果缺陷检测和通用目标检测有一个显著差异:多数场景下,你最终关心的不是「有没有缺陷」,而是「这个苹果能不能上产线」。把检测结果换算成缺陷面积占比,是个简单有效的改良方案。苹果的缺陷面积占比通常按像素估算,检测框面积不等于缺陷面积,但在缺陷形态接近椭圆时,用框面积近似是工程上可接受的。
写一个后处理脚本,把检测框大小换算为缺陷占比:
# estimate_defect_ratio.py import cv2 from ultralytics import YOLO model = YOLO("apple_defect_runs/exp_bruise_v1/weights/best.pt") img = cv2.imread("test/rotten_apple_017.jpg") h, w = img.shape[:2] results = model.predict(img, conf=0.25, imgsz=640)[0] apple_area = 0 defect_area = 0 for box in results.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) cls_id = int(box.cls[0]) if cls_id == 0: # 假设类别0是整果 apple_area = (x2 - x1) * (y2 - y1) else: # 其他类别都是缺陷 defect_area += (x2 - x1) * (y2 - y1) ratio = defect_area / apple_area if apple_area > 0 else 0 print(f"缺陷面积占比: {ratio:.1%}")conf=0.25是置信度阈值,产线场景通常比学术场景更保守,建议调到 0.35 以上减少误触发;imgsz=640要和训练时保持一致,否则精度会下降。模型在测试集上的 mAP 是 0.85,但到了实际产线可能会掉到 0.7 以下——光照、相机角度、苹果品种差异都会拉低精度,所以上线前必须用小批量真实数据做一轮验证。
我现在的习惯是:任何检测项目,训练只是第一步,上线前先采集 20~30 张产线实拍图,用模型跑一遍,肉眼看完结果再决定是否微调。苹果缺陷检测里,果锈和碰伤在低光照下极容易混淆,肉眼扫一遍往往能发现指标上没有暴露的问题。希望这份数据集加训练流程的拆解,能帮你把第一版缺陷检测模型更快跑到产线上去验证。
本文还有配套的精品资源,点击获取