皮革缺陷检测数据集实战:从解压到YOLOv8训练全流程
简介皮革缺陷检测数据集是一份面向制造业质量控制和计算机视觉算法研发的实用型YOLO格式多类别目标检测数据包含490张真实采集的皮革图像并已按训练集429张、验证集41张、测试集20张进行明确划分。每张图片均配套对应的YOLO格式txt标注文件可直观获得边界框与类别标签同时提供yaml数据集配置和docx说明文档方便直接接入YOLO、PyTorch等主流框架。整个压缩包共982个文件以jpg、txt为主体辅以yaml与docx总大小仅15.55MB数据量轻量但覆盖了正常皮革CUERO、明显缺陷MALO和轻微瑕疵REGULAR三类关键质量状态有助于训练模型在复杂场景下区分不同等级的表面问题。该数据集适用于皮革产线自动化质检、动物皮革原料分级定价以及职业院校AI缺陷识别教学也可作为迁移学习的基础数据迁移到其他工业表面检测任务。目前已有251人浏览学习对于希望快速搭建并验证缺陷检测模型的开发者而言是一套可直接落地的数据资源。1. 皮革缺陷检测数据集.zip先解开压缩包再谈模型做皮革产品的外观质检最头疼的不是算法选型而是数据到手那一刻。一个 zip 压缩包往往装着几百张原始皮革图和图对应的标注文件格式可能是 VOC XML、COCO JSON也可能是 YOLO 的 txt。直接解压扔给 YOLOv8 训练大概率在预处理阶段就翻车——伪加密解不开、标注坐标越界、缺陷类别极度不均衡这些都是我踩过的坑。这篇文章按我自己跑通皮革缺陷检测模型的完整流程来讲怎么判断 zip 里的数据能不能用怎么切成模型吃得了的尺寸YOLOv8 的训练参数怎么定以及验收时该看哪些指标而不是盲信 mAP。2. 拆开 zip 之前目录结构、标注格式和解压校验拿到皮革缺陷检测数据集.zip第一件事不是双击解压而是先看压缩包内部结构。这一步能省掉后面至少半天返工。2.1 三种常见的目录组织方式用命令行先列一下包内文件unzip -l leather_defect_dataset.zip | head -50如果系统没有 unzip用 Python 也能看import zipfile z zipfile.ZipFile(leather_defect_dataset.zip) for name in z.namelist()[:30]: print(name) z.close()我实际见到的皮革类数据集目录组织基本是下面三种之一组织方式典型结构优点缺点VOC 风格JPEGImages/Annotations/ImageSets/标注信息完整带图片尺寸和检测框坐标需要转成 YOLO 格式才能训练YOLO 风格images/train/labels/train/images/val/labels/val/解压后微调 yaml 就能直接训如果切分不干净验证集会泄漏按缺陷类别分目录scratch/、hole/、stain/等肉眼查看样本分布方便要自己写脚本生成标注文件建议先跑上面那条 Python 命令把前后 30 个文件名打出来。如果看到Annotations/目录说明是 VOC 风格如果直接就是images/train和labels/train那就是已经整理好的 YOLO 风格能省不少事。这里有个容易忽略的点很多皮革数据集的图片文件名是中文或带空格比如牛皮_左前脚_003.jpg。YOLO 训练时对路径中的空格和中文兼容性不好强烈建议解压后统一重命名成纯英文加数字。2.2 标注格式识别VOC XML、COCO JSON 与 YOLO txt 的关系皮革缺陷检测里最常碰到的标注格式是 VOC XML。打开一个 XML 文件里面是这种结构annotation filenameleather_001.jpg/filename size width2448/width height2048/height depth3/depth /size object namescratch/name bndbox xmin423/xmin ymin512/ymin xmax520/xmax ymax530/ymax /bndbox /object /annotationVOC 的坐标是绝对值单位是像素。YOLO 训练需要的是归一化后的中心点坐标和宽高。COCO JSON 则是一个大字典里面images字段记录图片信息annotations字段记录检测框坐标同样用像素表示。最省事的做法是写一个脚本把 VOC 转成 YOLO txt# voc2yolo.py import xml.etree.ElementTree as ET import os classes [scratch, hole, stain, wrinkle, cut, pinhole, color_abnormal] def convert(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: for obj in root.iter(object): name obj.find(name).text if name not in classes: print(f跳过未知类别: {name} in {xml_path}) 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) # 转成 YOLO 格式中心点坐标 宽高除以图片宽高实现归一化 cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 类别ID写在前后面四个浮点数保留6位小数 f.write(f{classes.index(name)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n) if __name__ __main__: os.makedirs(labels/train, exist_okTrue) os.makedirs(labels/val, exist_okTrue) for xml_file in os.listdir(Annotations/train): if xml_file.endswith(.xml): convert(fAnnotations/train/{xml_file}, labels/train)这段代码做了三件事解析 XML 拿到绝对坐标、把绝对坐标换算成归一化值、按类别ID cx cy w h的格式写入 txt。注意类别的顺序要全局统一后面配置data.yaml时的类别列表必须和这里的classes一致否则训练的标签会错位。COCO JSON 转 YOLO 的原理一样只是数据源从 XML 换成了字典结构核心换算公式不变。2.3 解压与完整性校验伪加密和文件数对不上很多从网盘或邮件附件流转出来的数据集zip会在文件头里设置加密标志位但数据本身没有真正加密这叫 zip 伪加密。Windows 自带解压工具碰到伪加密会一直弹窗要密码实际上一行 Python 就能绕过去import zipfile # 伪加密的zip用Python zipfile可以直接读因为zipfile不理会加密标志位 with zipfile.ZipFile(leather_defect_dataset.zip) as z: z.extractall(./leather_dataset)如果解压时报RuntimeError: File is encrypted说明是真加密那就得找作者要密码。日常常见弱密码可以试一下123456、password、data2024这类但别抱太大期望。真加密的 zip 用暴力破解非常耗时不如去查包里的 README 或联系原作者。解压完成后先核对文件数量find leather_dataset -type f | wc -l find leather_dataset -name *.jpg | wc -l find leather_dataset -name *.xml | wc -l # YOLO格式的话图片数和txt数要对得上 find leather_dataset -name *.txt | wc -l图片数和标注文件数如果对不上多半是标注漏了一些难样本或者有图片本身损坏。这时候要看具体情况决定是弃用还是补标不要硬着头皮往下走。3. 预处理三件事切片、归一化和数据划分解压完成后下一步是把原始皮革图变成模型能吃的训练样本。这三个环节做扎实了训练效果至少差五个点。3.1 工业大图的切片策略把 5000 万像素图切成 640皮革产线上用工业相机拍出来的原图常见分辨率是 2448x2048 到 4096x3000单张图像素在 500 万到 1200 万之间。如果直接把整张图缩放到 640x640一条 3 厘米长的划伤在缩略图里可能只剩不到 10 个像素模型根本学不到特征。常见做法是滑窗切片用 640x640 的窗口、带一定重叠率在原图上滑动切出来的子图作为训练样本。我一般把重叠设成 64 像素这样既不会让目标刚好卡在切片边界也不会让相邻切片太过相似导致过拟合。# tile_images.py import cv2 import os def tile_image(img_path, out_dir, tile_size640, overlap64): img cv2.imread(img_path) if img is None: print(f图片读取失败: {img_path}) return h, w img.shape[:2] base os.path.splitext(os.path.basename(img_path))[0] idx 0 # 按行列滑窗步长 tile_size - overlap for y in range(0, h - tile_size 1, tile_size - overlap): for x in range(0, w - tile_size 1, tile_size - overlap): tile img[y:ytile_size, x:xtile_size] # 计算灰度标准差过滤掉几乎纯色背景的切片 gray cv2.cvtColor(tile, cv2.COLOR_BGR2GRAY) if gray.std() 8: continue cv2.imwrite(os.path.join(out_dir, f{base}_{idx}.jpg), tile) idx 1 if __name__ __main__: os.makedirs(tiles, exist_okTrue) # 批量处理所有原图 for f in os.listdir(origin_images): if f.lower().endswith((.jpg, .png, .bmp)): tile_image(forigin_images/{f}, tiles)参数方面有两个点值得调tile_size我常用 640 或 1280。如果显存吃紧或者缺陷本身不大用 640如果缺陷细长贯穿全图比如皮革正面的大面积划伤用 1280 切片更能保留上下文。overlap一般取 32 到 128皮革纹理复杂时取 96 以上保证缺陷至少完整出现在一张切片里。灰度标准差小于 8 的切片直接丢掉减少纯背景负样本干扰。3.2 标注归一化与坐标越界修复切片之后原来标注在整张大图上的坐标必须跟着换算。切片的左上角在原图中的坐标叫偏移量标注框坐标减去偏移量就是切片内的坐标。如果标注框超出切片边界要做边界截断如果截断后宽度或高度小于 10 像素直接丢弃这个框。# transform_boxes.py def transform_box(box, tile_x, tile_y, tile_size): x1, y1, x2, y2 box # 原图绝对坐标 nx1 max(0, x1 - tile_x) ny1 max(0, y1 - tile_y) nx2 min(tile_size, x2 - tile_x) ny2 min(tile_size, y2 - tile_y) # 截断后太小说明缺陷主体在切片之外丢弃 if (nx2 - nx1) 10 or (ny2 - ny1) 10: return None return (nx1, ny1, nx2, ny2)这段逻辑看着简单但容易漏的是同一个缺陷可能出现在多张切片里导致一个缺陷被标注成多个训练样本。这不算错检测任务本来就用 IoU 匹配来衡量预测框和真值框只要每个切片里的标注框坐标准确模型学到的就是这个位置的缺陷长这样重复出现反而相当于做了过采样。处理完切片和标注后抽几张图可视化验证把标注框画回切片上人眼确认框和缺陷边缘对得上。# visualize.py import cv2 def draw_boxes(img_path, txt_path): img cv2.imread(img_path) with open(txt_path) as f: for line in f: cls, cx, cy, w, h map(float, line.split()) x1 int((cx - w/2) * img.shape[1]) y1 int((cy - h/2) * img.shape[0]) x2 int((cx w/2) * img.shape[1]) y2 int((cy h/2) * img.shape[0]) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imshow(check, img) cv2.waitKey(0)这一步不要省我看过太多同事直接跳过可视化去训练最后发现标注框画在空白区域白白浪费几十个小时。3.3 按图划分训练/验证/测试集别让同一张图串集数据划分有两个常见的坑第一个是按标注文件划分而不是按图划分导致同一张原图的切片同时出现在训练集和验证集验证指标虚高。第二个是划分时没有固定随机种子每次跑结果不一致自己都说不清改进是来自模型还是来自数据变动。正确方式是先把原图划分成三份再把每份的切片归入对应目录# split_dataset.py import random import os import shutil random.seed(42) # 固定种子保证每次划分一致 # 假设切片文件名保留原图名前缀例如 leather_001_0.jpg tiles [f for f in os.listdir(tiles) if f.endswith(.jpg)] base_names sorted(set(_.join(f.split(_)[:-1]) for f in tiles)) random.shuffle(base_names) n len(base_names) train_bases set(base_names[:int(n*0.8)]) val_bases set(base_names[int(n*0.8):int(n*0.9)]) test_bases set(base_names[int(n*0.9):]) for tile in tiles: base _.join(tile.split(_)[:-1]) src ftiles/{tile} if base in train_bases: shutil.move(src, dataset/images/train/ tile) elif base in val_bases: shutil.move(src, dataset/images/val/ tile) else: shutil.move(src, dataset/images/test/ tile)注意这里按原图名前缀分组确保同一张原图的所有切片只进一个集合。随机种子选了 42这样以后不管谁跑这个脚本训练验证集划分都完全一致复现实验结果才有意义。比例上我习惯用 8:1:1如果数据量少于 300 张图可以考虑 9:1 只分训练和验证测试集留待现场实拍补充。4. 用 YOLOv8 在皮革缺陷数据上跑通训练预处理做完接下来就是把数据送给 YOLOv8。这一章给出可直接复制的配置和命令。4.1 目录重组与 data.yaml 配置YOLOv8 期望的目录结构是images/train、images/val和labels/train、labels/val图片和标注文件同名不同后缀。前面步骤已经生成好了直接确认一下结构dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/然后写数据配置文件# leather.yaml path: /your/abs/path/leather_dataset # 数据集绝对路径 train: images/train val: images/val nc: 7 names: 0: scratch 1: hole 2: stain 3: wrinkle 4: cut 5: pinhole 6: color_abnormal几点说明path一定要写绝对路径YOLOv8 对相对路径的处理有时候会把图片路径拼错浪费时间排查。训练集里图片张数除以批大小得到 step 数如果这个数小于 500说明数据量太小后面要考虑冻结层训练。4.2 训练命令与关键参数说明yolo detect train \ dataleather.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ patience30 \ fliplr0.0 \ mosaic0.3 \ scale0.2 \ cacheTrue \ projectruns/leather \ nameexp01参数解释按优先级来modelyolov8n.pt用预训练权重做迁移学习。皮革缺陷数据集一般就几百到几千张从零训练根本学不动必须用 COCO 预训练权重初始化。n 是 nano 版本显存要求低迭代快如果显存够用换成yolov8s.pt或yolov8m.pt精度通常更好。imgsz640和切片尺寸保持一致。显存允许时改成 1280 能提升小缺陷召回率但训练时间几乎翻四倍按自己节奏来。fliplr0.0关闭水平翻转。皮革缺陷里有方向性比如划伤角度和纹理方向相关翻转会让模型困惑。如果你的数据里缺陷方向随机开 0.5 也没问题。mosaic0.3Mosaic 增强把四张图拼成一张对小目标极不友好。皮革缺陷大多是小目标Mosaic 后尺寸再缩一半直接学不到特征。我一般调低到 0.3 或者干脆关掉。这个参数是血泪教训换来的。训练日志里重点看box_loss和cls_loss这两个指标在最后 30 个 epoch 应该持续下降。如果 loss 曲线在某个值附近横跳说明学习率太大或者数据里有脏标签。4.3 数据增强参数对缺陷检测的影响YOLOv8 的增强默认值是为自然图像调过的用在皮革上需要针对性调整。我的常用配置# augment.yaml 覆写部分增强参数 hsv_h: 0.01 hsv_s: 0.3 hsv_v: 0.4 translate: 0.1 scale: 0.2 fliplr: 0.0 mosaic: 0.3hsv_h调低到 0.01皮革颜色一致性很重要色相扰动太大会制造出真实场景不存在的颜色变体。hsv_s和hsv_v保持适中模拟产线光照波动。皮革表面高光反射很强光照变化是漏检的一大来源。scale控制随机缩放比例0.2 意味着图片最多放大缩小 20%。放大太多会让切片里的缺陷撑满整图失去尺度多样性。增强参数不是越多越好工业缺陷检测的通用法则是增强强度和数据量呈反比。几千张图可以开适度增强只有几百张图时增强强度大反而会导致模型学到的纹理特征偏离真实皮革表面。5. 避坑指南从解压到验收的 5 个翻车现场这一章把我在皮革缺陷检测数据集上踩过的坑集中复盘每条都按现象、原因、解决的顺序来写。5.1 zip 伪加密导致解压失败现象Windows 自带解压工具双击压缩包弹出输入密码窗口但数据集说明里根本没提密码。用unzip命令解压时报password protected。原因zip 文件头的通用位标志第 0 位被置为 1表示加密但实际数据区没有加密头属于伪加密。常见于网盘中转或打包工具。WinRAR 和 Windows 自带工具会严格检查标志位所以弹密码而 Pythonzipfile不检查这个标志只认真实数据是否加密。解决直接用 Python 解压import zipfile with zipfile.ZipFile(皮革缺陷检测数据集.zip) as z: z.extractall(leather_dataset)如果报RuntimeError: File is encrypted才是真加密。真加密的通用解决办法是查 README 文件名提示或联系数据提供方不要花时间暴力破解。5.2 样本极不均衡模型只认识划伤现象训练 150 epoch 后划伤类的 mAP 到 0.85孔洞类只有 0.3。val 批预测结果里几乎没有孔洞的预测框。原因皮革产线上划伤最常见孔洞和针孔很少见数据集本身就是长尾分布。模型在训练时大头是背景样本和多数类样本少数类被淹没。解决先做硬过采样——对少数类样本做几份物理拷贝。# oversample_minority.py import os import shutil src_dir labels/train image_dir images/train minority_classes [hole, pinhole, cut] images_containing_minority [] for txt in os.listdir(src_dir): with open(os.path.join(src_dir, txt)) as f: lines f.readlines() # 如果该图片的标签包含少数类则记录整个图片 for line in lines: cls_id int(line.split()[0]) if cls_id in [1, 5, 4]: # hole, pinhole, cut images_containing_minority.append(txt) break # 每个少数类图片复制3份 for txt in images_containing_minority: img txt.replace(.txt, .jpg) for i in range(3): shutil.copy(os.path.join(src_dir, txt), os.path.join(src_dir, f{txt[:-4]}_dup{i}.txt)) shutil.copy(os.path.join(image_dir, img), os.path.join(image_dir, f{img[:-4]}_dup{i}.jpg))这种复制不是数据增强而是让每个 batch 里少数类样本的占比变大。当少数类样本量少于 80 张时这个操作非常有效超过 200 张后用 copy-paste 增强或直接调类别损失权重更划算。5.3 标注与图片不匹配坐标越界与空标签现象训练跑着跑着 loss 变成 nan或者某个 image 的 mAP 一直是 0。训练日志里偶尔出现WARNING cur_labels shape之类提示。原因标注文件里的坐标超出 [0,1] 范围或者标签是空文件或者类别 ID 超出nc数。这些大多发生在手工标注转格式时。解决训练前跑一遍校验脚本。# check_labels.py import os NC 7 for split in [train, val]: label_dir fdataset/labels/{split} image_dir fdataset/images/{split} for txt in os.listdir(label_dir): if not txt.endswith(.txt): continue # 标签文件对应图片是否存在 img_name txt.replace(.txt, .jpg) if not os.path.exists(os.path.join(image_dir, img_name)): print(f[NO_IMG] {txt}) with open(os.path.join(label_dir, txt)) as f: for line in f: line line.strip() if line : print(f[EMPTY_LINE] {txt}) continue parts line.split() if len(parts) ! 5: print(f[BAD_FORMAT] {txt}: {line}) continue cls int(float(parts[0])) x, y, w, h map(float, parts[1:]) if cls 0 or cls NC: print(f[BAD_CLS] {txt}: class{cls}) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): print(f[BAD_BOX] {txt}: {line})这个脚本我每次训练前必跑5 秒的事能省掉几小时的排查时间。发现越界坐标直接删除那一行空标签文件看是人为漏标还是转换脚本 bug前者删文件后者修脚本。5.4 低对比度缺陷漏检现象深色牛皮上的深色划伤测试集 recall 不到 30%。浅色缺陷如白斑效果正常。原因缺陷和背景的灰度差异太小卷积网络提取不到足够判别特征。皮革纹理本身又有细微明暗变化和缺陷混在一起。解决一是训练时增强亮度对比度扰动二是推理时对输入图像做 CLAHE 对比度增强。import cv2 def preprocess_contrast(img): # CLAHE 能增强局部对比度比全局直方图均衡更稳 lab cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) l2 clahe.apply(l) lab2 cv2.merge([l2, a, b]) return cv2.cvtColor(lab2, cv2.COLOR_LAB2BGR)这个技巧最大的好处是只改输入图像不改模型结构和标签立竿见影。缺点是 CLAHE 可能放大皮革纹理噪声导致误检变多所以推理时要用同一套预处理在验证集上验证一遍确认 mAP 确实提升而不是假象。5.5 训练中断后可以从 last.pt 接着跑现象训练到 100 epoch 时服务器重启全部归零又得从头训 150 epoch。原因训练中断后没做任何保留性设置重新跑命令默认从权重初始化开始。解决YOLOv8 每次训练都会在输出目录保存last.pt和best.pt训练中断后直接yolo detect train resumeTrue \ projectruns/leather \ nameexp01resumeTrue会从输出目录自动读取last.pt和超参数继续从第 100 epoch 接着训练学习率也会按原调度器续上。注意把project和name参数和上次保持一致否则找不到权重文件。这个习惯我现在已经固化了凡是训练超过 2 小时的任务默认开 resume 兜底。6. 不看曲线看现场置信度阈值与 CLAHE 对比度增强模型训练完别急着看测试集 mAP先拿几张真实产线拍的照片跑推理。和 MVTec AD 这类规整的工业缺陷基准不同现场采集的皮革图片光照、曲面反光、纹理变化都更复杂测试集上的指标到现场经常要打八折。调置信度阈值是最快见效的环节。YOLOv8 默认conf0.25这个值对多数类可能合适但对少数类往往偏严。我的做法是分缺陷类型单独调容易混淆的wrinkle 和 scratch用高阈值 0.45特征明确的hole、pinhole用低阈值 0.15。如果不想写代码先跑一遍验证集预测结果看每个类别在不同置信度阈值下的 precision-recall 曲线取曲线的拐点作为该类的阈值。还有一个实际技巧大图推理时用滑窗加 CLAHE 预处理两个环节叠加效果稳定# infer_large.py import cv2 from ultralytics import YOLO model YOLO(runs/leather/exp01/weights/best.pt) def infer_large(img_path, tile_size640, stride576, conf0.25): img cv2.imread(img_path) h, w img.shape[:2] all_boxes [] for y in range(0, h - tile_size 1, stride): for x in range(0, w - tile_size 1, stride): tile img[y:ytile_size, x:xtile_size] tile preprocess_contrast(tile) # CLAHE增强函数见5.4 result model.predict(tile, confconf, verboseFalse) for box in result[0].boxes.data.tolist(): x1, y1, x2, y2, score, cls box all_boxes.append([x1x, y1y, x2x, y2y, score, cls]) return all_boxes步长取 576也就是 640 减 64 的重叠和训练时的切片策略保持一致推理效果最稳。跨切片的同一个缺陷可能在输出里出现多个框这一步我通常会再跑一个全局 NMS把 IoU 大于 0.5 的邻近框合并能让最终报警更干净。如果细长划伤总是用水平框框进大片背景导致误检可以考虑用 MMRotate 这类旋转框检测器它对带角度的缺陷框得更紧。但前提是原始标注就得是旋转框格式如果手里还是水平框就需要补标一轮成本不低建议先在水平框方案上把阈值和预处理调到位再评估是否有必要上旋转框。这几年折腾下来我养成的习惯是任何数据集到手先花 20 分钟看目录、验完整性、画几个标注可视化再碰训练按钮。这个习惯把我在皮革缺陷检测项目上的返工率降了一半。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →