ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

马铃薯缺陷检测数据集实战:从YOLO格式转换到模型上线的避坑指南

马铃薯缺陷检测数据集实战:从YOLO格式转换到模型上线的避坑指南 简介本资源面向从事农产品质检、智能农业与目标检测算法实践的开发者提供马铃薯缺陷检测的YOLO格式数据集可直接用于YOLO全系列网络训练与验证。数据集融合了常见马铃薯缺陷场景共划分5个类别涵盖发芽、真菌病害、机械损伤等类型并配有对应的class类别文本文件方便直接接入训练流程。压缩包内共2000个文件以1999个txt标签文件和1个Python可视化脚本为主整体约409.72MB其中txt文件为图像对应的标注信息show.py可将检测框绘制到图像上便于快速核查标注质量与数据分布。目前已有436人学习下载适合需要快速搭建缺陷检测基线、验证模型效果或扩充农业视觉数据的研究者与工程师参考使用。1. 马铃薯缺陷检测数据集从「能跑通」到「敢上线」之间差了什么拿到「马铃薯potato图像、缺陷检测数据集」这个标题的人十有八九不是想写论文而是手里已经有一台相机、一条分选线或者一个必须交差的检测模型。马铃薯的缺陷检测和通用目标检测最大的区别在于缺陷不是独立物体而是长在薯块表面的区域——发芽、绿皮、黑斑、机械擦伤、腐烂这些类别边界模糊、面积占比小、光照一变就换一副面孔。数据集如果只给一堆「好薯/坏薯」的整图分类标签模型上线后基本没法定位问题出在哪真正能用的是带边界框或掩码的缺陷标注让模型告诉你「这个薯块的左上方有一块绿皮」。这个方向适合三类人做农产品分选设备的工程师、想用 YOLO 系列练手真实工业数据的算法同学、以及需要快速验证「缺陷检测能不能替代人工目检」的产线负责人。它解决的核心问题是把「人眼判断」变成「可复现的推理输出」难点不在模型结构而在数据集的类别定义、标注一致性和训练时的长尾分布。下面按「数据集怎么读 → 怎么转成 YOLO 格式 → 怎么训 → 坑在哪 → 怎么验证」推一遍中间给可抄的命令和参数。2. 马铃薯缺陷数据集的结构与类别定义先搞清楚你手里是什么2.1 图像采集条件决定了数据集的上限马铃薯图像数据集的质量八成取决于采集时的光照和背景。常见做法是传送带上方加漫射光源、背景用深色哑光板让薯块轮廓和表面缺陷都能被稳定分割出来。如果数据集里混了自然光下手机拍的图模型会学到「背景亮度」而不是「缺陷纹理」换到产线就翻车。我一般先做一件事把数据集按采集批次分组统计每组的平均亮度、对比度和分辨率差异过大的批次要么单独训要么直接剔除。缺陷类别通常分这几类不同数据集叫法不同但本质接近类别典型视觉特征标注难点发芽 sprout芽眼处凸起、颜色偏白或绿小目标易和反光混淆绿皮 greening表皮绿色斑块边界渐变框大小主观黑斑 blackspot局部黑色凹陷与阴影难区分机械伤 mechanical擦痕、破皮形状不规则长条形腐烂 rot软塌、深色湿斑常与黑斑重叠提示如果数据集只给了整图标签先别急着训检测模型用分类模型做一轮基线确认缺陷在图像上是否可分再决定要不要补标注。2.2 标注格式与目录组织目标检测数据集常见三种标注Pascal VOC 的 XML、COCO 的 JSON、YOLO 的 txt。马铃薯缺陷数据集如果是公开来源多半是 VOC 或 COCO如果是自采标注工具LabelImg、CVAT、Labelme导出的格式又不一样。先统一到一种中间格式再转 YOLO别在训练脚本里做多格式兼容那是给自己挖坑。一个干净的目录长这样potato_defect/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml是 YOLO 系列训练的入口配置内容如下path: /data/potato_defect train: images/train val: images/val test: images/test nc: 5 names: [sprout, greening, blackspot, mechanical, rot]path写数据集根目录train/val/test写相对路径nc是类别数names顺序必须和标注文件里的 class id 严格对应。顺序错一位模型训出来的类别就全乱这种错误在验证集指标上不一定立刻暴露但上线必炸。2.3 用脚本统计类别分布先看长尾有多严重拿到数据集别直接开训先跑一遍统计。下面这段脚本读 YOLO 格式的 labels输出每个类别的框数量和图像占比import os from collections import Counter label_dir potato_defect/labels/train counter Counter() img_with_cls Counter() total_imgs 0 for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue total_imgs 1 seen set() with open(os.path.join(label_dir, fname)) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id int(parts[0]) counter[cls_id] 1 seen.add(cls_id) for c in seen: img_with_cls[c] 1 print(总图像数:, total_imgs) for cid in sorted(counter): print(fclass {cid}: 框数{counter[cid]}, 出现图像数{img_with_cls[cid]}, f图像占比{img_with_cls[cid]/total_imgs:.2%})逻辑说明逐文件读取cls_id是每行第一个字段框数统计的是标注实例总数img_with_cls统计的是「至少出现一次该类别的图像数」。这两个指标要分开看——某个类别框数多但图像占比低说明它集中在少数图上模型容易过拟合到那几张图的背景。参数上label_dir换成你的实际路径即可不需要额外依赖。如果发现rot只占 3% 的图像而blackspot占 60%这就是典型长尾。处理方式后面第 4 章会讲先记住这个分布。3. 把 VOC/COCO 标注转成 YOLO 格式转换脚本与四个边界坑3.1 VOC XML 转 YOLO txt 的完整脚本VOC 的坐标是左上角xmin, ymin和右下角xmax, ymaxYOLO 要的是归一化的中心点cx, cy和宽高w, h。转换公式cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h脚本如下import os import xml.etree.ElementTree as ET from PIL import Image classes [sprout, greening, blackspot, mechanical, rot] cls_map {name: i for i, name in enumerate(classes)} def convert(xml_path, img_path, out_path): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in cls_map: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 边界裁剪防止标注越界 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) if xmax xmin or ymax ymin: continue cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_map[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) for fname in os.listdir(Annotations): if not fname.endswith(.xml): continue stem fname[:-4] convert(fAnnotations/{stem}.xml, fJPEGImages/{stem}.jpg, flabels/{stem}.txt)逻辑说明cls_map把类别名映射到 0 起始的 id和data.yaml的names顺序一致。边界裁剪那几行是关键很多公开数据集的标注框会超出图像边界不裁剪会导致归一化坐标大于 1训练时 loss 异常。if xmax xmin过滤掉零面积框。参数上classes列表必须和你的数据集类别完全一致多一个少一个都会让映射错位。3.2 COCO JSON 转 YOLO 的差异点COCO 的bbox是[x, y, w, h]且category_id不一定从 0 连续。转换时要先建立category_id → 连续 id的映射不能直接用原始 id。另外 COCO 的images和annotations是分开的数组需要先按image_id聚合。常见做法是写一个coco2yolo.py核心逻辑和上面类似只是取值方式不同。这里不展开完整代码记住两个坑一是category_id不连续二是 COCO 允许iscrowd1的标注这类框通常要跳过。3.3 转换后必须做的三项校验转换完别急着训跑三个检查# 1. 检查是否有空 label 文件 find labels/train -name *.txt -empty | head # 2. 检查坐标是否越界大于1或小于0 awk {if($21||$31||$41||$51||$20||$30||$40||$50) print FILENAME} labels/train/*.txt | head # 3. 检查图像和标签是否一一对应 for f in images/train/*.jpg; do stem$(basename $f .jpg) [ -f labels/train/$stem.txt ] || echo 缺失标签: $stem done空 label 文件意味着这张图没有目标YOLO 训练时会被当作纯背景图如果数量太多会拉低召回。坐标越界说明裁剪逻辑没生效。图像标签不对应会导致训练时直接报错或静默跳过。这三项检查花不了两分钟但能省掉几小时的排查。4. 用 YOLOv8 训练马铃薯缺陷检测参数怎么设、长尾怎么救4.1 训练命令与关键参数YOLOv8 的训练入口很简洁但参数设错会让结果差很多。一条我常用的命令yolo detect train \ datapotato_defect/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience30 \ mosaic1.0 \ mixup0.1 \ copy_paste0.1 \ degrees15 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4 \ fliplr0.5 \ flipud0.0 \ projectruns/potato \ nameexp1参数说明modelyolov8s.pt是速度和精度的折中马铃薯缺陷目标偏小yolov8n容易漏检yolov8m在数据量不足时过拟合。imgsz640是默认值如果缺陷区域在原图里小于 20 像素考虑提到 960 或 1280但显存和速度要权衡。mosaic1.0是 YOLOv8 默认开启的增强对马铃薯这种背景单一的数据集有帮助但如果缺陷和背景强相关比如绿皮总在薯块顶部mosaic 会破坏空间关系可以降到 0.5。flipud0.0是因为马铃薯在传送带上没有固定的上下方向但如果你希望模型学到「芽眼朝上」这种先验就别开上下翻转。4.2 长尾类别的三种处理方式回到第 2 章统计出的长尾问题。假设rot只占 3%直接训会让模型几乎不预测这个类别。三种处理方式按投入从低到高第一种是过采样。在训练集里把含rot的图像复制多份简单粗暴但有效。注意复制后要重新划分 train/val别让同一张图同时出现在两边。第二种是损失加权。YOLOv8 不直接暴露类别权重参数但可以通过自定义 dataset 或修改 loss 实现。更实际的做法是用cls_pw参数部分版本支持或者改用 focal loss 的变体。如果不想改源码过采样是性价比最高的。第三种是合成数据。用 copy-paste 把rot区域贴到健康薯块上扩充样本。YOLOv8 的copy_paste0.1就是做这件事但它是在训练时在线合成对极端长尾帮助有限。离线合成可以用 albumentations 或简单的 PIL 贴图注意贴图时的边缘融合和光照一致性否则模型学到的是「贴图痕迹」而不是「腐烂纹理」。4.3 训练过程要看哪些曲线训练启动后重点看三个东西train/box_loss和val/box_loss的差距、metrics/mAP50的走势、以及每个类别的 AP。如果train持续下降但val早早反弹是过拟合加增强或减模型。如果mAP50卡在某个值不动先看是不是某个类别 AP 接近 0那就是长尾没处理好。YOLOv8 训练结束会在runs/potato/exp1下生成results.csv和混淆矩阵混淆矩阵能直接看出哪些类别被互相误判——马铃薯数据集里blackspot和rot经常混如果混淆矩阵显示这两类互相误判严重说明标注边界本身就不清晰得回去统一标注标准而不是调模型。5. 马铃薯缺陷检测的避坑与排查五条血泪经验5.1 现象验证集 mAP 很高上线后几乎全漏检原因验证集和训练集来自同一批采集光照、背景、薯块品种都一致模型学到了批次特征而不是缺陷特征。解决按采集批次划分 train/val而不是随机划分。如果只有一批数据至少按时间顺序切分用前期数据训、后期数据验。5.2 现象模型把传送带上的污渍当成黑斑原因标注时只标了薯块上的缺陷但背景里的深色污渍和黑斑视觉相似模型无法区分「在薯块上」和「在背景里」。解决要么在标注时把背景污渍也标成一个background类要么在预处理阶段用分割模型先把薯块抠出来再在 ROI 内做检测。后者更稳但多一个模型。5.3 现象小目标发芽检测召回率极低原因imgsz640下原图里 15 像素的芽眼缩到 640 后只剩几个像素特征几乎消失。解决提高输入分辨率到 960 或 1280或者用切片推理SAHI把大图切成小块分别检测再合并。切片推理的代价是速度下降产线节拍紧的话要算清楚。5.4 现象训练 loss 正常但推理结果框重叠严重原因NMS 的iou_thres默认 0.7马铃薯缺陷区域经常相邻甚至重叠比如黑斑旁边就是腐烂NMS 会把相邻的真实框误删。解决把iou_thres调到 0.5 到 0.6或者在验证时观察不同阈值下的 mAP选一个平衡点。这个参数在model.predict()或val()里传。5.5 现象换了新品种的马铃薯后模型性能骤降原因不同品种的表皮颜色、纹理差异大模型过拟合到了训练品种的外观。解决训练时加强颜色增强hsv_h、hsv_s、hsv_v调大或者用灰度图训练再在推理时做颜色归一化。更彻底的做法是收集多品种数据但成本高。实际项目中我一般会留一个「品种自适应」的微调流程新品类上线前用几十张图做一轮小学习率微调。6. 验证与进阶怎么判断这个数据集值不值得继续投入6.1 用混淆矩阵和 PR 曲线定位问题类别训练完别只看一个 mAP 数字。YOLOv8 会在runs/potato/exp1下生成confusion_matrix.png和PR_curve.png。混淆矩阵的对角线是正确分类非对角线是误判。如果greening被大量判成sprout说明这两类的视觉特征在标注时就没分清回去看标注规范。PR 曲线能看出每个类别的查准查全平衡点如果某个类别的曲线几乎贴着坐标轴说明样本太少或特征太弱考虑合并类别或补充数据。6.2 一个快速判断数据集是否可用的基线实验在投入大量标注和训练之前我会先做一个「最小可行实验」从数据集中随机抽 200 张图用预训练的 YOLOv8s 直接推理不训练看模型能不能零样本检出部分缺陷。如果完全检不出说明缺陷特征和 COCO 预训练类别的差异太大需要从头训如果能检出部分说明特征有一定通用性微调成本可控。这个实验半小时内能跑完比盲目训三天再发现数据不可用划算得多。6.3 从检测到分级的进阶路径检测出缺陷位置只是第一步产线真正需要的是「这个薯块该不该剔除」。常见做法是在检测框基础上加一个分类头判断缺陷的严重程度轻度/中度/重度或者直接回归一个「可售概率」。YOLOv8 的classify模式可以单独训一个分类模型把检测框裁剪出来送进去。更紧凑的方案是用 YOLOv8 的seg模式做实例分割用掩码面积占比作为严重程度指标。分割标注成本比检测框高但如果缺陷边界本身模糊分割反而能倒逼标注一致性。6.4 我踩过的最大的一个坑早期做马铃薯缺陷检测时我花了两个月调模型结构从 YOLOv5 换到 YOLOv8 再换到各种魔改版本mAP 涨了不到 2 个点。后来把标注数据重新过了一遍发现 15% 的框是错的——有的把阴影标成了黑斑有的绿皮框只框了一半。重新标注后同一个模型 mAP 直接涨了 11 个点。这件事之后我养成了一个习惯任何检测项目先花时间把标注质量量化出来比如随机抽 100 张图人工复核算一个「标注准确率」。如果低于 95%先修数据别碰模型。数据集的坑模型补不回来。希望帮到你。本文还有配套的精品资源点击获取
返回列表