
简介这套鸡蛋目标检测方案基于YOLOv8实现配套完整鸡蛋数据集与PyQt图形界面适合刚接触目标检测的初学者、算法调参人员以及需要快速落地检测Demo的开发者。包内数据集已划分好train、val、test目录并配置好data.yamlYOLOv5、YOLOv7、YOLOv8等算法均可直接读取训练。资源共1023个文件压缩包约84MB主要包含py训练/推理脚本、jpg标注图片、txt与xml标注文件、yaml配置以及少量pt权重、ui界面文件和mp4演示视频目录规范便于查找。已有186人学习浏览可用于复现鸡蛋检测流程、理解数据组织方式、二次开发自己的检测界面。附有参考博客链接方便结合博文对照实验结果和数据集详情节省从零整理数据与调试环境的时间。1. 从一枚鸡蛋开始yolov8、数据集与pyqt界面各自扮演什么角色在产线质检、养殖场计数和农产品分拣这些真实场景里鸡蛋目标检测往往比想象中更难蛋壳颜色浅、表面反光、相互堆叠遮挡加上摄像头角度和光照变化频繁传统图像处理做的边缘检测和阈值分割几乎一碰就碎。深度学习目标检测的解法是把这个问题拆成三个独立但连贯的部分——yolov8作为模型负责从图像里定位并分类出“鸡蛋”数据集负责给模型提供“鸡蛋长什么样”的监督信号pyqt界面则负责把模型的输出变成普通人能直接操作的桌面应用。这三件事缺一不可没有数据集yolov8只能拿预训练权重做通用检测识别不了你自己现场的那批鸡蛋没有pyqt界面模型就算跑通了也只能在命令行里输出坐标框没法给产线工人用。本文按“数据准备 → 模型训练 → 界面部署”的顺序走一遍完整落地路径中间给可直接复制的命令、参数表和排错思路适合已经从YOLOv5入门、想转yolov8做具体项目的开发者也适合刚开始接触yolov8训练自己数据集、对pyqt封装还不熟的工程师。2. 用yolov8训练自己的鸡蛋数据集环境、标注与配置2.1 为什么选yolov8而不是继续用yolov5yolov8在2023年发布后快速成为目标检测项目的主流选择不是因为它的名字比yolov5新而是因为几个在实际训练里能感受到的改动。模型的Backbone与Neck部分引入了C2f结构相比yolov5的C3模块多了更密集的梯度分支连接简单说就是让特征在层与层之间流动时丢失更少小目标检测能力因此增强——这对鸡蛋这种尺寸不大、又经常互相遮挡的目标是实打实的收益。Head部分从yolov5的耦合检测头改成了Decoupled-Head分类和回归分支分开计算收敛速度更快在相同epoch数下通常能拿到更高的mAP。另一个容易被忽略的实际原因是yolov8的仓库把训练、验证、导出、推理全部统一成一条yolo命令行项目从开始到交付只需要这一个工具链不需要像yolov5那样在detect.py和train.py之间反复切换。yolov8还直接内置了mAP计算、混淆矩阵、损失曲线图、F1曲线等可视化输出训练结束后自动在runs/目录下生成省去自己写评估脚本的时间。2.1.1 初步验证确认你的机器能不能跑yolov8训练yolov8不一定非要GPU但鸡蛋检测这种小目标任务用CPU训练会非常痛苦。一个常规的鸡蛋数据集按1000张图、每张图5到20个标注框来算用GTX 1660 Ti这个级别的显卡训练300个epoch大约需要3到5小时用CPU的话这个时间要翻10倍以上。先跑下面这段代码确认环境python -c import torch;print(torch.__version__, torch.cuda.is_available())输出里torch.cuda.is_available()显示True才说明GPU驱动和CUDA版本对齐了。如果显示False去NVIDIA官网查一下显卡对应的CUDA版本然后用匹配的CUDA版本重新安装pytorch。常见坑是这样pytorch默认安装的是CPU版因为pip在找不到nvidia相关包时会静默降级。安装命令要明确指定cu121或对应版本conda create -n yolov8 python3.10 -y conda activate yolov8 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralyticsultralytics这个包是yolov8的官方实现训练、推理、导出功能全在里面。装完后跑一次yolo predict下载预训练权重顺便验证整个链路的可用性。2.2 标注数据集用labelImg还是labelme做目标检测标注最常用的两个工具是labelImg和labelme。区别在于labelImg输出的是YOLO格式的txt文件每行一个目标格式是“类别id 中心点x 中心点y 宽度 高度”坐标全部归一化到0到1之间labelme输出的是JSON格式的多边形坐标适合做分割用在检测上还需要额外转换。鸡蛋检测只需要矩形框所以用labelImg就行。安装labelImg有两种方式直接pip装或者用GitHub源码启动。pip版本经常遇到Qt插件加载失败的问题源码方式更稳pip install PyQt5 lxml git clone https://github.com/HumanSignal/labelImg.git cd labelImg python labelImg.py打开软件后把默认的PascalVOC格式改成YOLO格式这个步骤很关键。在界面左侧点击“PascalVOC”按钮切换到“YOLO”然后在Change Save Dir里指定标签保存目录再把Open Dir指向图片目录。标注的时候用W键画框A和D键切换上一张和下一张图片快捷键熟悉之后标注效率能接近每张图30秒。标注时的两个注意事项直接决定后期模型上限第一所有鸡蛋都要标包括被遮挡的部分。yolov8训练时会通过数据增强引入随机裁剪和透视变换如果被遮挡的鸡蛋不标模型学到的特征就是“只有露出来的才是鸡蛋”换一个堆叠更严重的批次就崩了。第二类别id从0开始编号并且要和之后的data.yaml文件完全对应。比如只有鸡蛋这一类那标注文件里所有行都是0 0.5 0.5 0.2 0.2这种含义的结构。2.2.1 目录结构coco格式是yolov8的默认前菜yolov8的训练数据目录结构有固定要求推荐直接按下面的结构组织egg_dataset/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 └── labels/ ├── train/ # 训练集标注txt └── val/ # 验证集标注txt图片和标注文件的命名要完全一致比如IMG_001.jpg对应IMG_001.txt。图片推荐统一用jpg格式png格式虽然yolov8支持但标注工具在保存时偶尔会记录RGBA通道信息训练时读取会出现通道数不一致的报错。val集的比例按总数据量走500张以下取15%到20%1000张以上取10%左右。别按固定数字分不然数据量小的时候验证集可能只有几十张图mAP波动会很大。标注完成后在egg_dataset同级的目录放一个data.yaml内容如下path: /absolute/path/to/egg_dataset # 改成你自己的实际路径 train: images/train val: images/val nc: 1 names: [egg]path字段这里建议写绝对路径。yolov8在相对路径处理上对工作目录很敏感如果你在别的目录下启动训练命令相对路径会指向错误的位置报错信息还特别暧昧只会说AssertionError: train: No images in xxx。这种问题排查起来浪费时间直接用绝对路径最省事。2.3 数据质量检查训练前必做的三步标注完成不等于数据可用。训练前把这三件事跑一遍能避免训练到一半才发现数据问题的尴尬。第一步检查标注框是否真的贴合目标。用一段小脚本把标注框画到原图上随机挑20到30张图人工过目import cv2 img cv2.imread(IMG_001.jpg) with open(IMG_001.txt) as f: lines f.readlines() h, w img.shape[:2] for line in lines: cls, x, y, bw, bh map(float, line.split()) x1 int((x - bw / 2) * w) y1 int((y - bh / 2) * h) x2 int((x bw / 2) * w) y2 int((y bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check.jpg, img)这段代码把归一化的坐标换算回像素坐标后画框。重点看两个地方框是否明显超出鸡蛋边缘重叠严重的区域是否漏标了目标。这一步发现的问题比任何模型调参都影响最终精度。第二步检查标注文件的类别id是否越界。清洗手法是逐行读取txt文件find labels -name *.txt | xargs awk {if ($1 0) print $0} | head如果输出有内容说明标注里出现了大于等于1的类别id而data.yaml里nc: 1只接受0号类别。这种情况训练时模型会报类别索引错误。第三步删除无标注的图片。很多标注软件允许跳过某些图不标这些图留在训练集里相当于纯噪声会让模型把背景也学进去。过滤方式for f in images/train/*.jpg; do base$(basename $f .jpg) if [ ! -f labels/train/$base.txt ]; then rm $f fi done这条脚本配合yolov8训练自己的数据集这个高频需求场景可以再提炼一个原则无效图片删除的优先级高于补充新图。50张干净图片的数据集效果往往好过200张带噪声的图片。3. yolov8目标检测模型训练命令、参数与效果验证3.1 发起训练最小可用命令与生效逻辑数据准备好了训练的命令极其简单yolo train modelyolov8s.pt data/path/to/egg_dataset/data.yaml epochs300 imgsz640 batch16逐行解释关键参数modelyolov8s.pt表示从yolov8s这个预训练权重开始继续训练而不是从零初始化。迁移学习在这里的意义是让模型复用coco数据集里学到的通用视觉特征——边缘、纹理、颜色分布等低层特征对鸡蛋同样适用只要模型原本下采样后的特征图能覆盖鸡蛋所在区域。imgsz640是训练时的输入分辨率鸡蛋目标如果普遍小于32×32像素建议改成imgsz1280但显存占用会增大很多需要同步减小batch。batch的默认值是16如果你的显卡只有8GB显存大概率显存溢出。NVIDIA显卡可以用nvidia-smi看显存占用报错信息里的CUDA out of memory非常直白。调小batch到8或4即可代价是训练步数变多但最终精度差别不大。3.1.1 训练必调参数对照表参数默认值建议值说明model无yolov8s.pts版是精度与速度的平衡点m版本精度略高但推理更慢epochs100300鸡蛋数据集通常200到300轮收敛100轮mAP偏低imgsz640640或1280小目标密集时用1280但推理端也要同步batch168或16按显存调整GTX 1660 Ti用8安全patience5050验证集mAP连续50轮不提升则提前停止workers84Windows下超过4容易出DataLoader报错device00指定GPU编号多卡用0,1,2,3pretrainedTrueTrue强烈建议保留从零训练需要数据量翻倍其中patience参数值得单独说mAP连续patience轮不涨就自动终止训练这个机制在白嫖Google Colab这类有时限的环境里能保住训练成果。但鸡蛋数据集如果目标较小mAP曲线提升很慢patience设40到60是安全范围设太小会在模型还没有完全收敛时就被掐断。3.2 训练输出解读从runs目录到损失函数曲线训练结束后所有输出都在runs/detect/train/下第二次训练是train2以此类推。目录里最值得关注的文件是results.png和weights/。results.png分两列展示多条曲线训练损失box_loss、cls_loss、dfl_loss、验证损失、精度Precision、召回率Recall、mAP50、mAP50-95。判断模型是否正常收敛有两条标准线可以参考训练损失和验证损失都在下降且趋于平缓说明没有欠拟合验证损失在某个点之后反弹上升而训练损失继续下降就是过拟合信号。过拟合在鸡蛋数据集上的典型表现是mAP50值还能继续涨但mAP50-95反而回落说明模型开始死记硬背训练图的具体背景泛化能力变差。缓解办法首选增加数据量其次是调大hsv_h、hsv_s、flipud这几个增强参数的值让模型看到更多变体。weights/目录下有两个权重文件best.pt是验证集mAP最高的一轮权重last.pt是最后一轮权重。部署和评估都用best.pt这是约定俗成的规矩。3.2.1 验证模型效果用mAP说话验证集上的评估命令yolo val modelruns/detect/train/weights/best.pt data/path/to/egg_dataset/data.yaml执行结束时终端会打印一串指标。鸡蛋检测这个场景正常的参考线是mAP50大于0.9算优秀0.85到0.9可以接受低于0.85就要回到数据层面找问题。mAP50-95一般比mAP50低10到15个百分点属于正常现象这个指标对框的位置精度非常敏感鸡蛋这种近似椭圆的物体本身形状有变化mAP50-95在0.7以上已经不错。如果mAP50不错但mAP50-95偏低优先检查标注框是否普遍比实际鸡蛋大一圈。很多标注员习惯在鸡蛋外留一圈安全边距yolov8的anchor匹配机制对这种标注偏差比较敏感修正标注后再训练往往有立竿见影的效果。yolov8本身没有anchor参数需要调因为它是anchor-free的设计模型自己学习目标的几何特征所以标注质量对结果的影响被放大了。3.3 运行时有主见的推理命令训练和验证通过后单张图片推理yolo predict modelruns/detect/train/weights/best.pt source/path/to/test_image.jpg conf0.25conf0.25是置信度阈值低于这个值的结果会被过滤掉。产线场景建议设0.5以上因为误检一个不是鸡蛋的目标比漏检一个鸡蛋后果更严重数据集本身质量高的话0.3到0.35之间能找到更多被遮挡的鸡蛋。摄像头实时推理用source0默认调起编号为0的摄像头。这个模式适合现场快速验证模型在真实光线下的表现但别指望它直接当产线系统用——yolov8的原生推理在普通PC上处理单路摄像头没问题多路或需要录制的场景还是要走后面的pyqt方案。4. 用PyQt做鸡蛋检测界面架构、代码与GPU推理优化4.1 界面骨架线程分离是唯一正确做法PyQt界面不能简单地在按钮回调里直接跑模型推理因为推理是阻塞操作单帧检测在GPU上要30到80毫秒在CPU上要1到3秒UI会直接卡死。标准的做法是把推理放到工作线程里主线程只负责刷新界面。下面的代码展示了这个结构import sys import cv2 from PyQt5.QtCore import QThread, pyqtSignal, Qt from PyQt5.QtWidgets import QMainWindow, QLabel, QPushButton, QApplication, QFileDialog from ultralytics import YOLO class DetectThread(QThread): frame_ready pyqtSignal(object, list) # 原始帧 检测结果列表 def __init__(self): super().__init__() self.model None self.cap None self.running False def load_model(self, weights_path): self.model YOLO(weights_path) def run(self): self.running True while self.running: ok, frame self.cap.read() if not ok: break results self.model(frame, verboseFalse) self.frame_ready.emit(frame, results[0].boxes) def stop(self): self.running False信号槽机制是这个结构的关键子线程持有模型每拿到一帧就跑推理然后通过frame_ready信号把结果发回主线程。这里必须把verboseFalse传进self.model()否则每帧都会向终端打印一次推理日志连续运行个把小时终端文件会膨胀到几百MB。frame_ready信号携带了两个参数原始帧和检测结果。主线程只负责把结果画上去再刷新QLabel这样即使推理变慢界面也只是刷新慢一点不会无响应崩溃。4.2 主窗口逻辑加载模型、选图像还是摄像头class MainWindow(QMainWindow): def __init__(self): super().__init__() self.label QLabel(self) self.label.resize(960, 640) self.btn_load QPushButton(加载模型, self) self.btn_img QPushButton(检测图片, self) self.btn_cam QPushButton(摄像头检测, self) self.thread DetectThread() self.thread.frame_ready.connect(self.update_frame) def update_frame(self, frame, boxes): for box in boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) conf float(box.conf[0]) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fegg:{conf:.2f}, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch frame.shape bytes_per_line ch * w img QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(img).scaled( self.label.size(), Qt.KeepAspectRatio))摄像头的打开放哪取决于你要检测本地视频流还是网络摄像头。本地摄像头用cv2.VideoCapture(0)网络RTSP流把0替换成rtsp://ip:port/stream。ctx需要注意RTSP流如果断线重连cap.read()会一直返回False要在循环里加重连逻辑否则线程会悄无声息地退出。box.xyxy[0]返回的是tensor对象必须用.tolist()转成Python整数列表否则cv2.rectangle会报类型错误。这是ultralytics新版本接口和旧教程差异最大的坑之一很多网上代码写的box.xyxy[0][0]取数组元素的方式在新版本里已经无效。4.3 把pyqt界面工具链落成成品的三步界面在开发机上跑通只能算第一步交付给现场使用还需要处理模型体积、运行依赖和并发问题。按惯例我会把这三步走完再算项目验收。第一步把PyTorch权重转成ONNX格式推理速度和兼容性都上一个台阶yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640从这一步开始在pyqt界面里加载ONNX模型需要额外引入onnxruntimepip install onnxruntime-gpuONNX模型的推理代码改动极小把YOLO(weights_path)换成YOLO(best.onnx)即可ultralytics内部会自动选择onnxruntime后端。这一步带来的收益是推理帧率提升30%到50%取决于显卡和驱动版本并且整个推理过程不再依赖CUDA和pytorch部署机器的环境问题少一大半。注意GPU版onnxruntime需要机器上的CUDA、cuDNN版本匹配否则运行时报DLL加载失败。如果现场机器不统一可以用CPU版onnxruntimeGTX 1660 Ti上CPU推理单帧大约400毫秒但胜在零依赖。第二步把pyqt应用和模型文件用PyInstaller打包成exepip install pyinstaller pyinstaller -F -w --clean main.py-w参数禁止控制台窗口弹出-F把所有依赖打进一个exe。打包后检查dist/main.exe的大小一般会到500MB以上因为opencv和pytorch基础库被完整带进来了。如果换用onnxruntime可以控制在200MB以内。现场机器再装一遍显卡驱动就能跑。第三步加一个最朴素也最有效的视觉自检在界面上保留最近20帧的检测结果统计置信度分布的抖动程度。如果20帧里同一位置的鸡蛋有一半的帧没检出说明模型置信度刚好卡在阈值边缘。此时不是调界面程序而是回到训练任务里降低conf阈值或者用更多角度和光照的数据重新训练一轮。这个启发式检查在产线稳定性要求高的场景里比看单帧mAP直观得多。本文还有配套的精品资源点击获取