首页 / 资讯中心 / 文章详情

基于YOLOv8与PyQt5的竹签计数检测系统实现

基于YOLOv8与PyQt5的竹签计数检测系统实现 ★ FEATURED ARTICLE
很多做餐饮供应链、食品加工、手工艺品代工的朋友大概率都经历过“数竹签”的噩梦。竹签这东西看着不起眼真要大数量清点时人工一根根数既慢又容易出错用秤称重换算数量又受竹签干湿度、粗细波动影响误差大得离谱。我这次尝试的是一套基于深度学习YOLOv8的竹签计数检测系统用目标检测模型对画面中的竹签逐根定位再通过PyQt5做一个可交互的可视化界面把“看到一根算一根”变成自动化的实时计数。整套系统跑下来单张图片内百根竹签的计数误差可以控制在个位数而且模型还能直接用在摄像头实时场景里。这篇文章就把整个项目的思路、数据集制作、模型训练、界面集成和踩坑记录完整写出来适合正在做物体计数类项目、想入手YOLOv8或者准备给检测模型套一个Python界面的朋友参考。1. 系统整体设计与技术选型思路做这类计数检测项目最容易犯的错误是拿到需求就直接开跑模型。我见过不少朋友把YOLOv8一顿下载随机拍了几张图丢进去训练结果自然是灾难性的。所以在动代码之前先把整体架构想清楚比什么都重要。1.1 为什么是YOLOv8而不是其他检测框架先说选择YOLOv8的理由。目标检测领域的方案很多比较早的Faster R-CNN精度不差但推理速度慢做实时摄像头场景很吃亏。SSD速度快但对小目标和细长物体的检测效果一般。YOLOv5本身很成熟不过YOLOv8在结构上做了几项关键升级更贴合竹签检测这种需求。竹签有一个很特殊的形态特点细长、长宽比极高一根烧烤竹签在1080P画面里可能只有几十像素宽却有几百像素长。传统Anchor-based方案对这种极端长宽比的目标很不友好需要设计大量不同尺寸的先验框才能覆盖。YOLOv8改成了Anchor-Free的解耦检测头直接预测目标中心和边框相当于天然避开了“竹签长宽比不好匹配预设锚框”的麻烦。再加上C2f模块的特征提取能力和多尺度融合竹签这类小目标的细节不容易在深层网络里丢干净。实际测试下来YOLOv8s在精度和速度的平衡上表现最好单张图推理大约在10到20毫秒量级放到PyQt5界面里流畅度完全够用。还有一个现实原因是生态成熟度。Ultralytics官方维护的代码库封装得很完整训练命令、验证脚本、模型导出、推理API一条龙对不擅长从零撸网络的人来说省了太多事。做计数系统核心难点本来就不在于复现网络结构而在于数据、调参和工程集成。提示如果项目对帧率要求极高比如每分钟几百件的高速产线可以优先尝试YOLOv8n或者导出为TensorRT加速如果计数精度优先且算力充裕YOLOv8m甚至YOLOv8l都能带来一定的召回率提升。1.2 为什么需要一个PyQt5可视化界面很多技术人觉得模型跑通了输出一个计数数字不就行了但在实际生产环境里完全不是这么回事。一线操作人员不可能去打开命令行敲Python脚本也不可能去解析JSON检测结果。他们要的是一张能直观看到“每根竹签都被红框框住”的界面一个能显示当前总数的面板一个能加载图片、打开摄像头的按钮。PyQt5相比其他GUI方案有几个不可替代的优势。第一它底层基于Qt控件丰富且稳定QLabel显示图像、QPushButton响应点击、QTableWidget展示统计信息都是开箱即用。第二PyQt5和OpenCV的配合非常顺畅图像数据经过OpenCV处理后转成QPixmap就能直接显示不需要额外的格式转换库。第三打包成exe在Windows环境部署很成熟车间工人双击就能运行不需要安装Python环境。界面设计上我采用的是左图右控件的经典布局左侧大区域用于显示原图和检测结果右侧放置功能按钮、计数显示面板和日志输出区。这样操作人员在看实时画面时注意力能集中在左侧右侧保持状态信息的稳定展示。界面不需要花哨但一定要信息清晰、逻辑顺手。1.3 系统架构与工作流程整个系统的数据流其实很简单可以概括为“输入端 - 模型推理 - 后处理 - 界面展示”四个环节。输入端支持三种数据来源单张图片、批量图片文件夹、USB摄像头实时视频流。统一通过OpenCV的接口读入图像再做预处理。预处理阶段主要就是调整图像尺寸适配YOLOv8模型输入的分辨率。模型推理部分加载训练好的权重文件对当前帧执行前向传播得到一组检测框、置信度和类别。后处理环节除了常规的NMS去重还要针对竹签计数做专门的逻辑比如设置置信度阈值、合并重复检测框。最后界面根据检测框数量刷新计数显示并把画好框的图像展示出来。这样分层设计的好处是每一块都能独立测试和替换。比如不想要摄像头了可以只改输入端模型效果不好可以直接换权重文件界面逻辑改动也不影响模型代码。实际开发时我发现这种清晰的模块划分让调试难度下降了很多出了问题能快速定位是数据问题、模型问题还是界面问题。2. 竹签计数数据集的制作是成败关键说句实在话竹签计数这种通用目标检测任务训练一个能看的模型不难难的是在真实场景里把计数误差压低。而决定成败的第一关不是模型结构是数据集质量。我前后迭代了三版数据集才把现场准确率从惨不忍睹拉到基本可用。2.1 采集场景设计核心是贴近真实第一次做数据集时我偷懒了拿一把竹签在光线均匀的白色背景前拍了几十张又简单做了些随机旋转和缩放增强训练出来的模型看起来精度很高验证集mAP到了0.95以上。结果一到实际车间场景就崩了现场是暖色调的黄色灯光背景是深色的操作台竹签堆叠方式和我的数据集完全不同漏检率一下飙到百分之二三十。后来我重新设计采集方案专门强调“场景覆盖度”。采集时至少覆盖以下几个维度不同背景深色操作台、浅色桌面、不锈钢台面、蓝色塑料筐不同光线明亮日光灯、车间侧光、阴影区域、夜间补光不同摆放形态整齐平行排列、散乱交叉、多层堆叠、部分压在纸箱里不同数量规模三五根的小撮也有上百根的密集摊开。一个场景至少拍几百张每个维度组合都尽量出现才能让模型真正学到竹签的本质特征而不是某个特定背景下的特征。有个容易被忽略的细节是采集分辨率。我一开始用手机拍的照片分辨率高但训练时统一缩放到640x640后竹签的细节被严重丢失。后来我调整策略在保证检测目标尽量大竹签单根在画面中不小于几十像素宽的前提下拍摄。如果你要检测的场景是整筐几百根竹签建议镜头离竹签近一些或者干脆训练时用1280x1280分辨率。2.2 标注规范一台竹签一个框数据标注我用的LabelImg工具输出为YOLO格式的txt文件。万事开头难但竹签标注的难点不在于工具操作而在于标准统一。我的标注规范说起来很简单每一根完整可见的竹签都用一个矩形框精确框住。但执行起来全是细节。比如一根竹签斜跨整幅画面框必须调整到恰好贴合整根竹签的斜向范围不能为了省事只框中间一段。比如竹签被其他竹签遮挡了三分之一只要主体可见依旧要标注不能漏。比如画面边缘毛刺一样的半根竹签露出超过一半也要标注。遇到场景中混有筷子、木棒这类易混淆物体时不标注因为这类物体不属于目标类别也顺便提升了模型抗干扰能力。标注框的贴合度直接影响计数准确率。框太松检测头学会了一个框包含多根竹签的概率就高框太紧又容易把一根竹签的头部或尾部裁掉导致特征学习不完整。实际体验下来框的四周留一点余量即可但余量一定要统一不要这张图贴得很紧那张图留一大片空白。标注完成后我做了严格的复核把单张图片的检测框数量重新数一遍和人工目测数量核对避免漏标。注意标注时如果场景中竹签数量特别多很容易标到一半就眼花漏掉。建议采用“从左上角往右下角逐行扫视”的方式标注并且每个批次标完就统计一下总数和已知数量比对。模型效果不好时首先检查标注数据的质量而不是急着调参。2.3 数据增强别把方向搞反了YOLOv8的训练管线自带增强策略包括Mosaic拼接、随机仿射变换、HSV色彩扰动、水平翻转等默认配置在大多数场景下都够用。但针对竹签计数这种密集目标场景增强策略要稍微调整。Mosaic增强本身很强大它把四张图拼接成一张让小模型能够看到更多上下文信息。但训练AI生成漂亮没问题一到挑战场景就必须关闭“合成的假象”带来的副作用。Mosaic里会大量出现竹签被拼接边缘切断的假象如果增强太强模型可能把截断的竹签当成完整竹签来学习。我的做法是把Mosaic关闭或把Mosaic概率从默认的1.0降到0.5让模型多关注真实完整的竹签形态。色彩增强方面我保留了HSV扰动因为不同车间灯光的色温差异确实很大需要模型对颜色变化有一定鲁棒性。但饱和度和明度的扰动幅度我没有给很大因为这会让浅色竹签在深色背景上的边缘特征被削弱。旋转增强我很谨慎给了一个较小的角度范围。竹签的摆放并不是完全无规律如果旋转过度让竹签竖起来训练而真实场景里都是横向平铺反而会产生域偏移。说白了增强的目的是模拟真实场景的变化范围不是制造现实中不存在的极端情况。训练前想想模型最后会在什么环境里服役增强策略就要围绕那个环境来做。3. YOLOv8模型训练全流程实操数据准备完毕终于来到模型训练的环节。这里记录的是我从零开始训练一个竹签检测模型的完整命令和参数取舍也包括我在多次迭代后得出的调参心得。3.1 训练环境准备我本机用的是GPU训练操作系统是Windows 10显卡显存8GB。核心依赖包括Python 3.9、PyTorch 2.0.1、CUDA 11.8、Ultralytics 8.0.xxx版本、OpenCV、PyQt5。在Windows环境安装Ultralytics很简单pip install ultralytics pip install pyqt5 opencv-python装好之后可以写一段快速验证代码确认CUDA可用import torch from ultralytics import YOLO print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果输出的cuda可用性是False大概率是PyTorch版本和CUDA驱动不匹配建议先去PyTorch官网用命令生成器选择对应版本重新安装。3.2 数据集配置与训练参数调优训练前需要把数据集整理成Ultralytics要求的目录结构datasets/bamboo/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/每个图片对应一个同名的txt标注文件内容是“类别 中心x 中心y 框宽 框高”的归一化数值。我最终训练集约800张图、标注实例总数接近25000个验证集约200张图。这个规模对单类别目标来说不算大但场景覆盖度一旦做起来效果明显比数量堆得多更管用。data.yaml文件写成这样train: datasets/bamboo/images/train val: datasets/bamboo/images/val nc: 1 names: [bamboo_skewer]训练命令我用的是yolo detect train datadatasets/bamboo/data.yaml \ modelyolov8s.pt \ epochs300 \ imgsz640 \ batch16 \ patience50 \ device0 \ projectbamboo_train \ nameexp1这里有几个参数值得展开说。预训练权重的选择我建议直接用yolov8s.pt。竹签本身的纹理特征和通用物体特征有不少共性迁移学习能让训练起点高很多特别是在数据量不算大的情况下。从零训练不仅收敛慢最终精度也往往不如带预训练的版本。epochs设为300是因为我发现竹签数据在前100轮训练时loss下降很快但到后期计数精度仍然有缓慢提升。patience设为50则是防止过拟合拖时间如果验证集指标连续50轮不提升就自动停止。imgz选640是8GB显存下batch16的安全选择如果想用1280分辨率我会建议batch降到4到8否则很容易显存爆炸。3.3 训练过程监控与效果评估训练过程中要重点关注两类指标loss曲线和验证集指标。Ultralytics会在每个epoch结束后打印精确率Precision、召回率Recall、mAP50和mAP50-95。在竹签计数场景中我评估模型的侧重点跟一般检测任务略微不同。对于通用检测mAP50-95是一个核心指标但计数任务真正要关心的是召回率。原因很简单漏检一根竹签意味着计数直接少一根而框的位置稍微不精确对计数的最终结果几乎没有影响。所以我训练时紧盯Recall这个数字要求验证集上的Recall至少达到0.95以上才算勉强具备现场部署的资格。有一次训练出来的模型Precision是0.98但Recall只有0.87。表面上看指标不错但现场一测每100根竹签漏掉十几根计数误差根本没法接受。后来我通过补充更多重叠场景数据、把置信度阈值从默认的0.25下调到0.1把Recall拉到了0.96计数误差才算可控。这也再次说明指标一定要结合实际业务场景来理解和取舍。训练完成后工作目录下会生成best.pt和last.pt两个权重文件。best.pt是验证集上指标最优的权重一般直接拿它来做推理。如果部署时追求更快的速度还可以用yolo export modelbest.pt formatonnx把模型导出为ONNX格式配合OpenCV DNN模块部署可以脱离PyTorch环境运行这是后续做更轻量化部署的常用手段。4. 检测结果到计数功能的落地模型训练出来只完成了第一步。从检测框到最终输出一个准确的数字中间还隔着一个计数逻辑的合理设计。不少朋友在跑通模型后就以为大功告成实际上计数逻辑的设计深深影响着最终系统的可用性。4.1 计数器逻辑检测只是第一步Ultralytics的推理API返回的是一个Results对象里面包含了当前帧中所有被检测到的目标信息包括每个目标的类别ID、置信度和归一化坐标框。最简单的计数逻辑就是统计属于目标类别的检测框总数from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model(test_img.jpg, conf0.25, verboseFalse) boxes results[0].boxes count int((boxes.cls 0).sum())但这样粗糙的统计在真实场景里不够用。首先NMS后处理偶尔还会留下同一个目标被重复检测的情况一个竹签被模型预测成两个重叠度不高的框此时总数会虚高。我的处理方式是增加一个去重阶段如果两个框的中心距离小于某个阈值比如20个像素且尺寸相似就认为它们是重复检测只保留置信度高的那个。另外置信度阈值不能盲目设低。Recall不够时阈值低可以帮助捞回一些漏检但阈值太低会引入大量误检把桌面的阴影纹理也当成竹签。我最后的做法是对现场采集的几十个不同场景做了一遍阈值扫描画出一条计数误差曲线选择误差最低的阈值点作为固定参数。4.2 处理重叠与遮挡的计数策略竹签交叉堆叠是最棘手的场景。两根竹签呈X形交叉检测器可能因为重叠区域的特征干扰只检出一根而漏掉另一根三根竹签紧紧并排则可能被框成一个粗的框数量直接从3变成1。我从数据层面和推理层面同时做了优化。数据层面训练集中专门增加了大量交叉堆叠的真实图片让模型见过足够多的“两根交叉”形态学会通过露出的头尾来区分独立目标。推理层面我调整了NMS的IoU阈值从默认的0.5调到0.7降低高IoU框被抑制的概率减少密集重叠时漏框的数量。此外还可以开启Ultralytics的半精度推理halfTrue虽然这不会直接改善漏检但提速后方便在摄像头场景下尝试多帧融合——连续取几帧结果如果计数波动小就取中间值或众数这样单帧偶发漏检不会直接毁掉整个计数结果。这里也分享一个关于计数任务本身的观点检测框的位置其实不太用追求像素级精确只要数量对得上计数就是成功的。所以重叠严重时与其纠结模型为什么没把框画准不如把精力放在怎么让框的数量和真实目标数一致上。4.3 计数准确率的评估方法部署前必须建立一套客观的计数准确率评估方法。我的做法是准备一份专门的测试集包含若干张不同难度等级的现场图像每张图由人工提前数出准确数量再让系统跑一遍算出绝对误差和相对误差。评估结果用表格记录场景类型人工数量系统计数绝对误差相对误差少量平行摆放8800%中等数量交叉摆放535411.9%大量密集堆叠12612243.2%暗光环境散落373812.7%把整个测试集跑下来重点看平均相对误差。如果平均误差在2%以内现场使用时的体验已经相当好。如果误差过大不要急着改模型先回到数据层面复盘是重叠场景数据不足还是背景干扰过多又或者是光照太暗导致细节丢失。这种逐项排查的效果远好于盲目堆训练轮数。5. PyQt5界面设计与系统集成模型和计数逻辑都稳定了最后一步就是把它包成一个普通操作人员也能上手使用的界面。PyQt5在这个环节展现出了强大的工程实用性但也藏着不少坑。这一节从布局到多线程把完整路径捋一遍。5.1 界面布局与交互流程我的界面主窗口分为左右两个区域。左侧是一个大尺寸的QLabel用于显示图像和检测结果右侧是一个固定宽度的控制面板包含四个按钮打开图片、打开文件夹、打开摄像头、停止检测、一个计数数字显示控件、一个置信度阈值滑动条以及一个日志输出框。交互逻辑是这样的点击打开图片会弹出一个文件选择对话框选定图片后系统加载图片并送入模型推理推理完成后在左侧显示画好框的效果图同时在右侧计数面板更新当前数量。打开文件夹时则对目录下的所有图片进行批量推理输出一个汇总报告记录每张图的计数结果。摄像头模式是最复杂的点击后调用计算机的默认摄像头系统以实时视频流的方式进行检测每帧刷新计数结果。如果摄像头画面里竹签在移动就会出现同一个目标跨帧被重复计数的问题。我在实时模式下加入了一个简单的去重逻辑维护一个目标ID列表只有当新检测出来的目标中心点与上一帧所有目标的位置都相差超过一定距离时才把它当作新目标计数。使用OpenCV的追踪算法可以做得更精细但在竹签这种固定环境下简单距离判断已经够用。5.2 使用QThread避免界面卡顿这是整个PyQt5集成中最重要的一个技术点。很多第一次写界面的朋友会把模型推理直接放进按钮的click事件里结果就是点击按钮后整个窗口冻结鼠标转圈画面一卡就是好几秒用户体验极其糟糕。原因很简单GUI的事件循环和推理任务在同一个线程里执行推理阻塞了界面响应。解决办法是把推理工作放到一个单独的QThread子线程中主线程只负责界面响应和结果展示。我封装了一个CameraThread类继承自QThreadclass DetectThread(QThread): frame_signal pyqtSignal(dict) def __init__(self, model, camera_index0): super().__init__() self.model model self.cap cv2.VideoCapture(camera_index) self.running True def run(self): while self.running: ret, frame self.cap.read() if not ret: continue results self.model(frame, conf0.25, verboseFalse) count int((results[0].boxes.cls 0).sum()) plotted results[0].plot() self.frame_signal.emit({img: plotted, count: count}) def stop(self): self.running False self.cap.release()在界面里只需要连接这个线程发出的信号self.detect_thread.frame_signal.connect(self.update_frame)然后在update_frame方法里更新图片和计数显示。这样无论推理多慢界面都能保持流畅滚动、点击响应。如果推理一帧超过200毫秒观察用户是否容易误操作需要注意及时在UI上给出“处理中”状态提示避免重复点击。注意多线程涉及界面更新时永远只能通过信号槽机制在主线程里更新控件不能在工作线程里直接调用控件的setText等方法否则会出现随机崩溃、界面闪烁等诡异问题。5.3 本地模型推理与显示刷新在界面里做推理时要注意图像的格式转换。OpenCV读入的是BGR格式的numpy数组而QLabel显示图片需要QPixmap。转换链路一般是rgb_image cv2.cvtColor(bgr_plot, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape bytes_per_line ch * w qt_image QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap QPixmap.fromImage(qt_image) self.image_label.setPixmap(pixmap.scaled( self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))这里比较坑的是bytes_per_line参数它必须等于通道数乘以宽度。如果图像宽度是奇数而你又直接把numpy数组data传给QImage有时会因为行字节对齐问题导致图像扭曲或者偏色。稳妥做法是先用np.ascontiguousarray确保数据内存连续再做转换。摄像头模式的帧率也需要控制。如果模型推理速度是每秒20帧但界面刷新的速度跟推理速度不一致会造成视频卡顿或画面撕裂。我在摄像头线程里做了简单的节流控制模型推理的帧率上限同时确保UI刷新与检测帧率保持一致。实际体验中15帧每秒左右已经足够流畅因为竹签计数场景本身不是一个高速运动场景帧率不必拉满。6. 常见问题排查与实操避坑实录系统的开发过程中一定不会是一帆风顺的。这里把我踩过的坑、读者最容易遇到的问题和处理思路整理成一份速查表希望帮你少走弯路。6.1 检测环节的典型问题问题现象可能原因排查方法重叠竹签大量漏检训练集缺少重叠样本补充交叉堆叠场景图片重新训练计数稳定多一根背景被误检为竹签提高置信度阈值检查标注中是否包含阴性样本暗光下漏检特别多图像对比度过低补光或在训练增强中加入随机亮度调暗竹签密集时框成一团分辨率不足NMS阈值不合适提高推理分辨率调整NMS IoU阈值模型对特定背景过拟合训练集背景单一增加不同台面颜色和纹理的图片我遇到的最典型问题第一版就是训练集背景太单一导致的现场失效后来重做数据后这个问题基本消失。建议你一旦发现模型在验证集上指标很漂亮但现场效果差先怀疑数据覆盖度。6.2 界面运行中的典型问题界面最常见的坑是摄像头关闭不掉。Windows下OpenCV的视频捕获对象如果线程没正确释放摄像头灯会一直亮着程序退出时甚至可能报错。解决方法是确保在关闭按钮中调用线程的stop方法让线程内部执行cap.release()而不是在主线程里直接销毁摄像头对象。另一个问题是界面启动较慢。因为加载YOLOv8模型需要初始化PyTorch环境通常需要几秒钟。如果用户点击按钮过早界面看起来像卡住了。我在程序启动时就提前加载模型配合一个“模型加载中…”的提示标签用户感知会好很多。对于追求启动速度的部署场景可以改成ONNX推理或者转成TensorRT但这些优化带来的复杂度要根据项目需要谨慎评估。还有一个容易被忽略的坑是路径问题。用PyInstaller打包成exe后程序内引用模型文件的相对路径可能失效导致运行时报找不到文件。我的习惯是把模型路径写成一个可配置参数放在界面里同时也支持在当前目录下自动搜索权重文件这样部署时不需要修改代码。6.3 训练过程中的常见问题训练时最常遇到的报错是显存不足。8GB显存跑yolov8s且imgsz640时batch不能太大我通常用16。如果想用1280分辨率batch得降到4或8或者开启amp混合精度训练这一步能显著降低显存占用。如果还是不够建议先拿yolov8n跑通流程确认成果后再换大模型。还有一个容易误导人的现象训练loss下降但Recall一直上不去。这时最可能的原因是训练集里的正样本质量有问题比如标注遗漏严重、或者标注框没有完全包含竹签导致模型学到的是不完整的特征。需要回到标注环节修正而不是继续加epochs。我自己曾经在标注时偷懒少标了几十根竹签结果无论怎么调参召回率的提升都非常有限把漏标补上后指标就正常了。7. 给同类计数项目的一点个人经验做竹签计数系统最大的体会是计数检测类项目真正的技术难度并不在网络结构而在于数据场景的覆盖度和工程集成细节。YOLOv8把目标检测的门槛压得很低任何人拿到官方教程都能训练出一个看起来可用的模型但能不能在实际现场稳定运行拼的是对数据的用心程度和对业务场景的理解深度。如果你也在做类似的计数项目无论是竹签、木签、棉签还是五金件我建议第一步不是急着找模型而是先花两周去收集真实场景的图像。把背景、光照、摆放姿态的多样性做足把标注质量做到位模型训练就会顺利很多。界面方面优先考虑多线程和交互流畅性不要让它成为项目交付时的最后一根稻草。我自己后续还打算在这个系统上做两件事一是把分类从单类别扩展成“原色竹签”和“碳化竹签”两个类别二是用标定板做一个实际尺寸推算让竹签在画面里斜着摆时也能更稳定地计数。这套基于YOLOv8加PyQt5的方案还有很大的扩展空间希望这篇记录能给同路人一些帮助。
阅读完成 · 觉得有帮助?
咨询建站