ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

YOLO鸡蛋品质分级实战:615张图从数据标注到模型训练

YOLO鸡蛋品质分级实战:615张图从数据标注到模型训练 简介面向YOLO系列目标检测实战的鸡蛋品质分级数据集专为训练血染蛋、棕色蛋、脏蛋、白蛋及钙沉积蛋等常见外观类别设计适合深度学习初学者与质检场景开发者直接用于模型训练、验证与效果对比。包内共1846个文件包含615张JPG原图、615个YOLO格式TXT标签、615个VOC格式XML标签及1个data.yaml配置文件标签格式规范且已按训练/验证需求拆分配合yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等主流版本均可快速上手。压缩包整体约25.41MB体积紧凑、目录结构清晰便于下载后立即开展实验。目前已有104人学习下载适合作为入门级分类检测练习数据或小型产线品质分选预研素材。借助两种标签格式读者既能省去手工标注时间又可根据偏好选用不同框架进行迁移训练与精度评估快速获得可视化的检测结果。1. 鸡蛋品质分级没你想的那么简单615 张图能解决的问题边界拿到这份《YOLO算法-鸡蛋品质分级数据集》第一反应是「615 张图能干什么」。说实话如果你想训一个直接上产线的蛋品分拣模型615 张肯定不够但如果你想搞懂 YOLO 从数据集到训练部署的完整链路或者做一个能区分白鸡蛋、棕色鸡蛋、脏鸡蛋、血染鸡蛋、钙沉积蛋的检测原型这份数据刚刚好。它最值钱的地方不是数量而是把标签和配置都做好了yolo 格式和 voc 格式的双份标注、train/valid/test 已划分、带 data.yaml拿到手不用做格式搬运直接进训练环节。我拆这份资源的时候最深的感受是——类别之间的边界比想象中模糊脏蛋和钙沉积蛋在特定光源下几乎长得一样这才是真正考验标注质量和调参功夫的地方。2. 数据集的目录结构与双标签格式yolo txt 和 voc xml 怎么对应2.1 拿到压缩包先看目录train / valid / test 怎么分的解压之后我先按工程习惯把目录树列了一遍。很多新手拿到数据集会直接打开 images 乱翻其实第一件事应该看整体结构确认训练集、验证集、测试集是怎么组织的标签文件跟图像文件是不是同名同路径。这份数据集的划分方式比较标准大概长这样dataset/ ├── train/ │ ├── images/ │ │ ├── img_0642_150.jpg │ │ ├── img_0642_151.jpg │ │ └── ... │ └── labels/ │ ├── img_0642_150.txt │ ├── img_0642_151.txt │ └── ... ├── valid/ │ ├── images/ │ └── labels/ ├── test/ │ ├── images/ │ └── labels/ ├── classes.txt └── data.yaml注意看它是不是严格按 images / labels 子目录组织的。yolov5 和 yolov8 的官方仓库默认就认这个结构训练时通过 data.yaml 指定 train 和 val 的 images 路径模型会自动去同级的 labels 目录里找同名 txt。这里有个容易踩的坑图像和标签必须同名后缀一个 .jpg 一个 .txt少了任何一个都会被当成无标签样本跳过。另外 classes.txt 是给 labelimg 这类标注工具用的每行一个类别名顺序决定类别索引。你在训练脚本里看到的 class id 0、1、2就是按这个文件的排序来的。这个顺序直接决定了模型的输出含义训练完做推理时如果发现「0 类目对不上」八成就是这里没对齐。2.2 yolo 格式和 voc 格式的换算关系不要只看后缀这份数据集的另一个卖点是同时提供了两种标签格式。yolo 格式的 txt 和 voc 格式的 xml 分开存文件名一致内容表达的是同一个框但坐标系完全不同。我见过不少人在这一步翻车用脚本读 xml 转换时把归一化坐标当像素坐标用结果画出来的框全偏。对比项yolo 格式txtvoc 格式xml坐标存储归一化比例值0~1绝对像素值框的表达class x_center y_center width heightxmin ymin xmax ymax文件形态每行一个目标对象每个图像一个独立 xml典型用途直接训练 yolo 系列模型适配 detectron2、Faster R-CNN 等框架yolo 格式里的 x_center、y_center 是中心点坐标占图片宽高的比例width 和 height 也是相对图片宽高的比例。voc 格式则是一组像素级的坐标比如 xmin213、ymin45、xmax368、ymax276。换算关系很简单x_center (xmin xmax) / 2 / 图片宽度width (xmax - xmin) / 图片宽度。如果你要自己写转换脚本这个公式是最核心的后面我会给完整代码。更隐蔽的问题是归一化坐标的精度。yolo 格式的坐标精度就是 txt 文件里的小数位数一般保留 6 位就够用但如果有人导出时只保留 2 位框的位置会有肉眼可见的偏差。我习惯拿到 txt 后直接用 OpenCV 画几个框出来看看这一步花两分钟能省后面训练完才发现标注问题的半天时间。2.3 data.yaml 拆解训练前先把配置读懂这份资源自带的 data.yaml 是它「可以直接训练」的关键。不管你是用 yolov5、yolov8 还是 yolov10data.yaml 的字段都是相通的只是读法略有差异。核心配置大概是这样的结构path: D:/egg_quality_dataset/dataset train: train/images val: valid/images test: test/images nc: 5 names: 0: blood_stained_egg 1: brown_egg 2: dirty_egg 3: white_egg 4: calcium_deposit_eggpath 指向数据集根目录train、val、test 填的是相对 train 的路径。nc 是类别数这份数据集是 5 类——血染鸡蛋、棕色鸡蛋、脏兮兮的鸡蛋、白鸡蛋、钙沉积蛋。names 列表的索引顺序必须和标签文件里的 class 索引完全一致否则训练的模型「以为」0 是白鸡蛋实际标签里 0 是血染蛋最终验证集的混淆矩阵会乱成一团。提示data.yaml 里的路径分隔符在 Windows 下容易出问题。建议写成正斜杠或者直接用相对路径不要用反斜杠否则 yolov8 在 Windows 上经常报「No labels found」或者路径拼接错误。这个坑我后面专门写了一条这是新手最容易卡住的地方之一。3. 标签格式互转voc 转 yolo 的脚本与 labelimg 打标的衔接3.1 为什么需要转格式一种标注工具只出一类标签这份数据集虽然两种格式都给全了但多数读者手里实际攒的数据往往是单一格式。用 labelimg 打标的时候默认导出的格式可以在 xmlPascalVOC和 txtYOLO之间切换但很多人打标时选了 voc 格式后续又想用 yolov8 训练就得做一次转换。反过来说有些人手里的数据只有 yolo 格式 txt想迁移到其它目标检测框架就需要反向转成 voc。还有一种更常见的情况你自己补标了一批图片用 labelimg 导出成了 xml想把它们合并进这份数据集的 yolo 体系里。只有搞清楚两种格式的换算逻辑才能写出不出错的合并脚本。我一般会把转换脚本单独保存一份因为数据会迭代每次新增图片都手动改坐标既不现实也容易出错。3.2 voc 转 yolo 的独立脚本归一化坐标换算下面这个脚本是我在拆类似数据集时常用的模板直接读 xml 目录逐个解析目标框写出同名 txt。它的功能就是把 voc 的像素坐标转换成 yolo 的归一化坐标同时做一下边界裁剪防止坐标越界。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, img_width, img_height, class_map): xml_path: 单个 voc 标注文件路径 img_width, img_height: 对应图像的宽高 class_map: {类别名: 类别id} 字典 返回: 转换后的 yolo 格式行列表 tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_map: print(f警告: 类别 {name} 不在 class_map 中跳过) continue 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) # 裁剪到图像边界防止 w/h 超出 0~1 范围 xmin max(0, min(xmin, img_width - 1)) xmax max(0, min(xmax, img_width - 1)) ymin max(0, min(ymin, img_height - 1)) ymax max(0, min(ymax, img_height - 1)) x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height lines.append(f{class_map[name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) return lines这段代码的核心是坐标换算那四行中心点坐标等于两个角点取平均后再除以图像宽高宽高直接用像素差除以图像宽高。注意裁剪边界的部分labelimg 偶尔会导出刚好贴边的框如果是 xmax 等于图片宽度归一化后 width 可能正好是 1.0极端情况下标注错位甚至超过 1.0训练时虽然不报错但 loss 会异常偏高。函数里的 img_width 和 img_height 参数需要从图像文件中读取推荐用 cv2.imread 或 PIL 获取不要从 xml 的 size 节点读因为有些标注工具的 size 节点写的是原始分辨率而图片已经被缩放过了。3.3 反向转换与批量处理yolo 转 voc 验证标签有没有问题很多教程只教你 voc 转 yolo但其实反向转换才是验证标签质量的利器。把 yolo txt 转回 xml然后用 labelimg 打开看一眼比直接看数字靠谱得多。反向转换其实就是把归一化坐标乘回图像宽高再把中心点形式变回 xmin/ymin/xmax/ymax。import os import cv2 def yolo_to_voc(txt_path, img_path, class_names, output_dir): txt_path: yolo 标签文件 img_path: 对应图像 class_names: 类别名列表索引即类别id img cv2.imread(img_path) h, w img.shape[:2] xml_name os.path.splitext(os.path.basename(txt_path))[0] .xml header fannotation filename{os.path.basename(img_path)}/filename size width{w}/width height{h}/height depth3/depth /size object # 每读一行标注生成一个 object 节点字符串 with open(txt_path, r) as f: obj_strs [] for line in f: parts line.strip().split() cls_id int(parts[0]) x_center float(parts[1]) * w y_center float(parts[2]) * h box_w float(parts[3]) * w box_h float(parts[4]) * h xmin int(max(0, x_center - box_w / 2)) ymin int(max(0, y_center - box_h / 2)) xmax int(min(w, x_center box_w / 2)) ymax int(min(h, y_center box_h / 2)) obj_strs.append(f name{class_names[cls_id]}/name bndbox xmin{xmin}/xmin ymin{ymin}/ymin xmax{xmax}/xmax ymax{ymax}/ymax /bndbox /object) with open(os.path.join(output_dir, xml_name), w) as f: f.write(header \n.join(obj_strs) \n/annotation)这里有个关键细节yolo 的坐标是相对比例乘回宽高后要取整。取整方向有讲究xmin 和 ymin 用 int() 向下取整xmax 和 ymax 也是 int() 但建议取整后再做一次 min 裁剪防止刚好等于宽高的越界。我刚开始写这类脚本时没做裁剪结果有个框的 xmax 比图像宽度大了 2 个像素labelimg 能打开但会提示坐标非法。这种小毛病不影响训练但会污染数据治标不如治本。4. 划分训练集与配置训练从 data.yaml 到 yolov8 命令行4.1 按比例重新划分数据集的脚本加数据时不打乱原结构这份资源自带的划分是固定的但你在实际使用中很可能往里面加自己的图片或者觉得原划分的验证集太小需要重新洗牌。数据划分这事看起来简单其实有个很容易被忽视的规则图像和标签必须同步移动图片进了 train对应的 txt 也必须进 train 的 labels 目录。用 shutil 移动文件的时候漏一个就多一个「无标签样本」。import os import random import shutil def split_dataset(image_root, label_root, target_root, train_ratio0.7, val_ratio0.2, seed42): image_root: 图像所在目录 label_root: txt 标签所在目录 target_root: 目标目录会生成 train/valid/test 子目录 train_ratio, val_ratio: 训练集、验证集比例 random.seed(seed) # 固定随机种子保证每次划分结果一致 images [f for f in os.listdir(image_root) if f.endswith(.jpg)] # 先按比例打乱再切成三段 random.shuffle(images) train_end int(len(images) * train_ratio) val_end train_end int(len(images) * val_ratio) splits { train: images[:train_end], valid: images[train_end:val_end], test: images[val_end:], } for split_name, img_list in splits.items(): img_dst os.path.join(target_root, split_name, images) lbl_dst os.path.join(target_root, split_name, labels) os.makedirs(img_dst, exist_okTrue) os.makedirs(lbl_dst, exist_okTrue) for img_name in img_list: label_name img_name.replace(.jpg, .txt) # 同步移动图像和标签缺标签的图片直接跳过并打印 src_img os.path.join(image_root, img_name) src_lbl os.path.join(label_root, label_name) if not os.path.exists(src_lbl): print(f警告: {img_name} 缺少标签已跳过) continue shutil.copy(src_img, os.path.join(img_dst, img_name)) shutil.copy(src_lbl, os.path.join(lbl_dst, label_name))脚本的重点在 random.seed(seed) 那一行。不固定随机种子的话每次运行划分结果都不一样一旦你在某个结果上训练出了不错的模型想复现就要重新划分一遍数据这很麻烦。而固定 seed 之后比如 seed42无论跑多少次分配到 train 里的图片都是同一批这为实验对比提供了可控性。提示默认的 7:2:1 比例只是经验值。如果验证集图片太少比如不足 50 张mAP 的波动会非常大一个类别多一张错标就能让指标掉好几个点。遇到这种情况建议把比例调成 8:1:1 或者 8.5:1.5:0优先保证验证集有足够的多样性。4.2 训练命令与参数解读yolov5 还是 yolov8n 还是 s数据就绪后训练命令本身并不复杂。以 yolov8 为例在项目根目录下跑一行命令即可但这里的参数选择直接影响训练效果和耗时尤其是 batch size 和 imgsz 的搭配跟你的显卡显存强相关不能照搬。# 训练 yolov8n输入尺寸 640训练 100 轮 yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ project./runs \ nameegg_quality_v8n # 训练 yolov5s如果代码仓库是 yolov5 就进目录跑 train.py python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 640 \ --epochs 100 \ --batch-size 16 \ --device 0yolov8n 是 nano 版本模型最小、速度最快显存占用大约 4~5GB适合先跑通流程yolov8s 是 small 版本精度更好但对显存要求更高。615 张图像的规模用 nano 版本就能训出像样的效果没必要上 large 或 xlarge容易过拟合。epochs 我用 100 起步配合早停机制——yolov8 默认会监控验证集 loss 并在连续多轮没有提升时提前结束训练。batch 大小如果显存不够优先降到 8而不是调小 imgsz因为 imgsz 从 640 降到 416 对检测精度的影响比 batch 减半更明显。如果你用的是这份数据集自带的 data.yamlclasses 数量已经写好了命令行里不需要再指定类别数。但注意 model 参数加载的预训练权重yolov8n.pt 的类别数是 COCO 的 80 类训练时会自动替换成你数据集的 5 类不需要手动改权重文件——这是 yolo 系列在代码层面处理好的事情新手不用担心但要知道它内部做的其实是「迁移学习保留特征提取层替换检测头」。4.3 验证与测试不只是看 mAP还要看 PR 曲线训练完成后很多人习惯只盯着终端输出的 mAP50 和 mAP50-95 两个数字。这两个指标确实重要但远远不够。类别层面的差异才是关键血染蛋和棕鸡蛋的颜色相近脏鸡蛋和钙沉积蛋的纹理相似这些易混类别的 AP 值会被整体 mAP 掩盖掉。我用这份数据集训练完第一件事是跑验证命令然后逐个看每个类别的 AP 值。# 使用训练好的权重跑验证集 yolo detect val \ datadata.yaml \ model./runs/egg_quality_v8n/weights/best.pt \ imgsz640 \ batch16 # 导出模型并用一张测试图做推理可视化 yolo predict \ model./runs/egg_quality_v8n/weights/best.pt \ source./test/images/img_0642_150.jpg \ conf0.25 \ saveTrueval 命令会在 runs 目录下生成混淆矩阵图和 PR 曲线图这两张图比 mAP 数字更能说明问题。混淆矩阵能直接告诉你模型把钙沉积蛋误判成了哪一类PR 曲线能看出哪个类别的置信度阈值需要单独调整。我一般会保留训练日志首次训练结束后对着混淆矩阵决定要不要给某个类别加权或者增加对应类别的数据增强。5. 避坑排查鸡蛋分级训练里最常见的五个问题5.1 类别索引错位class0 不是你想的那个类现象训练正常跑完验证集 mAP 看着还行但推理时发现模型把白鸡蛋标成了棕色鸡蛋所有的类别全部错一位。原因标签文件 txt 里的 class 索引和 data.yaml 的 names 列表顺序不一致。常见于自己补标数据时labelimg 的 classes.txt 顺序与数据集的 class_map 不相同。比如 labelimg 里 0 是 brown_egg而 data.yaml 里 0 是 blood_stained_egg。解决写一个比对脚本读 data.yaml 的 names 和 classes.txt 的内容确认顺序一致。不一致时统一以 data.yaml 为准重新生成标签文件把 txt 里第一列的类别 id 替换成正确索引。这个动作必须放在训练之前训练后发现问题再改标签就得重新训。5.2 路径分隔符和数据路径写错Windows 踩坑现象yolov8 训练时报「No labels found in train/images」或「Image not found」但打开目录看图片和标签都在。原因data.yaml 里 path 字段用了反斜杠 Windows 路径或者 train 字段写成了绝对路径。yolov8 在部分版本里对 Windows 路径拼接处理不完善反斜杠被当成转义符。解决data.yaml 里统一用正斜杠path 字段留空也行直接把 train 和 val 写成绝对路径的正斜杠形式。如果还报错检查数据集的目录名有没有中文或者空格中文路径在 OpenCV 读图阶段大概率出问题。5.3 钙沉积蛋和脏鸡蛋互相混淆损失函数与数据增强的选择现象loss 一直在降但验证集混淆矩阵显示 dirty_egg 和 calcium_deposit_egg 之间大量互相误判两个类别的 AP 都上不去。原因这两类鸡蛋的表面特征本身高度接近脏斑和钙沉积在灰度图像上几乎一样的纹理。数据量又少模型学不到稳定的区分特征。解决三个方向同时试。第一给这两个类别增加数据增强强度尤其对钙沉积蛋做轻微旋转和尺度变化让模型见过更多形态第二换用损失函数或调整类别权重yolov8 里可以按类别数量反比设置权重抑制大类、提升小类第三检查标注质量钙沉积蛋的框是否把整只鸡蛋白边都框进去了导致模型学到的是「区域大小」而不是「沉积纹理」。5.4 标框贴边导致 w/h 越界训练不报错但指标不对现象训练过程没有任何报错但训练结束后画出来的一些预测框明显偏移回看标注发现部分 txt 里的 width 或 height 超过 1.0。原因标注时框贴着图像边缘归一化过程中 xmax 等于图像宽度导致 width 恰好是 1.0更极端的情况下标注工具记录的是 xmax 大于图像宽度归一化后超过 1.0。yolo 的训练代码不校验坐标范围越界值直接参与 loss 计算。解决写一个清洗脚本遍历所有 txt把 x_center、y_center、width、height 裁剪到 [0, 1] 区间越界的按边界值截断然后重新保存。这个动作配合前面的 yolo_to_voc 反向转换再用 labelimg 抽查一遍基本能消灭此类问题。5.5 小目标漏检615 张图的规模决定的调优方向现象模型对正常大小的鸡蛋检测效果不错但图像边缘或被遮挡的鸡蛋经常漏检验证集上 recall 偏低。原因615 张图里小目标样本本来就少模型训练时没有见过足够多的边缘截断样本另一个原因是输入尺寸 imgsz640 时边缘小目标在下采样后只剩很少的特征像素。解决提高 imgsz 到 768 或 896看显存是否允许同时开启 mosaic 数据增强让模型在训练时看到更多不同尺度和位置的目标。如果显存不够就回到数据层面把图片边缘截断的鸡蛋单独挑出来做复制粘贴增强——这是 615 张这种小规模数据集最有效的提点手段。6. 进阶玩法训练前先做一次标签可视化检查6.1 用 Python 快速画框验证标签训练之前先跑一轮标签可视化这是我拆数据集的固定动作比训练完再发现标注错误划算得多。上面的 yolo_to_voc 转换本身能检查坐标合法性但视觉检查能发现更隐蔽的问题框包住了整颗蛋还是只包住了脏斑、类别标得对不对、有没有一颗蛋标了两个框。import cv2 def visualize_yolo_label(img_path, txt_path, class_names): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path, r) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) x_center float(parts[1]) * w y_center float(parts[2]) * h box_w float(parts[3]) * w box_h float(parts[4]) * h x1 int(x_center - box_w / 2) y1 int(y_center - box_h / 2) x2 int(x_center box_w / 2) y2 int(y_center box_h / 2) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) return img把训练集里随机抽 30 张图跑一遍这个函数存成视频或拼图扫一眼比任何数据统计都直观。vscode 的 yolo 插件也有标签预览功能但批量检查还是脚本方便。6.2 用 yolo val 的混淆矩阵定位易混类别训练结束后的 val 输出目录里会有一个 confusion_matrix.png这张图是调参的核心依据。看它比看 mAP 有用得多如果血染蛋和棕色鸡蛋大面积互混说明模型在颜色特征上已经到达区分极限这时候不是继续调参的问题而是要考虑要不要加彩色纹理特征辅助。我通常的做法是先看矩阵里哪两个类别互混最严重去 data 目录里把这两个类别的图片各翻一遍确认人眼能否区分。如果人眼都难分那就是数据问题优先补数据如果人眼能分而模型不行才去动损失函数和数据增强。从那以后我每次拿到新数据集第一件事都是先做标签可视化训练完先看混淆矩阵再谈指标和调参。这个习惯帮我避开了至少三次「训练跑完但模型只能记住训练集」的无用功。这份鸡蛋品质分级数据集的 615 张图不算多但正因为小反而逼着你把每一步做规范——标注验证、数据划分、类别权重这些环节如果跳过模型翻车只是早晚的事。希望帮到你。本文还有配套的精品资源点击获取
返回列表