资讯动态

基于YOLOv8与PyQt5的竹签计数检测系统实现全流程解析

发布时间:2026/10/19 13:47:54 来源:尧图企业网站定制
竹签计数这件事听起来像是个“随便写个脚本就能搞定”的需求真做起来才知道坑有多深。单一背景散的竹签、斜着叠着插着的竹签、光线一变就发亮反光的竹签每一类都是对检测模型的考验。我这次用YOLOv8加PyQt5搭了一套完整的竹签计数检测系统从数据集制作、模型训练到桌面应用封装一步没跳最后在实拍环境下单帧检测能做到毫秒级响应计数准确率也稳定在可用的水平。这篇文章把整个过程拆开来讲适合那些想用YOLOv8落地某个具体小目标计数场景的开发者参考也适合刚接触目标检测想找一份完整工程范例练习的朋友。1. 项目整体思路为什么竹签计数是个值得认真做的“小难题”1.1 竹签计数的行业痛点与需求背景竹签这个东西在食品加工、烧烤配送、香薰生产等行业里是标准耗材。很多工厂每天要清点几十万根竹签过去全靠人工数数法无非是称重换算、按捆数估量或者让工人拿计数器一根根点。称重换算误差很大因为竹签含水量、粗细批次不同同样重量根数能差出百分之几人工点数倒是准可一根根点非常耗时间工人点久了眼睛也吃不消而且这种纯机械重复的岗位越来越难招人。我接触到的需求方之前试过机械计数装置就是那种让竹签从窄槽里一根根滚过去的光电计数器。这方案对规则排列的牙签有效果但竹签长度不一有时候两三根叠在一起就卡住了维护成本也不低。所以后来他们把目光转向视觉方案——用摄像头拍一张照片让程序自动数出画面里有多少根竹签。这个想法拆开看有两个核心问题一是怎么让程序准确找到每一根竹签的位置二是怎么把算法封装成一个普通人能直接使用的软件而不是让车间工人去敲命令行。1.2 方案选型为什么是YOLOv8而不是传统图像处理或更快部署的其他模型先说传统图像处理。竹签在图像里是细长条颜色和背景有差异理论上用阈值分割加连通域分析就能数。但实际素材里竹签经常交叉重叠、首尾相连一旦两根竹签碰在一起连通域就把它们合并成了一个目标误报漏报瞬间就上来了。你可以在二值化、形态学操作上调半天参数换一批光照条件就失效这种规则方案的鲁棒性扛不住实际生产环境。目标检测模型这边可选项其实不少。最早我考虑过Faster R-CNN检测精度确实高但推理速度太慢一张图几百毫秒实时性完全谈不上。也考虑过SSD速度还行但小目标检测能力偏弱而竹签在画面里恰恰属于细长的小目标。YOLOv8胜在几个点anchor-free设计对任意长宽比的目标更友好不需要像经典YOLO那样为细长目标手工调anchor模型体积从nano到xlarge覆盖范围广同一套代码可以按设备算力换规格官方生态成熟训练、导出、推理一条龙省去很多自造轮子的时间。实际对比下来yolov8s在竹签这类小目标上的精度已经够用速度和显存占用也合适所以最终确定用它。1.3 PyQt5在项目里的定位从算法到可用工具的最后一公里做算法的人容易忽略一件事模型跑通了不等于产品能用了。需求方是普通操作工人他们需要的是一个窗口程序能打开摄像头或者选一张图然后界面上直接显示每根竹签的框和总数最好还能一键导出统计报表。PyQt5就是干这个的。这个选择其实很务实。PyQt5在桌面端做界面开发的成本低和OpenCV配合顺畅QImage和numpy数组之间可以直接互转显示视频流也方便。当然也可以选Tkinter或者Web前端但Tkinter做出来的界面比较简陋拖拽布局和展示控件都费劲Web方案还得额外起服务、处理浏览器兼容问题对车间环境来说反而是负担。所以PyQt5作为本地离线工具既合适又稳妥。2. 数据准备竹签计数检测项目的地基工程2.1 数据采集拍出能用的竹签素材有哪些门道数据集的质量直接决定模型上限这一步别想省。我最初的采集思路是“多拍总没错”结果样张里大量图片是重复角度、重复摆放方式信息量很低还有些图片严重过曝竹签和背景几乎融为一体。后来我反思了采集流程归结出几个对竹签场景特别重要的点。首先是相机选择。条件允许的话优先用工业相机全局曝光能避免果冻效应分辨率建议500万像素起步这样单根竹签在画面里能占到足够多的像素。没有工业相机的话手机摄像头其实也能顶一阵但要注意固定机位避免手持抖动导致的模糊。其次是拍摄角度。竹签是细长圆柱体正上方垂直俯拍时竹签之间容易互相遮挡45度斜拍能看到侧面轮廓但投影会变短。我实测下来约60度俯角是比较好用的单根竹签的轮廓完整清晰交叉关系也容易辨认模型学到的特征更稳定。然后是光照。竹签表面有光泽强光下会形成高光反射带甚至产生反光斑块这会干扰特征提取。用柔光箱或者漫射板把光源打匀让竹签表面呈现均匀的哑光质感检测精度立刻上一个台阶。我试过用普通LED灯直射和加了柔光板两种方案模型的mAP50差了将近4个百分点所以这一条值得专门写出来提醒。2.2 标注规范细长目标的标注策略与常见错误数据准备好之后进入标注环节。竹签目标细长有的长宽比能达到10:1甚至更高标注时最容易犯的错误是“为了让框更好看”把框放大一圈。这么做看似无伤大雅但对YOLO这类回归模型来说框边缘贴不紧会直接导致定位精度下降尤其两个目标挨得很近的时候扩大的框会让回归目标变得模糊。我的建议是标注框尽量贴近竹签的物理边界宁紧勿松。遮挡是另一个麻烦两根竹签交叉叠在一起露出来的部分占目标主体被挡住的一小截要不要标完整我的做法是标被遮挡目标时框着整个竹签的实际范围哪怕有一部分被挡住。这样模型在训练时能学到“即使局部不可见完整目标尺寸也是可推理的”推理阶段处理轻微遮挡会稳很多。标注工具用labelimg足够输出格式直接选YOLO格式每个目标一行类别ID、中心点x、中心点y、框宽w、框高h都是归一化坐标。我踩过的一个细节坑是标注文件里的坐标精度。有的标注工具保存时只保留两位小数对竹签这种细长目标来说归一化后宽度可能只有0.02这个量级两位小数四舍五入一下误差就到百分之几了。所以在写标注配置时我强制保存精度到6位小数这一个设置能避免大量定位误差。2.3 数据增强哪些增强对竹签有效哪些会帮倒忙数据增强是提升模型泛化能力的有效手段但竹签这个场景有自己的特殊性盲目放大增强强度反而会坑。有效的增强包括轻度随机旋转±15度以内竹签摆放方向在生产线上是随机的多角度旋转能让模型适应不同朝向。亮度与对比度微调实际生产环境光照会有波动微调能让模型抵抗这类干扰。轻微模糊模拟镜头对焦不完全精准的情况一点点即可。马赛克增强YOLOv8自带增强我开着它让模型在训练时同时看四张图对小目标计数场景挺有帮助。帮倒忙的增强也要说明白。比如大幅度的错切变换和裁剪缩放会把竹签的形状特征破坏掉。竹签的判别关键就是“细长条”你如果把它裁成一小段或者错切成一个平行四边形模型学到的可能就不是“竹签”而是“某块细长亮色区域”了泛化方向就歪了。我一开始把增强强度拉得比较满训练出来的模型在测试集上对某些规则排列的竹签反而过拟合得很严重后来调低参数才回归正常。2.4 数据集划分与分布检查标注完成后按8:2划分训练集和验证集。划分的时候随机抽样不等于均分很多工具直接随机分结果训练集和验证集中聚集了大量同场景同摆放的图验证集评估出来的指标虚高。我的做法是先把图片按“拍摄批次”分组每个批次内部再按8:2拆分尽可能避免同批次图片出现在两边。这种做法在竹签场景里尤其重要因为同批次图片的光照、背景几乎一模一样模型很容易靠“背场景”刷高分数实际换新场景就露馅。划分完以后我还做了一件事检查每张图的标注目标数量分布。有些图一根竹签都没有这种负样本要保留少量能帮助模型学会“空场景不误报”但负样本太多会拉低正样本的学习效率一般控制在总样本的5%以内。全部检查和调整结束后再统计一下标注框的宽高分布确认没有异常的零宽零高标注才进入训练环节。3. YOLOv8训练全流程实录从环境搭建到模型导出3.1 环境搭建与依赖安装避坑训练环境这块我用的配置是稳定的组合Python 3.9、PyTorch 2.x、CUDA 11.8、Ultralytics库。这几个版本的组合经过验证是兼容的不建议追最新版因为新版本偶尔会引入一些莫名其妙的依赖冲突。有几个安装细节值得提醒。创建虚拟环境的时候用conda会省心很多直接指定Python版本不会污染系统的其他项目。装PyTorch记得用对应CUDA版本的index源如果图省事直接pip install torch装到的很可能是CPU版本训练速度能差几十倍。装完以后验证一下CUDA是否真的可用确保打印出来的CUDA版本和GPU实际驱动匹配。Ultralytics库的安装就简单了pip install ultralytics即可。如果只是做推理这个库开箱即用要做训练还需要确认安装了一个额外的依赖库用于高效的图像读取和增强没有它训练时数据加载会慢很多。这个小细节在官方文档里不算显眼但缺了它训练时间能拖长不少我建议安装后直接做一次小规模训练测试跑通再上全量数据。3.2 模型规格选择nano、small还是mediumYOLOv8提供了n、s、m、l、x五个档位的模型参数量和精度递增推理速度递减。竹签计数这个任务第一版我直接上了yolov8m想着精度越高越好结果在一张1080Ti显卡上训练显存吃紧推理速度也没达到实时要求。后来换成yolov8s精度只掉了0.8个百分点推理速度却快了一倍这种投入产出比显然更划算。这里有个经验值得分享小目标检测场景里模型参数量带来的收益通常不如输入分辨率来得实在。与其纠结从s升到m不如把训练分辨率从640提到1280。竹签细长分辨率上去以后单根竹签的边沿特征清晰很多模型学到的信息更多mAP提升肉眼可见。所以我的最终方案是yolov8s加1280输入两个因素组合下来效果和yolov8m加640差不多但推理速度优势明显。如果算力实在太紧张yolov8n也能跑只是对密集堆叠场景的召回率会差一些。我建议至少从s档起步除非部署环境真的很苛刻。3.3 关键训练参数解析与推荐配置训练参数对最终效果的影响比很多人想象得更大。我把我用的关键配置放出来并解释每个参数为什么这么设。yolo detect train datadataset/data.yaml modelyolov8s.pt epochs150 imgsz1280 batch8 lr00.01 lrf0.01 patience30 augmentTrueepochs设为150。竹签数据集不是特别大150轮足够模型充分收敛早停触发也不会浪费太多时间。imgsz设为1280。这是我在竹签项目里最重要的一个调整。损失点是显存batch只敢开到8但换来的是小目标检测精度的大幅提升。lr0设为0.01。YOLOv8默认就是0.01配合权重衰减可以稳住收敛过程。我在调参时试过把初始学习率调到0.02想加快收敛结果前几轮loss就出现了震荡后来又退回默认值。patience设为30。早停机制在连续30轮验证集指标无改善时停止训练防止无效的重复训练浪费算力。关闭了部分强增强参数避免竹签形态被过度破坏。具体来说我把mosaic关小了一些让模型在后期训练中看到更多完整目标形态。3.4 训练过程曲线解读与常见现象训练启动之后ultralytics会在终端打印每轮的损失值和指标训练结束后还会生成results.csv和一堆趋势图。读这些指标的思路比很多人想的简单。重点关注四个曲线box_loss边界框回归损失训练过程中应该持续下降并趋于平稳。如果震荡明显多半是学习率设置有问题或者batch太小导致梯度不稳定。cls_loss分类损失竹签是单类别任务这个曲线通常降得很快一旦降到接近0就说明分类任务已经学透了。dfl_loss分布焦点损失影响边框边缘的精细定位。这个损失收敛得相对慢它的稳定程度直接和最终边界框的贴合精度挂钩。mAP50和mAP50-95验证集上的核心指标。mAP50通常能到0.9以上mAP50-95会因为目标尺寸小、长宽比极端而偏低这很正常不用因为50-95不高就焦虑。我训练到第90轮左右四个损失曲线都进入平台期早停机制在102轮触发了。最终模型在验证集上的mAP50达到了0.93这个精度已经足够投入实际使用。训练完成后把best.pt导出推理部署就靠它了。4. PyQt5界面集成从模型到可用工具4.1 界面功能规划与布局设计界面设计的第一原则是让车间工人不培训也能上手。所以我只保留三个核心操作区域左侧是功能控制区中间是图像显示区右侧是计数结果区。功能按钮只有三个“选择图片识别”“打开摄像头识别”“导出统计表”其他花哨功能一律不加。吃透需求之后会发现工人最关心的信息其实是“总数”这一批竹签够不够数。所以我在结果区把总数字体放大加粗放在最显眼的位置每根竹签的置信度分数反而折叠到详情列表里。这个优先级设计我用模拟用户测试了一下操作人员上手几乎没有学习成本。界面构建用的是PyQt5的基本控件QPushButton做按钮QLabel放图像QTableWidget显示识别结果明细QComboBox做摄像头选择。布局使用QHBoxLayout和QVBoxLayout嵌套组合配合setStretch控制比例整个界面在不同分辨率屏幕上都不会变形。4.2 多线程推理架构不卡界面的核心PyQt5界面开发里最大的坑就是耗时操作直接跑在主线程。YOLO模型的推理如果直接放在按钮点击事件里点击的那一瞬间界面会整个冻结鼠标转圈体验极差。我用QThread做线程分离解决了这个问题。工作线程的职责是读取图像帧、执行模型推理、整理结果并发出信号。主界面收到信号后只负责更新UI控件。这样即使推理耗时达到几百毫秒界面依然能响应鼠标操作不会出现卡死假象。如果要做摄像头实时识别这个线程架构更是必不可少的——摄像头采集帧率30fps时主线程根本不可能同时处理视频流和UI刷新。信号传递的细节我踩过坑在自定义QThread里信号参数不要直接传numpy数组或者自定义对象跨线程传大对象容易出奇怪问题。我建议在信号里传图像的文件路径界面侧根据需要自己读取显示。传路径的做法天然规避了线程安全问题实测稳定多了。4.3 模型加载与推理链路实现模型推理这部分代码直接用Ultralytics的Python API就行底层已经封装得很干净。加载的时候我做了个小优化在模块加载阶段就完成模型初始化而不是每次识别前才加载模型。YOLOv8的权重加载和计算图初始化大概要花1-2秒如果放到每次推理循环里体验会非常拖沓。核心推理代码如下from ultralytics import YOLO model YOLO(weights/best.pt) def infer_frame(frame): results model.predict(frame, imgsz1280, conf0.25, verboseFalse) boxes results[0].boxes if boxes is None: return [], 0 count len(boxes) detections [] for box in boxes: x1, y1, x2, y2 map(int, box.xyxy[0]) conf float(box.conf[0]) detections.append((x1, y1, x2, y2, conf)) return detections, countconf阈值我设为0.25这是经过验证的平衡点。设太低会出现很多误检把背景里的反光条当成竹签设太高又容易漏掉部分被遮挡的目标。实际做项目时可以在界面里加一个滑动条让操作人员自行调节但我为了保持界面精简把它固定在了内部代码里只在配置文件中留了一个可选项。4.4 识别结果展示与统计导出识别完成后在图像显示区把每根竹签用绿色矩形框标出来左上角配上标签“stick”和置信度。这个绘制操作不用在繁忙的推理线程里做可以在主界面收到信号后直接用PIL或者OpenCV画。注意画图环节虽然耗时不多但如果一帧一帧画频率高的时候也会有压力我的做法是把绘制后的图像缓存成一帧图发到界面而不是允许界面逐条绘制。统计导出功能用pandas生成Excel报表。每行对应一根竹签编号、置信度、边界框坐标、来源图片文件名表格末尾是总计行。导出的逻辑很简单但有个小细节值得提文件名加时间戳比如“export_20250612_153022.xlsx”这样多次导出不会互相覆盖操作人员在追溯数据时也方便。摄像头模式我额外做了一件事从视频流里抽帧计数每隔5秒输出一次当前帧的识别结果并且把累计识别到的最大数值作为“本次清点结果”。这个设计是考虑竹签在输送带上连续移动的场景单帧识别会漏掉一部分正在进画面的目标取多帧最大值能显著提高统计准确度。5. 实战问题排查竹签检测中最容易踩的坑5.1 叠放密集场景漏检严重怎么优化密集竹签堆叠是最常见的难题尤其一次性拍摄几十根交错堆放的竹签时漏检率可能从5%直接跳到30%。我排查这个问题时先做了可视化诊断把推理结果画出来看漏检的目标特征发现基本都是边框互相重叠较大的目标。优化方向上第一步是调低NMS的IoU阈值让模型允许多个重叠框同时保留。YOLOv8的NMS阈值默认是0.5在密集场景下可以调到0.3左右。这个参数相当管用相当于告诉模型“两个目标哪怕高度重叠只要置信度都够高就要同时保留”。第二步是从数据层面解决补充更多高密度重叠的标注样本让模型见过更多这类形态。第三步是把输入分辨率从1280提到1536左右增加小目标像素信息。另外我还会在后期对“检测框数量”做一个后处理调节如果检测结果中出现了高置信度的大框且其面积恰好是相邻两个小框面积之和就把它拆分还原成两个目标。这个规则对极端叠放的场景很有帮助但要注意别误伤真正的单根粗签所以只在置信度都超过0.8的框之间生效。5.2 细长目标边界框不贴合定位偏差怎么破细长目标检测框容易出现一个问题框能框住目标但总是稍宽或者稍窄拉长了看就是对不齐。这个问题在竹签上的影响是计数时两根靠得近的竹签被检测框覆盖范围互相重叠导致NMS阶段错误合并。解决这个问题的核心是提高目标框回归的精细度。我有两个操作比较有效一个是在训练时降低dfl损失对应的权重系数让模型更重视边框的精确位置。另一个是彻底删掉数据里宽度标注畸形的样本——之前标注马虎时存在少量框只有两个像素宽的标注这类样本让模型的回归目标变得混乱。训练完的模型如果仍然出现系统性偏宽可以做一个简单的统计后校准收集一批测试图对比预测框宽度与实际标注框宽度算出比例偏差后在推理阶段按这个比例缩放预测框。虽然是偏“土”的方法但在实际项目中稳定可靠省去再训一轮模型的时间。5.3 PyQt5界面卡顿和视频掉帧问题界面卡顿最可能的原因是推理和处理图像绘制都在主线程执行。排查思路是先分清瓶颈在哪如果点击识别按钮后界面像死掉一样说明推理阻塞了主线程解决方案就是上一节说的QThread改造。如果视频显示时帧率忽高忽低说明图像转换或绘制开销太大。视频显示我额外用了两个优化。一是在摄像头读取线程里不显示原图只把推理结果画好后统一给界面这样界面侧只做一次绘图操作。二是控制界面刷新频率不必每帧都刷新QLabel固定每200毫秒刷新一次就足够流畅同时降低CPU和内存的压力。还有一个小细节QLabel显示大图时不要直接setPixmap原始大图先把图像用QImage.scaled缩放到界面显示区域大小再设置否则每帧都要传输几百KB甚至上MB的像素数据界面自然卡顿。缩放到QLabel的实际大小显示后内存占用和UI响应速度都会有明显改善。5.4 训练时loss不收敛或者收敛缓慢怎么定位训练loss不收敛我遇到过一次表现是box_loss震荡下降但cls_loss一路飙升。排查后发现原因出在数据标注上某个批次的图片标注类别ID写错了把竹签的2类标签混进去了一条。这种零散的错误样本会让分类损失始终降不下去。快速定位方式是用ultralytics自带的验证集评测跑一遍把错误预测的图片单独保存下来看。如果错误预测集中出现某个特定场景那大概率是训练集里该场景的数据不足或标注出错如果错误预测随机分布说明模型本身拟合能力不够考虑换更大模型或增加训练时间。还有一个训练不收敛的原因是学习率过高。我建议使用余弦退火学习率策略它能让学习率从初始值平滑衰减到接近0有效防止后期训练一直在最优解附近波动。调节的时候不要手软初始学习率从0.01往下调到0.005通常就能解决震荡问题。收尾实战后的几点心得整个项目从数据采集到界面封装前后花了两周左右的时间。我的感受是YOLOv8做竹签计数这类小目标检测任务核心瓶颈不在模型而在数据和工程细节。标注贴不贴边、分辨率够不够高、线程架构合不合理这些看似不起眼的点实际对最终效果的影响比选哪个预训练模型大得多。如果你也在做类似的小目标计数项目我建议先花时间把数据集做扎实把输入分辨率提上去再考虑调参和模型升级顺序对了后面的路会顺很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑