ARTICLE DETAIL

资讯详情

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

鸡蛋缺陷检测实战:VOC/YOLO格式转换与YOLOv8训练避坑指南

鸡蛋缺陷检测实战:VOC/YOLO格式转换与YOLOv8训练避坑指南 简介面向鸡蛋品质检测场景的缺陷识别数据集主要服务计算机视觉目标检测方向的开发者与科研人员可用于鸡蛋裂缝检测模型的训练与评估也适合作为学术实验或工业质检方案验证的数据基础。压缩包共2000个文件以xml标注文件为主另含说明txt整体体积64.39MB便于下载与解压使用。数据集同时提供Pascal VOC与YOLO两种标注格式覆盖egg、egg-crack两个类别标注框总数达2755个其中完整鸡蛋框2387个、裂缝框368个裂缝框占比约13.4%可供小样本类别增强与不平衡检测研究既可直接接入YOLO训练流程也可通过VOC格式用于Faster R-CNN、SSD等主流检测器。标注工具采用labelImg格式规范统一可省去自行标注的时间成本。已有274人学习下载适合需要标准化数据来验证检测算法、对比裂缝定位效果或快速开展鸡蛋质检项目的使用者。1. 鸡蛋缺陷检测数据集2077张两类别能帮你把哪些活干完拿到一个“鸡蛋缺陷检测数据集VOCYOLO格式2077张2类别.zip”第一反应别急着解压训练。2077张图在工业视觉里属于“小而贵”的规模——一张带裂纹、带血斑的蛋壳图人工标注成本比普通目标检测数据集高不少因为缺陷小、对比度低、边缘不清晰。它的价值不在“大”而在格式全VOC和YOLO两套标注都给了省掉最容易被新手卡住的坐标转换环节也意味着你可以直接从YOLOv8或者MMDetection开箱训练。这个数据集解决的是禽蛋分拣线上的核心问题把蛋壳上的裂纹、污斑这类表面缺陷框出来。适合谁用做农产品视觉检测预研的算法工程师想快速跑通一个结构化数据集的落地流程也适合带学生做课题的导师拿它当教学数据让新手练完整的数据检查、训练、评估链路。你要清楚一点它不能直接给你一条产线级的分拣系统但能帮你把“缺陷检测到底能不能行”这件事在两天内给出答案。下面整个思路都围绕这个目标展开先摸清数据底细再统一格式然后训练、避坑最后验证。2. 拆开压缩包先别训练VOC目录和YOLO目录分别该长什么样2.1 两类缺陷标注怎么看类别文件与标注字段压缩包同时带VOC和YOLO格式绝大多数情况下两种格式对应的是同一批图片只是标注文件的表现形式不同。VOC格式是Pascal VOC约定图片集中在JPEGImages标注是Annotations目录下的XML文件YOLO格式是Ultralytics/YOLO系列使用的结构图片在images标注是labels目录下同名TXT文件。我一般拿到zip先做一次目录核验不急着跑训练因为结构不对的话YOLO会直接报“No labels found”到时候你根本分不清是路径问题还是数据问题。先看类别。标题写了2类别也就是两个缺陷类。常见的是crack裂纹和stain污斑也有数据集会用hairline和blood一切以解压后的classes.txt或VOC的ImageSets/Main里的类别名为准。你要做的第一件事是打开标注文件确认类别名和你想象的一致。一个典型VOC的XML注释里name字段就是类别标签bndbox给出xmin、ymin、xmax、ymax的像素坐标。YOLO的TXT每一行是class_id center_x center_y width height全部归一化到0到1之间。别小看这个区别很多人后面训练发现框全偏了就是因为把像素坐标当归一化坐标喂了进去。下面这张表是我拿到这种“双格式数据集”时常用的核对清单建议你解压后逐一勾掉检查项VOC目录YOLO目录不匹配时的后果图片位置JPEGImages/*.jpgimages/*.jpg路径解析失败加载0张图标注位置Annotations/*.xmllabels/*.txt“No labels found”直接中断文件同名对齐xml和jpg同名txt和jpg同名漏标、报错类别定义classes.txt或xml内namedata.yaml里的names类别错位训练分不清坐标范围像素值不超过宽高值在0~1之间框越界、训练loss爆炸2.2 先跑一遍数据体检脚本再决定用什么模型不要相信压缩包简介里说“2077张2类别”就够用了。2077张图可能有一部分是空图没有任何缺陷也有一部分是同一张图重复出现尤其是从视频帧抽取的数据集。手边没有现成脚本的话我一般直接三行Python先数数import os from pathlib import Path # 假设YOLO目录结构images/ 和 labels/ 平级 image_dir Path(yolo/images) label_dir Path(yolo/labels) images list(image_dir.glob(*.jpg)) list(image_dir.glob(*.png)) labels list(label_dir.glob(*.txt)) print(f图片数: {len(images)}标签文件数: {len(labels)}) missing [img.name for img in images if not (label_dir / (img.stem .txt)).exists()] print(缺标签的图片:, len(missing), missing[:5])这段脚本的价值在于提前暴露“有图没标签”或“有标签没图”的问题。实际见过的数据集中缺个十几张是常态因为标注人员可能跳过模糊图或者导出脚本把空标注文件漏掉了。缺少量可以直接删掉缺得多了说明标签名不匹配得做一次rename。然后统计每个类别的目标数量和尺寸分布这一步会直接影响你后面模型选型和数据增强策略。我习惯把每张图里目标框的宽高都输出来看看有没有特别小或者特别大的目标。比如裂纹通常细长可能只有30x5像素污斑大一些。如果两类目标尺寸差异很大就需要注意小目标漏检如果某一类只有两三百个实例那类别不平衡会非常麻烦。统计脚本如下import os from pathlib import Path label_dir Path(yolo/labels) class_count {} box_count {} tiny_count {} for txt in label_dir.glob(*.txt): with open(txt) as f: lines f.readlines() for line in lines: parts line.split() if len(parts) 5: cls int(parts[0]) w float(parts[3]) h float(parts[4]) class_count[cls] class_count.get(cls, 0) 1 box_count[cls] box_count.get(cls, 0) 1 # 归一化后的宽高小于0.05认为是小目标 if w 0.05 and h 0.05: tiny_count[cls] tiny_count.get(cls, 0) 1 print(类别实例数:, class_count) print(小目标归一化边长0.05占比:, {k: v / box_count[k] for k, v in tiny_count.items()})这段跑完你基本就清楚要不要做切图、要不要加数据增强、要不要用小尺寸输入。两个类别的实例数差3倍以上就得考虑类别重加权或者过采样。小目标占比超过30%就得考虑把图片切成4块训练或者用更高分辨率输入。3. 把VOC转成YOLO格式转换脚本与四个边界坑3.1 一份能直接用的xml转txt脚本虽然标题说两种格式都给了但实践中总会遇到VOC的标注和YOLO的标注对不上或者你手里的数据其实只有VOC格式。常见做法是写一个转换脚本把XML里的像素坐标转成归一化坐标。这里给出一份我改过很多次、能直接跑的脚本import xml.etree.ElementTree as ET from pathlib import Path # 类别名 - 数字id按这个数据集的2类别举例子 CLASSES {crack: 0, stain: 1} def convert_voc_to_yolo(xml_path, output_dir, target_width, target_height): tree ET.parse(xml_path) root tree.getroot() # 优先读xml里的size其次用外部传入的宽高 size root.find(size) if size is not None: width int(size.find(width).text) height int(size.find(height).text) else: width, height target_width, target_height yolo_lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES[name] bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 转换为归一化坐标 x_center (xmin xmax) / 2.0 / width y_center (ymin ymax) / 2.0 / height box_w (xmax - xmin) / width box_h (ymax - ymin) / height # 过滤越界/无效框只保留0-1范围内的框 if not (0 x_center 1 and 0 y_center 1 and box_w 0 and box_h 0): continue yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) if yolo_lines: output_path Path(output_dir) / (Path(xml_path).stem .txt) output_path.write_text(\n.join(yolo_lines)) return len(yolo_lines) return 0逻辑说明脚本先解析XML里的size如果标注文件里没写宽高就用外部传入的参数兜底。然后逐个读object忽略不在类别表里的名字。转换的核心是中心点坐标等于(xminxmax)/2再除以图宽框宽等于xmax-xmin再除以图宽。最后加了过滤逻辑防止越界框进入训练。参数说明CLASSES字典必须和你的数据集一致如果类别名是crack_a、crack_b这种细分类你要么合并要么把新类别加进去。target_width和target_height仅当XML里没有size字段时才被使用正常情况请直接传图片实际宽高我一般用cv2.imread动态拿。3.2 转完必须做的三个校验转换不是跑完就完事我见过太多人转完直接训练然后mAP一直为0回头查才发现TXT里全是坐标错位。转换后第一件事随机抽5个TXT把里面的归一化坐标乘以图片宽高看是否落在图片范围内。第二件事找出所有没有TXT的XML这些往往是空标注如果空标注多说明这个数据集的负样本不少考虑保留并另建一个empty类别或者干脆删掉对应图片否则YOLO会忽略这些图。第三件事统计转换后的类别数量、目标数量是不是和原始XML对得上对不上就是过滤规则把一些正常框误删了。另外强烈建议在图片上可视化一批转换后的框直接把TXT坐标画回原图。代码很简单import cv2 from pathlib import Path img cv2.imread(yolo/images/sample.jpg) h, w img.shape[:2] with open(yolo/labels/sample.txt) as f: for line in f: cls_id, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cls_id)), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imwrite(check.jpg, img)这段代码的作用是把TXT里的归一化坐标还原成像素坐标再画到图片上。你只要看框是否紧贴裂纹或污斑就知道转换有没有跑偏。如果有一半框偏了先检查XML里的坐标是不是基于原图尺寸再看是不是XML里嵌套多了一个segmented节点影响解析。3.3 为什么我不建议你直接改xml当txt用总有人图省事想用sed把XML里的name替换成数字再把坐标直接拼成一行。这种做法的坑有四条第一XML里可能存在多个object每一行文本的顺序和xml顺序必须一致第二XML里可能出现没有bndbox的object直接处理会崩第三XML里的宽高如果和图片真实分辨率不一致整个坐标全废第四YOLO还支持class_names文件你一旦用sed改了类别名再想恢复原始标注就要返工。所以老老实实用Python脚本解析保留一份原始XML别动转换后的TXT放在另一个目录。这也是给你自己留后悔药——万一后面想换格式或者重新采样原始数据还在。4. 用YOLOv8把2077张图训成可用模型最小命令与参数4.1 构造data.yaml路径、类别名别写错YOLOv8训练自己的数据集第一个卡点就是data.yaml。它不像VOC那样靠目录结构隐式约定而是要把图片位置、标注位置、类别清单全部显式写出来。这里以Ultralytics YOLOv8的惯例为准给你一个模板# data.yaml path: /path/to/egg_defect # 数据集根目录绝对路径 train: images/train # 训练集图片目录相对path val: images/val # 验证集图片目录 names: 0: crack 1: stain这个yaml文件的意思是告诉YOLOv8去/path/to/egg_defect/images/train找图片对应的标签默认在相同父目录下的labels/train不需要你在yaml里单独写标签路径YOLO会自动把images替换成labels。如果你把YOLO格式的标注放在其他地方必须在train同级用labels目录或者使用train_labels字段但不建议那么干遵循默认结构最省事。参数说明path最好用绝对路径尤其是你还没固定项目目录的情况下names的顺序必须和标注TXT里class_id的顺序一致。如果YOLO格式的标注是0开头而你在names里把stain放在索引0那就全错位了。我处理过不少翻车现场基本都栽在names和class_id对不上。4.2 训练命令与预训练模型选择和超参数解读数据准备好了训练命令可以写得很短yolo detect train \ dataegg_defect.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ device0 \ patience30 \ projectruns/egg_defect解释一下各参数用途。modelyolov8n.pt是直接加载官方预训练模型权重这就是热词里说的“yolo预训练模型下载”——你第一次跑会自动下载网络不好的话先手动把权重放到~/.config/Ultralytics/下面。选n还是s取决于你的显卡和推理算力。如果你只有一张4GB显存的老卡yolov8n配imgsz640加batch16可能爆显存这时候把batch降到8imgsz降到480先看看。patience30的意思是连续30个epoch验证集mAP没有提升就早停2077张图按默认150个epoch训练通常60~80个epoch就收敛了不需要傻等150个epoch跑满。数据划分上2077张图我一般按8:1:1切成train/val/test或者直接让YOLOv8在初始化数据时自动划分但为了验证可复现我倾向手动切。切分脚本注意按图片名把对应的TXT一起移过去import random from pathlib import Path import shutil src Path(yolo/images) label_src Path(yolo/labels) train_dir Path(egg_defect/images/train) val_dir Path(egg_defect/images/val) train_dir.mkdir(parentsTrue, exist_okTrue) val_dir.mkdir(parentsTrue, exist_okTrue) images list(src.glob(*.jpg)) list(src.glob(*.png)) random.seed(42) random.shuffle(images) val_count int(len(images) * 0.2) val_images images[:val_count] for img in val_images: shutil.copy(img, val_dir / img.name) shutil.copy(label_src / (img.stem .txt), Path(egg_defect/labels/val) / (img.stem .txt))切分逻辑是随机打乱后取20%作为验证集剩下80%作为训练集。random.seed(42)保证每次切分结果一样这对复现实验很重要。注意把验证集的labels也挪到egg_defect/labels/val下YOLO才会自动读取。超参数方面针对2类小目标缺陷我通常会额外调整三个参数hsv_h、hsv_s、hsv_v控制颜色增强但鸡蛋裂纹对比度低过度调高会导致模型学颜色而不是学结构所以我把这三项都降到默认的一半mosaic1.0默认开这个增强对缺陷检测有帮助因为它把多张图拼在一起模拟了多目标场景fliplr0.5水平翻转对裂纹这类方向敏感的缺陷可能有害裂纹在翻转后方向变了但蛋壳裂纹本质上没有方向性域偏好所以一般可以保留。4.3 训练中的日志怎么看loss、iou、bn崩溃的信号训练启动后控制台会实时滚动box_loss、cls_loss、df_loss分布焦点损失和验证集mAP50-95。你要看的第一个信号是cls_loss如果两个类别的实例数差得非常大cls_loss会在一开始就比正常值高一截而且后期降不下去。第二个信号是mAP50曲线正常情况从第20个epoch开始会明显上升如果到第30个epoch仍然接近于0大概率是数据路径配错了或者标签文件为空。第三个信号是loss出现NaN或者突然飙到几十。这往往是“yolo训练中bn崩溃”的现象——BatchNorm层的running_mean和running_var在梯度爆炸时变成NaN。原因多是学习率太高、batch太小、或者数据里有极端的坐标异常值。遇到这个先降低学习率到0.0005再把batch加大到至少8如果仍然崩溃就检查标签文件里的坐标有没有inf或非常大的值。这个坑后面专门展开。5. 鸡蛋缺陷训练最容易翻车的5个坑现象、原因、解决5.1 裂纹和污斑分不清类别不平衡的连锁反应现象模型对污斑的mAP能到0.8裂纹却只有0.2甚至预测时把裂纹全部识别成污斑。 原因数据集中污斑的实例数是裂纹的3倍以上模型天然倾向预测样本多的类别且裂纹在视觉上和污斑形状差异不大。 解决先做采样均衡。如果裂纹只有300个目标污斑有1000个可以对包含裂纹的图片做过采样比如每张裂纹图在训练时多重复两遍或者用mosaic增强让模型看到更多裂纹。更简单的是调整loss的类别权重YOLOv8支持cls参数但注意这要结合回调我一般先做过采样效果不够再动loss。5.2 小目标检测失效缺陷在整图里占比太小现象验证集上裂纹的mAP50-95只有0.05而mAP50有0.4说明框的位置没问题但IoU偏差太大预测框和真实框重叠度不够。 原因生产线拍到的蛋壳裂纹往往只有30x8像素整图是640x640目标归一化后不到0.05x0.01属于典型小目标。YOLOv8下采样到20x20的特征图时这个小目标可能只剩1个像素。 解决把输入分辨率从640提到1280代价是显存翻倍2077张图其实不算多建议先从imgsz960试如果显存不够就做滑窗切图把每张图切成4块再训练缺陷被放大了一倍。切图时注意边界框跨切分线的情况我一般让相邻切块有50像素重叠避免裂纹被切断。5.3 yolov8训练中bn崩溃loss变NaN、mAP为0现象训练到第几个epoch时box_loss突然变成nan后续所有指标归零。这个坑在用小batch和默认学习率训练时特别常见。 原因2077张图的batch如果设为4BN层的统计量颠簸很大加上裂纹样本少梯度局部出现极端值触发数值不稳定也就是“yolo训练中bn崩溃”。 解决先确认是不是标签异常把标签文件全扫一遍过滤掉坐标大于1或小于0的。然后把batch至少提到8或16batch太小就用batch8 imgsz640组合。再不行就把optimizer换成AdamW并设置学习率0.0005AdamW对极端梯度更钝一些。如果上述都不行就加载预训练模型不要从随机初始化开始尤其你只有几千张图从头训练很容易数值爆炸。5.4 标注边界框偏差xml里框宽高为0或越界现象图像能加载loss也能降但可视化验证图片时发现很多框是歪的或者完全没有覆盖缺陷。 原因原始标注工具导出时可能把xmaxxmin的线状目标也算进标注或者size字段和实际图片不一致。我在一个数据集里遇到过xmin: 222.40这种小数转换时是没问题但如果XML里的宽高比真实图大很多归一化坐标会整体偏小。 解决转换脚本里加上过滤逻辑我已经在3.1里实现了。另外在3.2的可视化校验之后再补充一个更严格的校验检查每个TXT文件最后一行是否换行符没有换行符会导致YOLO读最后一行数据不完整。用wc -l快速看行数和XML里object数量对照不一致立刻查。5.5 数据量不足硬上大模型过拟合与欠拟合的平衡现象训练集loss降到0.1验证集loss还停在0.5且yolo混淆矩阵里对角线不明显非对角线元素多。 原因2077张图只够表达一个中等的特征空间但你用了yolov8x或者不加载预训练直接随机初始化模型参数远多于有效信息它只能死记训练集。 解决先换yolov8n或yolov8s参数少没那么容易过拟合。然后冻结主干前10层训练让前几层保留预训练提取的通用纹理特征只训练后面检测头。最后加正则化比如weight_decay0.0005和dropout0.1。最重要的一点是2077张图只适合做“预设可行性”实验别指望它让你上线。你有两条路一条是继续采样扩充数据另一条是做生成式数据增强对裂纹区域做仿射变换模拟不同姿态。6. 验证模型不是看一眼mAP就完事混淆矩阵、PR曲线与数据扩充6.1 用YOLOv8自带的验证命令拿到关键指标训练完成后runs/egg_defect/weights/best.pt就是你的模型。先用标准命令验证yolo detect val \ modelruns/egg_defect/weights/best.pt \ dataegg_defect.yaml \ imgsz640 \ conf0.25 \ iou0.45这会在runs/egg_defect/val下生成confusion_matrix.png和PR_curve.png。重点看混淆矩阵的每一行是不是归一化的如果裂纹那一行有30%被识别成污斑说明两类特征没分开要考虑把这两个类别在语义上重新定义比如把“细长形”归为裂纹“块状”归为污斑。PR曲线则关注召回率0.8处的精确率在这个点如果精确率掉到0.5以下说明模型为了召回漏检丢了很多误检产线场景要权衡。6.2 从2077张到生产数据扩充策略与闭环2077张只是起点。我自己的经验是先把这个模型跑在一条简易的传送带视频上把视频回放的每一帧丢给模型同时用conf0.1把所有疑似缺陷的帧保存下来人工筛掉误检再把这些“难例”重新标注后加入训练集。这个过程是数据扩充里性价比最高的——不需要你额外拍图只是把模型自己的错误拿来喂回模型。另一个技巧是对已有的裂纹标注做形态学增强比如对标注区域做随机旋转±15度、缩放0.8倍把它贴到另外一张健康蛋壳图上拼出合成样本这种copy-paste增强对小目标缺陷特别有效能短期把裂纹实例数翻倍。验证模型时我习惯把conf从0.25降到0.1看一次预测结果因为产线漏检的代价远大于误检即使牺牲一点精确率也要优先保召回。最后提一句任何数据集都不可能一次满足所有产线光照、蛋壳颜色、传送带速度的条件你在实验室里跑通的模型放到现场前要拿真实产线的视频重新做一次domain adaptation。这么多年下来我最大的教训就是别在一份2077张的数据集上追求极限精度把它当作打通流程的“冷启动”然后尽快建立自己的数据回流闭环那才是模型能长期用的关键。希望这些踩坑记录能帮你少走一段弯路也让这份数据集真正发挥出它该有的价值。本文还有配套的精品资源点击获取
返回列表