☰
竹子缺陷检测数据集与YOLO训练全流程解析
2026/10/2 20:50:08 网站建设 项目流程

简介:面向竹子培育与加工场景中的缺陷检测需求,这份数据集提供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_ok=True) os.makedirs(labels_dir, exist_ok=True) 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. 训练配置与参数调节:这套数据集该怎么喂给 YOLO

4.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 \ model=yolov8s.pt \ data=data.yaml \ epochs=150 \ imgsz=640 \ batch=16 \ patience=30 \ project=bamboo_defect \ name=train_v1 \ pretrained=True # 训练完成后评估 yolo val \ model=bamboo_defect/train_v1/weights/best.pt \ data=data.yaml \ imgsz=640 \ batch=16

参数按工业场景做调整。patience=30是早停策略,验证集 loss 连续 30 轮不降就自动结束,150 轮的 epoch 上限其实很少跑满。pretrained=True用 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里可以写batch=4配合多次迭代模拟大 batch 的效果。

train 阶段的 batch 大小影响 BN 层统计量,明显改小后需要把模型参数里的 momentum 调高一点来补偿。简单来说,batch 从 16 降到 4,momentum=0.937这个默认值可以不动,但如果降到 2 或 1,要改成momentum=0.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 直接变成 nan,P 和 R 归零。

原因:学习率过大或者数据里有像素值异常的图片。工业实拍图偶尔会有纯黑、纯白或损坏的图片,这些图片进入网络后激活值爆掉。

解决:先排查数据,把像素均值接近 0 或 255 的图片删掉。再用小学习率从头训练,比如lr0=0.001。如果原来是 AMP 模式下出的问题,试试关掉混合精度跑一轮,看是否还出现。

5.5 验证集 mAP 不错但漏检多

现象:验证集指标达标,放到流水线测试发现同一缺陷时检得出时检不出。

原因:工业场景下拍摄距离和角度变化,目标尺度分布和数据集不完全一致。模型见过的尺度范围有限,超出范围的样本自然漏检。

解决:收集现场失败样本,定期补充进训练集做增量训练。这个方法笨但有效,光学、机械、软件调完之后,剩下的精度都得靠喂数据喂出来。

6. 切片推理:搞定虫眼这类小目标的最后一招

工业场景出图经常是 2000 万像素以上的大图,直接把整张图喂给 YOLO 会把小目标缩到几个像素,漏检是必然。常见做法是切片推理:把原图切成若干有重叠的块,分别检测,再把结果映射回原图坐标。

import numpy as np from ultralytics import YOLO def slice_inference(model, img_path, slice_size=640, overlap=0.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, dtype=np.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, imgsz=slice_size, conf=0.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 秒量级,流水线节拍允许的话这个方案最稳妥。

这套数据集的完整价值只有在做切片推理时才体现出来——因为它包含大量真实的大分辨率竹材图,不像学术数据集那样预先裁剪好目标。从那以后我做工业检测项目,凡是目标像素占整图比例低于千分之一的,都强制先跑一遍切片推理再谈指标,这已经是习惯了。希望这份数据集和这些踩坑记录能帮你在竹子缺陷检测上少绕几圈。

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

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

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

立即咨询