马铃薯缺陷检测数据集构建指南:从标注到YOLOv8训练实战
2026/9/12 22:45:17 网站建设 项目流程

简介:这份数据集面向计算机视觉与深度学习目标检测开发者,聚焦马铃薯表面缺陷识别,可直接用于YOLO系列模型训练。素材整合了常见的马铃薯发芽、真菌病害、机械损伤等缺陷样本,并划分训练集与验证集,类别文件定义了Sprouted potato、Diseased-fungal potato、Damaged potato等五类缺陷。压缩包总计两千个文件,其中一千九百九十九个txt为YOLO格式标注文件,每个文件对应一张图像的检测框坐标与类别,另有1个show.py脚本,可快速将框体绘制在图像上完成可视化验证。数据处理参考Labelme标注规范,整体样本规模超过八千张图像,背景真实、干扰多样,适合评估模型鲁棒性。资源包大小约409.72MB,目前已有429人学习与使用,适合中高级算法工程师在农业质检、智能分拣等场景中直接微调或迁移使用。

1. 马铃薯缺陷检测数据集:不只是把土豆拍清楚那么简单

做工业质检或农业智能分选的人,迟早会撞上同一个问题:手里有相机、有产线、有检测需求,但就是没有一套能直接喂给 YOLO 的数据集。马铃薯这种外观差异极大的目标,恰恰是目标检测里最典型的“看起来简单、做起来头疼”的场景——表皮颜色从浅黄到深褐,形状从椭圆到歪扭,缺陷又分表面、内部和形状三类,每一类对标注的要求都不一样。本文围绕马铃薯图像缺陷检测数据集,讲清楚从采集、标注、格式转换到训练评估的完整链路,重点解决“缺陷怎么定义、标注怎么打、模型怎么收敛”这三个核心问题,适合正在自建数据集的算法工程师,也适合刚接手农业视觉项目、需要快速理解数据坑的后端与测试同学。

2. 马铃薯缺陷检测数据集的构成与缺陷类别体系

2.1 缺陷检测任务的本质是分类加定位,不是像素分割

目标检测里的“缺陷检测”,输出的是每个缺陷的边界框和类别。马铃薯缺陷检测数据集里的标注对象不是整颗土豆,而是土豆表面或内部的缺陷区域。这意味着采集图像时,一张图里可能同时出现多个缺陷,也可能一颗土豆同时有几种缺陷。数据集的维度因此比普通物体检测更多:既要保证单类目标的多样性,也要保证多缺陷共现的样本比例。

常见做法是把缺陷分为三类:表面类(黑斑、溃烂、绿皮、机械损伤)、形状类(畸形、二次生长、裂口)、内部类(空心、褐变)。内部缺陷常规光学相机拍不到,需要借助近红外或多光谱设备,或者干脆把土豆切开后拍切面。所以马铃薯缺陷检测数据集按拍摄方式又可拆分为“整薯表面数据集”和“切面数据集”,两者在模型部署时的用途完全不同——产线分级用前者,抽检质检用后者。

2.2 一个可落地的类别标签清单与判定规则

定义类别时,类别数量要跟后续的人工审核成本挂钩。我不建议一开始就把“病斑”细分成晚疫病、早疫病、疮痂病,因为农业病理学分类需要专业植保人员参与,标注成本高,而且很多病斑在产线图像上肉眼无法可靠区分。更稳的做法是先按“是否需要剔除”二分类,再细分到 4~6 个形态学可判定的类别。

下表是一个经过实际项目验证的类别体系,字段含义依次是类别 ID、名称、典型外观、判定要点:

ID类别名典型外观判定要点(标注员用)
0black_spot表皮黑色或深褐色斑点直径 > 2mm,边界清晰,与擦伤(无黑化)区分
1rot溃烂、软化、凹陷表面湿润,色泽异常,常有渗出
2green绿皮面积超过薯表 10% 即标注,注意阴影误判
3crack裂口、机械损伤表皮开裂,长度大于薯长 1/4
4malformed畸形整体形状异常,框住整个土豆,不框局部
5hollow空心(仅切面)切面内部空腔或褐变,标注在空腔区域

类别 ID 的排序和后续模型训练时的类别名保持一致,避免中途改名导致标签重映射。malformed 这种整颗级别的缺陷,建议用整颗土豆的外接框,而不是单独框某个凸起,否则会让模型学到“畸形 = 局部凸起”的错误关联。

2.3 采集方案的三个关键参数:分辨率、角度、光源

马铃薯缺陷检测数据集的采集质量,直接决定模型能否收敛。分辨率上,单颗土豆在图像里至少要有 200×200 像素,缺陷区域至少 20×20 像素,不然即使是 YOLOv8 这种对中小目标相对友好的模型,也很难在小缺陷上获得稳定置信度。按照普通工业相机 500 万像素、视野覆盖 5~8 颗土豆估算,实际部署时土豆单颗像素能做到 300×300 以上,这个量级对于黑斑和溃烂检测基本够用。

拍摄角度分两类:俯拍(摄像头垂直对准输送带)和侧面拍摄(透明挡板后水平拍摄)。俯拍最容易布置,但会把大裂口拍成细线;侧面拍摄需要解决反光和景深问题。产线上常见方案是俯拍 + 两个对角侧光源,既能打亮凹陷和裂口,又能避免顶部大块高光。拍切面时用俯拍加环形无影光源,避免刀痕造成的伪缺陷。

3. 马铃薯缺陷数据集的标注与格式转换

3.1 用 LabelImg 还是 X-AnyLabeling?标注工具的选择逻辑

标注工具的选择取决于两个因素:团队规模和你需不需做预标注。如果只是几百张图、两三个人,LabelImg 足够,它的优点是轻量、免训练、点开就能用;缺点是没有自动追踪和半自动辅助,遇到大量重复产线图会非常累。X-AnyLabeling 这类基于 SAM 的工具可以做预标注,先用一个初步模型自动标一版,人工再改,效率能提升 3~5 倍。

我一般会建议先手工标注 200 张图,让模型跑一个初版,再用这个初版对剩余图像做推理,把置信度高的结果直接转成标注,人工只修低置信度的框。这个流程下,YOLO 格式的标注文件每一行对应一个缺陷实例,格式为:

class_id x_center y_center width height

其中坐标值都是归一化到 [0,1] 的浮点数,x_center 和 y_center 是边界框中心点坐标,width 和 height 是框的宽和高。以下是一颗土豆同时有黑斑和绿皮时的标签文件内容:

0 0.5234 0.6121 0.0823 0.0711 2 0.7810 0.4377 0.1523 0.0988

格式转换时要注意,很多标注工具默认输出 Pascal VOC 格式(XML),需要转成 YOLO。转换逻辑是:读 XML 里的 xmin、ymin、xmax、ymax,再除以图像宽高得到归一化坐标。如果原标注里是 COCO 的 JSON 格式,则要额外处理分割掩码,这里只贴 XML 转 YOLO 的核心代码:

import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, class_map): tree = ET.parse(xml_path) root = tree.getroot() img_w = float(root.find('size/width').text) img_h = float(root.find('size/height').text) lines = [] for obj in root.findall('object'): cls = obj.find('name').text if cls not in class_map: continue box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{class_map[cls]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_path = os.path.join(out_dir, os.path.splitext(os.path.basename(xml_path))[0] + '.txt') with open(out_path, 'w') as f: f.write('\n'.join(lines))

这段代码把每个目标的类别名映射成 class_map 里的数字 ID,并完成坐标归一化。关键点是wh也做了归一化,如果漏掉这一步,训练时模型会依据错误的宽高比去匹配锚框,导致收敛极慢。还有一个容易踩的坑:XML 里可能包含 difficult=1 的对象,这类目标极不清晰,训练时应该跳过,否则会引入大量噪声标签。

3.2 数据划分的坑:按批次划分而不是按图像随机划分

马铃薯产线图像往往来自同一批次的连续拍摄,同一颗土豆会被拍到多张,相邻图像极其相似。如果按图像随机划分训练集和验证集,验证集里会出现大量跟训练图像高度重叠的样本,把模型的记忆能力误当成泛化能力。这种情况下,验证集 mAP 能到 0.95,但换到新批次土豆立刻掉到 0.7。

正确的做法是按拍摄批次或时间序列划分。采集时给每个批次一个目录名,比如batch_20240910_001,划分脚本直接按目录取前 80% 作为训练、后 20% 作为验证。如果土豆是在旋转台上按角度拍摄,那同一颗土豆的多个角度的图像必须全部落在同一个集合里。这也是马铃薯这类农产品数据集和通用目标检测数据集的最大区别——样本时间相关性强,不能粗暴 shuffle。

划分完再统计每个类别在训练集和验证集中的实例数量,确保每个类别都至少出现 50 个实例。如果某个类别实例太少,就要回采集阶段补数据,不要用复制粘贴来凑数,否则会让模型学到重复纹理特征。

4. 用 YOLOv8 训练马铃薯缺陷检测模型的最小流程

4.1 数据组织与配置文件

选定 YOLOv8 是因为它对小数据集和中等算力比较友好,训练脚本、验证指标和导出工具链都统一,不需要自己写 NMS 后处理。把数据集组织成以下目录结构:

potato_defect/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── potato_defect.yaml

YAML 配置文件里指定路径、类别数和类别名:

path: /data/potato_defect train: images/train val: images/val test: images/test nc: 6 names: ['black_spot', 'rot', 'green', 'crack', 'malformed', 'hollow']

注意 yaml 里的val路径不能指向空目录,YOLOv8 在训练过程中会使用 val 集计算 mAP 并保存最佳权重。如果 val 集为空,训练不会报错,但模型会从最后一个 epoch 保存权重,失去早停与选优能力。

4.2 训练命令、关键超参与显存估算

最小训练命令如下:

yolo detect train \ data=/data/potato_defect/potato_defect.yaml \ model=yolov8n.pt \ epochs=200 \ imgsz=640 \ batch=16 \ device=0 \ patience=30 \ project=runs/potato \ name=exp1

参数说明:model=yolov8n.pt表示加载 COCO 预训练权重,马铃薯检测属于特殊目标检测,预训练权重能显著加速收敛并提高小样本下的精度;imgsz=640是输入图像短边缩放值,如果原图是 1280×960,训练时会按比例缩放到长边不超过 640,缺陷细节会丢失很多,所以务必备注原图分辨率,当原图缺陷小于 30×30 像素时,imgsz至少提到 960;patience=30表示验证集 mAP 连续 30 个 epoch 不上升就提前终止,避免过拟合和无效计算。

显存方面,yolov8n 在 640 分辨率下 batch=16 大约占用 8GB 显存,batch=32 需要 14GB 左右。如果你只有一块 6GB 显存的卡,建议把 batch 降到 8,并把perspective增强关闭以减少 CPU 数据增强压力。训练日志里重点观察Box(P)Cls(P)mAP50-95三个指标,前两个关注定位和分类精度,第三个是综合指标。马铃薯缺陷检测中 mAP50 比 mAP50-95 更有参考价值,因为缺陷框的边界在标注时本身就有主观性,IOU 阈值过高会低估模型真实表现。

4.3 类别不平衡与损失权重调整

缺陷检测数据集最常见的失衡场景是:『黑斑』样本上千个,『hollow』空心只有 50 个。YOLOv8 默认按类别频率自动调整损失权重,但当少数类占比低于 5% 时,自动权重效果有限。手动干预的做法是在训练配置里修改cls系数,或对少数类样本做离线复制增强。经验值是:少数类样本量低于多数类 1/20 时,先做 3~5 次复制,再配合 mosaic、旋转增强。复制时不能只复制原始图,要把少数类缺陷贴到背景马铃薯图上做合成数据,这样能保留上下文信息。

合成粘贴使用下面这段逻辑:

import cv2 import numpy as np def paste_defect(bg_img, fg_img, fg_bbox, output_path): # bg_img: 没有该缺陷的马铃薯背景图 # fg_img: 从另一张图里裁剪出的缺陷区域 x1, y1, x2, y2 = fg_bbox patch = fg_img[y1:y2, x1:x2] h, w = patch.shape[:2] scale = np.random.uniform(0.8, 1.2) patch = cv2.resize(patch, (int(w * scale), int(h * scale))) ph, pw = patch.shape[:2] bx1 = np.random.randint(0, bg_img.shape[1] - pw) by1 = np.random.randint(0, bg_img.shape[0] - ph) # 简单 alpha 融合,直接把 patch 贴到背景 bg_img[by1:by1+ph, bx1:bx1+pw] = patch cv2.imwrite(output_path, bg_img)

这段逻辑把裁剪出的缺陷区域随机缩放后粘贴到新的背景上,并生成新标签。核心坑在于缩放范围不能过大,否则缺陷纹理失真,模型会把“模糊”当成特征。粘贴位置要尽量避开土豆边缘和薯皮高光区域,否则模型会学习到“缺陷只出现在图像边缘”的错误空间位置先验。

5. 数据增强策略对马铃薯小缺陷检测的影响

5.1 关闭会让缺陷消失的几何增强

YOLO 训练默认开启大量数据增强,但对马铃薯缺陷检测,有两类增强必须谨慎:一是强透视变换,二是随机裁剪加缩放。透视变换会把椭圆形的土豆拉成奇怪的四边形,导致缺陷的纹理被拉伸;随机裁剪后缩放会让小缺陷变成马赛克,模型在验证集上看不清缺陷,被迫用猜测输出。多数情况下,缺陷检测的验证集 mAP 低不是因为模型没学好,而是增强把标签对应的视觉特征破坏掉了。

一个比较稳妥的增强配方如下:

# augment.yaml scale: 0.2 # 缩放幅度 fliplr: 0.5 # 水平翻转 mosaic: 0.5 # mosaic 概率调低 hsv_h: 0.01 # 色相变化调小,避免绿色类误判 hsv_s: 0.3 hsv_v: 0.4

mosaic 在聚划算场景中很有用,但马铃薯表面颜色均匀,四张不同光照的土豆拼在一起,会让模型误把光照差异当成缺陷纹理,所以概率降低到 0.5。色相变化从默认的 0.015 降到 0.01,是因为绿皮和黑斑在 HSV 空间比较接近,色相扰动过大容易让模型产生混淆。

5.2 验证增强效果的三个标准:混淆矩阵、特征图、坏例分析

训练完一轮后,除了看 PR 曲线,还必须打开confusion_matrix.png。马铃薯缺陷检测里最常看到的是 black_spot 和 rot 互相混淆,以及 green 被预测为背景。前者是因为早期腐败表面也会出现黑褐色斑点,单靠 RGB 确实无法区分,这时候要么增加近红外通道,要么把两类合并成“表面可见缺陷”。后者是因为绿皮面积较小时,模型将它与光泽阴影合并,此时要把 green 的最小外接框标注补到位,并在增强里减少阴影区域的像素权重。

特征图可视化用于定位缺陷,使用 YOLOv8 的 Grad-CAM 扩展或者直接输出中间层的特征响应。如果看到高危特征集中在土豆边缘,说明标注时把边缘伪影标进去太多,需要回查标注质量。最后把验证集预测结果导出成带标签的图像:

yolo predict model=runs/potato/exp1/weights/best.pt \ source=/data/potato_defect/images/val \ save_txt=True \ save_conf=True \ conf=0.25

生成的 txt 文件里每一行是类别 ID、置信度和归一化坐标,拿它和 ground truth 做逐图比对,筛选出漏检和误检的图像,重点看漏检图里缺陷的尺寸、位置和遮挡情况。这一步比看 mAP 数字更有价值,因为目标检测数据集的质量问题往往隐藏在坏例里,而不是平均指标里。

5.3 部署时的图像预处理要和训练保持一致

很多项目训练时用 640 分辨率,部署时为了跑实时视频流降到 416,导致精度滑坡。马铃薯产线是静态成像,帧率压力没那么大,不建议随意降低推理分辨率。以 20ms 处理一张图的需求为例,yolov8n 在 640 分辨率下用 RTX 3060 大约 8ms,CPU 上可能需要 80ms 以上,但工业现场大多可以用 GPU 盒子或者编解码卡。推理前注意做同样的归一化操作,YOLOv8 的 predict 接口默认做了 letterbox 和归一化,但如果自己写 C++ 推理或 ONNX Runtime 部署,需要把(img / 255.0)和减均值除方差的操作按训练时的参数复制过来。若训练时没改hyp.scalehsv_h,推理端不需要额外处理,但图像输入的 BGR 与 RGB 顺序必须一致,否则缺陷的颜色特征会完全错位,黑斑可能被识别成绿皮。

最后建议在项目收尾时,把所有错误预测的置信度分数分布拉出来看一眼。如果模型在低置信度区间大量输出 0.3~0.5 的框,说明训练数据里的缺陷样本不够“极端”,可以针对性地补充低对比度、阴影遮挡、表面有水渍的样本。目标检测数据集的质量提升从来不是一劳永逸,而是一个围绕坏例反复迭代的过程——马铃薯这种缺陷表观差异极大的对象,尤其吃这一套。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询