竹子缺陷检测数据集与YOLO训练全流程解析 简介面向竹子培育与加工场景中的缺陷检测需求这份数据集提供2953张标注图片统一为YOLO与VOC兼容格式可直接用于YOLO系列、Faster RCNN、SSD等主流目标检测模型训练。类别覆盖Bud、Sprouted_Bud、Damage_Bud三类对应芽体、萌芽与损伤芽便于开展细粒度表型识别实验。资源包共2000个文件其中1999个txt标注文件对应每张图片的边界框信息另有1个yaml配置文件用于定义类别与路径压缩后整体大小约77.18MB轻量易部署。标注文件名与图片一一对应目录结构简洁无需额外转换即可接入常见训练框架。目前已有357人学习下载适合深度学习者、农业AI研究者和竞赛选手快速搭建竹子缺陷检测基线模型也可作为数据增强或迁移学习的基础样本集。1. 竹子缺陷检测数据集目标检测入门最难找的工业场景样本做目标检测这些年公开数据集见了不少VOC、COCO、各类遥感影像轮着玩但真正落到工业现场才发现最有价值的数据往往是最没人整理的。竹子缺陷检测数据集就是典型——原木、竹材加工厂里裂纹、霉变、虫眼直接决定产品等级流水线上却很少有现成的视觉方案可用。这套数据集恰好补上了这个缺口它按目标检测的标准格式组织标注了竹子表面常见缺陷类别拿过来就能喂给 YOLO 系列训练省掉的是从零采集、清洗、标注的几周时间。对做工业视觉、木材加工自动化的从业者来说这套数据比花哨的学术数据集实在得多尤其适合作为缺陷检测落地的第一个实战项目。2. 数据集构成与标注规范先看清标签再谈训练2.1 缺陷类别与场景划分这套数据集按缺陷类型做了分类常见的是开裂、霉变、虫眼、污渍这几类。每个类别在表型上有明显视觉差异开裂呈长条状深色纹理霉变是灰绿色斑块虫眼是密集小孔污渍则是边界模糊的暗色区域。类别的区分度和目标尺度差异较大——虫眼在整幅图中可能只占几十个像素而霉变能覆盖半个竹节。数据集中图片多来自实拍竹材表面光照和拍摄角度并不统一。这反而比匀光匀背景的合成数据更接近工业现场流水线上竹材位置固定但表面反光变化大室外堆场更是受天气影响严重。训练时不能用理想化数据集的标准去衡量它而要把光照变化当成天然的增强手段。2.2 标注格式与文件组织标注采用 PASCAL VOC 格式每张图片对应一个同名 XML 文件。XML 里包含对象类别名称和边界框坐标坐标以左上角和右下角两点的像素值记录。这个格式是目标检测数据的老牌通用格式后续转成 YOLO 的 txt 或者 COCO 的 json 都很方便。数据集文件按训练集、验证集和测试集划分比例大概是 7:2:1。目录下还有 label_map 文件标明类别名和索引的对应关系。拿到手后第一步不是急着训练而是先用脚本把标注可视化一遍确认边界框和缺陷位置是否对齐——标注错位在实拍数据里不罕见跳过这步等于带病上车。3. 把数据改成 YOLO 能吃的格式一个脚本解决 VOC 到 YOLO 的转换3.1 目录重建与类别映射YOLO 系列训练需要的是 txt 标注文件每行一个目标格式是“类别序号 x_center y_center width height”所有坐标都归一化到 0 到 1 之间。转换脚本先重建目录结构把图片和标签分开放再逐张解析 XML。import os import xml.etree.ElementTree as ET from collections import defaultdict def convert_voc_to_yolo(voc_dir, yolo_dir, classes_file): # 读取类别映射 with open(classes_file, r) as f: classes [line.strip() for line in f.readlines()] class_map {name: idx for idx, name in enumerate(classes)} # 准备输出目录 images_dir os.path.join(yolo_dir, images) labels_dir os.path.join(yolo_dir, labels) os.makedirs(images_dir, exist_okTrue) os.makedirs(labels_dir, exist_okTrue) xml_files [] for root, _, files in os.walk(voc_dir): for name in files: if name.endswith(.xml): xml_files.append(os.path.join(root, name)) for xml_path in xml_files: tree ET.parse(xml_path) root_elem tree.getroot() # 读取图像尺寸 size root_elem.find(size) img_w float(size.find(width).text) img_h float(size.find(height).text) # 对应的图片文件名 img_name os.path.splitext(os.path.basename(xml_path))[0] img_ext .jpg # 按实际图片扩展名修改 dst_img_path os.path.join(images_dir, img_name img_ext) txt_lines [] for obj in root_elem.findall(object): name obj.find(name).text if name not in class_map: print(f跳过未映射类别: {name}) continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 坐标归一化 cx ((x1 x2) / 2) / img_w cy ((y1 y2) / 2) / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 过滤异常标注 if cx 0 or cy 0 or w 0 or h 0: print(f跳过非法标注: {xml_path} 目标 {name}) continue label class_map[name] txt_lines.append(f{label} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) # 写入 txt 标注 txt_path os.path.join(labels_dir, img_name .txt) with open(txt_path, w) as f: f.write(\n.join(txt_lines) \n)转换逻辑的关键有三处类别索引必须和训练配置文件里的 names 列表完全一致否则会出现标签错位即模型学到的类别名和实际标签对不上归一化时用的是图像原始宽高不是缩放后的尺寸过滤掉坐标落在图像外或宽高为负的异常框这类脏数据是后期训练震荡的隐形杀手。脚本里打印跳过信息的逻辑很值得保留。数据量大时不要静默处理把跳过的样本列表导出回头单独核查一遍避免漏掉整批标注偏移的问题。常见做法是加一个--check-only参数先只扫描不输出检查完再正式转换。3.2 数据增强策略与样本均衡竹子缺陷数据集的类别天然不平衡开裂和霉变样本多虫眼因为目标小且出现频率低样本量明显偏少。直接拿原始数据训练模型会偏向多数类虫眼的召回率会很差。一种朴素的补法是做离线增强把样本少的类别通过复制变换来增加数量。注意不要只做翻转和旋转针对竹子表面纹理方向还可以加入轻微的光照扰动和局部遮挡模拟。增强后的图片重新生成标注注意变换和标注要同步尤其是旋转时坐标需要重新计算不能沿用原标注。我的做法是写一个增强脚本对每张图以 0.5 概率做水平翻转以 0.3 概率做亮度抖动再单独对虫眼类做 3 倍的复制增强。这样训练集的比例从悬殊变成接近 3:1整体收敛稳定很多。4. 训练配置与参数调节这套数据集该怎么喂给 YOLO4.1 数据集 YAML 与模型选型拿 YOLOv8 做例子训练前要把数据集配置写清楚。data.yaml文件的路径、类别数、类别名必须和转换脚本里的 class_map 一致这点最容易翻车。# data.yaml path: ./bamboo_defect train: images/train val: images/val test: images/test nc: 5 names: 0: crack 1: mold 2: insect_hole 3: stain 4: split数据集里含虫眼这类小目标模型选型上我倾向于用 YOLOv8s 而不是 nano。s 比 nano 多一倍的参数量在小目标召回上的提升明显而工业场景下推理速度差异并不关键。如果算力紧张可以先用 nano 跑通再用 s 做最终训练。输入尺寸建议设为 640。竹材表面缺陷的边界框通常不是极端细长就是极小640 是速度和精度的平衡点。设成 1280 对小目标有额外帮助但显存占用会翻倍训练时间拉长很多机器跑不动。4.2 训练命令与超参调节# 训练命令 yolo train \ modelyolov8s.pt \ datadata.yaml \ epochs150 \ imgsz640 \ batch16 \ patience30 \ projectbamboo_defect \ nametrain_v1 \ pretrainedTrue # 训练完成后评估 yolo val \ modelbamboo_defect/train_v1/weights/best.pt \ datadata.yaml \ imgsz640 \ batch16参数按工业场景做调整。patience30是早停策略验证集 loss 连续 30 轮不降就自动结束150 轮的 epoch 上限其实很少跑满。pretrainedTrue用 COCO 预训练权重做初始化对缺陷检测这种数据量只有几千张的任务是必须的从零训练很难有好的效果。训练过程中主要看两个指标box_loss 和 cls_loss 的下降曲线。如果 loss 在某个阶段反复震荡优先调低学习率或者把lr0从默认的 0.01 降到 0.005。验证集 mAP50 在训练结束后会自动输出但 mAP50 对缺陷检测这类场景只是一个底线参考更该关注的是每个类别单独的 AP。把 best.pt 的验证结果 CSV 拉出来看如果某个类别 AP 明显偏低基本就是样本量不足或者标签不一致导致的。4.3 显存不够时的替代方案显存不够是工业场景的常态。batch 降到 4或者用--cache disk让数据缓存在磁盘而不是内存。再不行就开启梯度累积yolo train里可以写batch4配合多次迭代模拟大 batch 的效果。train 阶段的 batch 大小影响 BN 层统计量明显改小后需要把模型参数里的 momentum 调高一点来补偿。简单来说batch 从 16 降到 4momentum0.937这个默认值可以不动但如果降到 2 或 1要改成momentum0.95才能保持稳定。另一个省显存的手段是用混合精度训练AMP 在 YOLO 里默认开启不用额外配置。注意 AMP 和 batch 大小无关别被网上那些混淆了。5. 避坑指南竹子缺陷数据集训练的五个常见问题5.1 标签文件行格式错误导致大量目标被丢弃现象训练日志里出现大量 warning提示标签文件为空或格式非法mAP 始终上不去。原因转换脚本生成的 txt 里如果类别索引超过 nc 范围或者坐标值不在 0 到 1 之间YOLO 训练时会把对应行当作无效数据丢弃。最常见的源头是类别映射不一致——data.yaml 里类别顺序和转换脚本的 classes.txt 不一致。解决先跑一遍yolo val并打印每张图的标签数量和原 XML 里目标数量对比。两个数对不上就检查被丢的是哪些文件统一修正映射后重新转换。血泪经验是转换脚本里那个 classes_file 路径每次都要重新确认别复制旧工程的。5.2 训练集和验证集内容重叠导致指标虚高现象画出来的 PR 曲线完美loss 曲线很漂亮但拿到现场图片一测检测结果一塌糊涂。原因数据划分时同一根竹子上不同角度的照片可能被分到了训练集和验证集。模型相当于开卷考试验证指标没有参考价值。解决按“样本分组”划分而不是按文件随机分。同一根竹材的所有图片归入同一个组整个组进训练或验证。这样验证集才真正反映泛化能力代价是训练集稍小但指标更有说服力。5.3 虫眼类 AP 奇低但整体 mAP 还过得去现象总 mAP50 有 0.85但看分类别指标insect_hole 的 AP 只有 0.3 出头。原因小目标在默认的 anchor 设置下匹配不到足够多的正样本加上样本量本来就少双重劣势叠加。解决把输入尺寸从 640 提到 960同时把虫眼类别的样本通过 Mosaic 增强多复制几份。如果还不行考虑用 YOLO 的 SAHI 切片推理这个放到最后一章展开。5.4 训练过程中 loss 出现 NaN现象训练到一半loss 直接变成 nanP 和 R 归零。原因学习率过大或者数据里有像素值异常的图片。工业实拍图偶尔会有纯黑、纯白或损坏的图片这些图片进入网络后激活值爆掉。解决先排查数据把像素均值接近 0 或 255 的图片删掉。再用小学习率从头训练比如lr00.001。如果原来是 AMP 模式下出的问题试试关掉混合精度跑一轮看是否还出现。5.5 验证集 mAP 不错但漏检多现象验证集指标达标放到流水线测试发现同一缺陷时检得出时检不出。原因工业场景下拍摄距离和角度变化目标尺度分布和数据集不完全一致。模型见过的尺度范围有限超出范围的样本自然漏检。解决收集现场失败样本定期补充进训练集做增量训练。这个方法笨但有效光学、机械、软件调完之后剩下的精度都得靠喂数据喂出来。6. 切片推理搞定虫眼这类小目标的最后一招工业场景出图经常是 2000 万像素以上的大图直接把整张图喂给 YOLO 会把小目标缩到几个像素漏检是必然。常见做法是切片推理把原图切成若干有重叠的块分别检测再把结果映射回原图坐标。import numpy as np from ultralytics import YOLO def slice_inference(model, img_path, slice_size640, overlap0.2): img cv2.imread(img_path) h, w img.shape[:2] step int(slice_size * (1 - overlap)) results_all [] for y in range(0, h, step): for x in range(0, w, step): # 边界处理确保切片不越界 x_end min(x slice_size, w) y_end min(y slice_size, h) slice_img img[y:y_end, x:x_end] # 切片小于模型输入尺寸时做填充 if slice_img.shape[0] slice_size or slice_img.shape[1] slice_size: pad_img np.full((slice_size, slice_size, 3), 114, dtypenp.uint8) sy, sx slice_img.shape[:2] pad_img[:sy, :sx] slice_img slice_img pad_img offset_x, offset_y x, y else: offset_x, offset_y x, y results model.predict(slice_img, imgszslice_size, conf0.25)[0] boxes results.boxes.xyxy.cpu().numpy() scores results.boxes.conf.cpu().numpy() clses results.boxes.cls.cpu().numpy().astype(int) for box, score, cls in zip(boxes, scores, clses): x1, y1, x2, y2 box # 映射回原图坐标 results_all.append([ x1 offset_x, y1 offset_y, x2 offset_x, y2 offset_y, score, cls ]) return results_all切片重叠率取 0.2 是个折中重叠太少目标被切断后检测不到重叠太多同一个目标被多次检出后处理 NMS 要处理大量重复框。切到边界时用灰色填充而不是直接丢弃避免边缘目标被吞。最后把每个切片的坐标按原图偏移量加回去再跑一次全局 NMS 就能得到整图的检测结果。切片推理还有个隐藏好处推理内存占用大幅降低GPU 显存只有 6G 的机器也能跑 2000 万像素大图。坏处是推理时间线性增加一块 640×640 切片大约 10 毫秒2000 万像素图切 50 块左右整体耗时在 0.1 秒量级流水线节拍允许的话这个方案最稳妥。这套数据集的完整价值只有在做切片推理时才体现出来——因为它包含大量真实的大分辨率竹材图不像学术数据集那样预先裁剪好目标。从那以后我做工业检测项目凡是目标像素占整图比例低于千分之一的都强制先跑一遍切片推理再谈指标这已经是习惯了。希望这份数据集和这些踩坑记录能帮你在竹子缺陷检测上少绕几圈。本文还有配套的精品资源点击获取