
1. 项目概述与核心价值1.1 这个检测系统到底能做什么我做了这么久的计算机视觉项目说实话行为检测这类需求始终是最常被问到的方向之一。但很多朋友一上来就想着复现识别猫狗、识别车牌那种公开项目真正落到细分场景里的反而不多。今天要拆解的这个项目聚焦的是吸烟、喝水和打电话三种行为的实时检测基于YOLOv8来做目标检测配套完整的YOLO数据集、Python工程代码和一个可操作的UI界面。先别急着觉得简单这三类行为和常规物体检测有本质区别。识别猫也好、瓶子也好目标物本身是静态的、完整的、特征稳定的。但吸烟这个动作里真正的核心目标是烟头——一个可能只有十几个像素的微小目标而且经常被手部遮挡喝水涉及瓶颈、瓶身、嘴巴、仰头动作的配合仅仅框住瓶子是不够的系统要判断的是人正在喝这个状态打电话就更典型了手机贴在耳边时会遮挡大半有些场景下还要区分手持通话和佩戴耳机通话。所以这个项目不是简单套一个预训练权重就行它是一个完整的行为识别工程流。这个系统能落地的场景非常明确工地安全生产监控、化工厂区烟雾明火预警、驾驶舱内违规行为识别、机房/实验室作业规范检测。这些场所对吸烟行为有硬性禁令对饮酒/饮水的区分管理也有需求而驾驶场景对打电话的监管更是刚需。我手上的一个实际案例是给某工地做安全帽检测之后甲方紧接着提的需求就是能不能把工人抽烟也识别出来——这就是这类项目最典型的客户画像。1.2 这套方案的技术选型逻辑为什么是YOLOv8而不是YOLOv5、YOLOv7或者改用传统的图像分类加目标检测两阶段方案这个问题我每次都会被问到其实答案很直接生态成熟度YOLOv8的官方仓库自带训练、验证、导出全流程不需要额外拼装模块这对于做个能交付的工程来说太重要了。精度和速度的平衡点在边缘设备比如工地用的工控机上YOLOv8n/s级别的模型能做到实时推理的同时保持相当不错的小目标召回率这对检测烟头这种小目标很关键。内置多任务头YOLOv8的检测头结构对微小目标有更友好的特征表达虽然有争议但实际项目里它就是比YOLOv5x在烟头这类目标上表现更稳代价是训练时间稍长。周边生态丰富数据标注格式、模型导出ONNX、TensorRT加速、UI界面接入的参考资料都有大量积累遇到问题不至于卡死。但我也要泼一盆冷水如果你只想做一个演示Demo什么模型都够用如果你要做交付级产品那么核心难点根本不在于模型代码本身而在于数据集的真实性和场景适配度。这一点后面会展开说。1.3 适合谁看看完能得到什么这篇拆解适合三类人第一类是正在做毕业设计或课程项目的学生需要一套能跑通、能演示、能答辩的行为检测系统第二类是想接安防监控、工地巡检类外包项目的开发者需要一个可复用的烟火气项目模板第三类是对YOLOv8感兴趣、想了解目标检测从数据到部署全流程的入门学习者。我会按照从零到一的顺序把数据准备、模型训练、UI界面搭建、常见问题排坑这四个阶段完整讲一遍。整个项目涉及Python 3.9、YOLOv8官方库、PyTorch 2.x、OpenCV、PyQt5以及一个标注好的YOLO格式数据集。另外我会把实际项目中踩过的坑、试错的经验也一并写清楚——比如为什么你的验证集准确率很高但现场测试一塌糊涂比如标注数据时最常见的错误习惯。提示这个项目不涉及任何敏感技术领域全部基于公开框架和自建数据集实现可放心在合规环境下开发。2. 整体方案设计与拆解2.1 行为检测系统的总体架构把这个系统拆开看可以分成五个核心模块数据层、训练层、推理层、UI交互层和输出管理层。数据层的核心是一个带标注的YOLO格式数据集。我看到不少初次做这类项目的朋友最容易犯的错是一上来就去找吸烟数据集喝水数据集这种现成资源。但说句实话公开的细粒度行为数据集质量参差不齐很多是网图随意截的类别混淆严重——烟头和手指分不清、喝水瓶子标注成垃圾桶、打电话遮挡目标截断标注。这种数据训练出来的模型实验室里玩玩还行真正现场跑就是灾难。所以更可靠的路线是自己采集/爬取一批图片用标注工具按照统一标准二次整理然后再考虑补充公开数据集做混合训练。训练层就是YOLOv8模型的训练脚本配置涉及数据YAML编写、超参数选择、训练策略调整、验证指标评估。推理层是把训练好的权重接入到实时视频流或图片批量处理流程里通过OpenCV读取帧送入模型推理得到检测框、类别和置信度。UI交互层承担的是给人看的职责。如果只是脚本输出检测结果那叫能用但项目的交付对象往往是甲方、导师或管理者他们需要一个可视化的界面来查看实时画面、检测结果和统计报表。所以UI层要解决视频展示、检测框叠加、数据统计和基本的交互控制。输出管理层负责把检测结果落成结构化数据——检测日志、报表统计、告警触发甚至联动其他设备比如发现吸烟行为自动抓拍存档。严格说这部分才算真正的产品化但很多项目里通常被忽略我会给一个轻量级实现方案。2.2 三种行为的检测难点拆解吸烟行为小目标与遮挡的双重难点吸烟检测的本质是检测正在燃烧的烟头。传统目标检测的头号难题是烟头在整个画面中占比太小。在1080P的监控画面里一个工人的烟头可能只有20x5像素这个尺寸在YOLOv8下采样后的特征图上几乎只剩1个点左右。所以如果直接把原始图片缩放到640x640去训练烟头信息早就丢失干净了。实践中我的处理经验是(1)训练时将图片裁切成更聚焦的区域或者说用更大的输入分辨率比如960x960甚至1280x1280代价是推理速度下降(2) 必要时做二次检测——先用大模型框定人体区域person类再在人体框内检测烟头这相当于一个简单的两阶段策略(3) 合理使用数据增强特别是mosaic、copy-paste这类手段把烟头贴到更多样化的背景上。另外吸烟行为的另一个麻烦是烟和手指的视觉混淆。如果你标注时不统一标准烟头是只标烟头还是把手指一起框进去模型学到的特征就会很混乱。我强烈建议标注时统一为包含烟头且明显处于吸食状态的边界框——宁可框得紧一点也不要把整只手框进去。喝水行为动作语义抽取喝水检测比吸烟看似简单瓶子目标大啊但实际做起来很别扭系统要判断的是人在喝水而不是画面上有瓶子。一个工人把矿泉水瓶放在桌上模型会检测出water_bottle但这不算喝水行为只有瓶子在嘴边、头部后仰、有明显的饮水动作时才应该触发检测。解决这个问题有两条路一是把喝水动作样本单独标注成一个类别标的不再是瓶子而是人正在喝水的完整瞬间这样YOLO学到的就是人-瓶-嘴部关系的组合特征二是结合姿态估计的关键点头部、手腕、肘部位置通过相对位置关系判断喝水动作。综合考虑数据成本和推理效率推荐第一种方案这也是我在这个项目里选择的路线。标注的时候要特别注意负样本——手持瓶子但没喝、端杯子走到一半、擦嘴瞬间这些都要标注成背景而不是drinking否则模型会无差别输出误检。打电话行为目标多形态打电话的行为特征是手机靠近耳部。但在实际数据里一个工人可能用左手、右手接电话可能手机贴着左耳或右耳可能低头看手机发消息虽然这也算违规操作但和打电话动作特征并不一致。更麻烦的是很多工地、工厂工人会戴蓝牙耳机或有线耳机通话此时手机可能放在口袋里画面里根本没有手机目标系统就容易漏检。我的做法是定义两个子类别手持通话和耳机通话。手持通话检测的核心目标是手机和耳部的重叠区域耳机通话则依赖人体姿态的低头嘴唇微动特征这个特征在监控画面上特别明显。另外打电话行为的时间持续性很强可以通过时序判断提高置信度单帧检测到打电话且置信度偏低时可以结合前后几帧的检测结果做简单的投票滤波这样避免瞬间误检也能降低漏报。这部分会在后面的推理优化里给出具体代码思路。2.3 为什么选UI界面而不是纯命令行直接说结论如果你只写脚本交付这个项目的价值会打五折。原因有几个层面。第一行为检测系统的最终用户通常不是开发者。工地的安全员不熟悉终端操作但你看一个双击打开、显示实时画面的窗口他就能明白系统有没有在工作。第二行为检测的现场调试极度依赖可视化反馈——模型把一瓶可乐误判为吸烟时你需要马上看到是哪个类别出了问题而不是翻日志。第三很多人忽略的是一个带UI的界面对于项目的验收感是质的提升。毕业设计答辩时、项目展示时一个清晰直观的界面窗口远远胜过一屏幕滚动日志。这个项目我采用的是PyQt5作为桌面UI框架。为什么不是Web界面因为考虑到很多监控场景是本地局域网部署、无公网环境桌面端应用更稳妥也不需要考虑浏览器兼容性和并发问题。如果你更熟悉Web技术用Flask/FastAPI搭建一个本机Web服务也可以但实时视频流在浏览器里的延迟和编解码开销会多出不少麻烦。下文中我会按桌面端方案展开并在实操环节给出具体界面布局和核心代码。3. 核心细节解析与实操要点3.1 数据集的标准与构建方案数据来源组合策略我一贯的建议是自建为主、公开为辅。对于本项目可能需要收集不少于1500张包含吸烟/喝水/打电话行为的真实场景图片并均衡三个类别的数量。可以从这几个渠道解决开源数据集平台比如常用的行为识别数据集上有部分抽烟、打电话的图片但需要检查标注质量。我实际用过标注混乱的公开数据最后逼得还是半标重来。你如果不想太折腾可以在公开数据基础上加几百张自己拍的图做补充修正。视频抽帧从公开的监控场景视频、纪录片、工地施工视频中按帧抽取这是获取现实姿态数据最高效的方法——一秒钟视频抽3帧十分钟视频就能获得数百张包含不同姿态的样本。手机实拍自己/朋友在教室、楼道、办公室里模拟动作拍摄。这种数据最可控但要注意背景多样性别拍来拍去都是同一个白墙会导致模型过拟合环境。标注工具与规范标注工具我用的是LabelImg或者X-AnyLabeling都支持YOLO格式直接输出。X-AnyLabeling自带了一些预标注能力可以先用一个粗糙模型跑一遍自动标注人工修正边界框效率能提升不少。我建议的标注规范类别名称统一为英文小写加下划线smoking、drinking、calling避免中文路径和中文类名在训练框架里出幺蛾子。吸烟类边界框围绕烟头及烟身不包含整只手当烟头被完全遮挡但手指有明显持烟动作时不标注。喝水类边界框围绕水瓶嘴部区域的组合区域纯手持水瓶不在嘴边不标注。打电话类边界框围绕头部和手机如果有耳机通话行为框住头部区域手机拿在手里但没有贴耳、且低着头看屏幕的根据需求决定是否标注——如果要严格检测违规操作建议把看手机也纳入打电话类别不然客户现场测试会发现漏了一大半。标签检查不能省标注完成后一定要做标签复查。我踩过几次坑之后形成了这样一个习惯写一个小脚本把每张图片的标注框和类别名重新画到图上肉眼一张张扫。重点检查三类问题类别贴错、边界框与目标偏移太多、一张图出现两个同类目标只标了一个。这个流程虽然枯燥但效果立竿见影——数据错则模型必错。3.2 YOLO格式数据集的目录结构与组织YOLOv8训练要求的数据集目录结构约定如下建议严格遵循dataset/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 └── labels/ ├── train/ # 训练集标签 └── val/ # 验证集标签images和labels的同名文件要一一对应比如img_001.jpg对应img_001.txt。labels目录下每个txt文件的格式是class_id x_center y_center width height其中坐标值都是相对于图片宽高的归一化比例不是像素绝对值。举个例子如果一张1080x720的图片中一个人打电话的边框像素坐标是左上角(200, 300)、右下角(500, 600)那么转换公式是x_center (200 500) / 2 / 1080 0.324 y_center (300 600) / 2 / 720 0.625 width (500 - 200) / 1080 0.278 height (600 - 300) / 720 0.417标注工具大多能自动完成这个转换但理解原理才能排查坐标错乱的问题。数据集划分上如果总样本数在1500张左右我建议训练集和验证集按8:2或9:1划分同时要保证三个类别在训练集和验证集中的比例大致一致避免验证集恰好全是某一类。更严格的做法是写脚本按类别统计分布然后分层抽样。3.3 环境准备与依赖安装为了减少环境坑我建议直接基于官方YOLOv8环境来搭建。推荐环境版本如下组件版本建议说明Python3.9 - 3.113.8以下有些新库不支持PyTorch2.x2.0以上的CUDA支持更完善ultralytics8.x官方YOLOv8实现库OpenCV4.8视频流处理PyQt55.15桌面UI框架numpy1.24基础计算安装命令大致是pip install ultralytics opencv-python PyQt5 numpy # 如果有NVIDIA GPU先按官网安装对应版本PyTorch pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121没有GPU纯CPU训练的话模型会非常慢建议至少准备一个带CUDA的NVIDIA显卡哪怕是6GB显存级别的入门卡或者用云GPU服务按小时租用训练完成后下载权重到本地推理。注意Ultralytics库版本迭代很快如果你用了旧教程的命令发现报错先检查是不是版本升级导致的API变化。我一般习惯固定版本号pip install ultralytics8.1.0避免隔几个月后莫名其妙出现不兼容。4. 实操过程与核心环节实现4.1 模型训练配置YAML与超参数选择data.yaml的编写在数据集根目录下创建一个smoking_data.yaml文件# 训练配置 path: E:/project/smoking_detection/dataset # 数据集根目录绝对路径 train: images/train val: images/val nc: 3 names: 0: smoking 1: drinking 2: calling这里我踩过一个大坑path字段使用相对路径在某些版本的ultralytics下能跑通但有时在训练换机器后出现找不到数据的诡异问题。建议直接使用绝对路径并且在训练前用yaml.safe_load读一遍确认路径生效。训练命令与参数基础训练命令yolo detect train datasmoking_data.yaml modelyolov8m.yaml pretrainedyolov8m.pt epochs150 imgsz640 batch16 device0几个关键参数我的实际经验参数推荐值理由modelyolov8mn/s模型太小烟头这类小目标学得吃力l/x模型对算力要求高m是性价比选择imgsz640起步显存不够或追求速度用640目标小而场景复杂时建议提升到960batch16视显存调整6G显存建议812G以上可试16epochs150-300早期150看曲线走势不够再加patience30早停耐心值验证集指标连续30个epoch不上升就停训练过程中重点看两个指标mAP50和mAP50-95。mAP50表示IoU阈值0.5下的平均精度是基础门槛mAP50-95则对边框定位质量要求更高。如果mAP50很高但mAP50-95偏低说明你模型定位没问题但边界框不够精细可以适当调高imgsz再训一轮。训练过程中的LOSS曲线解读每个epoch后控制台会打印box_loss、cls_loss、dfl_loss训练集和验证集各一份。我一般关注验证集loss是否持续下降和mAP50是否稳步上升。如果验证集loss先降后升、mAP50同步下降基本就是过拟合了——减少epoch、增加数据增强、或者补充数据是三个方向。4.2 模型评估与验证训练结束后在runs/detect/train/目录下ultralytics会生成confusion_matrix.png、results.png、val_batch*.jpg等可视化文件。这里我提醒一个关键习惯不要只看混淆矩阵一定要看val_batch的标注图片。混淆矩阵告诉你的是数字层面的错分情况但可视化图片能告诉你为什么错分——比如很多drinking被误标成smoking你一看图才发现喝水动作和吸烟动作在嘴部区域极其相似模型学到的是手靠近嘴这个共性特征。这时候就需要从数据层面增加区分度喝水样本中要补充仰头状态的图片吸烟样本中要补充烟头明显发光的图片从特征层面拉开两者的距离。如果有测试视频或图片可以用命令行直接验证效果yolo detect predict modelruns/detect/train/weights/best.pt sourcetest_video.mp4 imgsz640 saveTrue这里的saveTrue会把检测后的视频保存下来方便你肉眼检查检测效果。我通常会在不同光线、不同距离、不同背景的视频上各跑一遍发现漏检严重时先分析场景差异再回到数据层面补样本。4.3 UI界面的实现界面功能规划UI需要解决的核心问题是让非技术人员可以用。我设计的界面包含视频源选择区可以打开本地视频文件、连接摄像头设备号0代表默认摄像头也可以直接填写RTSP网络串流地址。实时画面显示区原始视频画面和检测叠加画面二合一显示检测框上显示类别标签和置信度。检测结果统计区三个类别的实时计数比如吸烟2人“喝水0人”“打电话1人”同时有总触发次数。控制和状态区开始检测、暂停、停止按钮模型加载状态栏FPS实时显示。PyQt5界面核心代码下面是一段简化的核心逻辑代码保证UI响应不卡顿的关键在于检测推理放到独立线程里不能在主线程中做耗时的模型推理否则窗口会直接无响应import sys import cv2 import torch from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget from PyQt5.QtCore import QTimer, QThread, pyqtSignal import numpy as np from ultralytics import YOLO class DetectorThread(QThread): frame_signal pyqtSignal(np.ndarray) # 检测后的帧 stat_signal pyqtSignal(dict) # 统计信息 def __init__(self): super().__init__() self.model YOLO(runs/detect/train/weights/best.pt) self.cap None self.running False self.source 0 # 默认摄像头 def set_source(self, src): self.source src def run(self): self.running True self.cap cv2.VideoCapture(self.source) while self.running: ret, frame self.cap.read() if not ret: break results self.model.predict(frame, imgsz640, conf0.45, verboseFalse) annotated results[0].plot() # 绘制检测框 # 统计各类别数量 names results[0].names cls_list results[0].boxes.cls.tolist() counts {smoking: 0, drinking: 0, calling: 0} for c in cls_list: cls_name names[int(c)] if cls_name in counts: counts[cls_name] 1 self.frame_signal.emit(annotated) self.stat_signal.emit(counts) if self.cap: self.cap.release() def stop(self): self.running False class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(行为检测系统) self.label QLabel(实时画面显示区) self.btn_start QPushButton(开始检测) self.btn_stop QPushButton(停止检测) self.stat_label QLabel(统计吸烟0 喝水0 打电话0) layout QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.stat_label) layout.addWidget(self.btn_start) layout.addWidget(self.btn_stop) container QWidget() container.setLayout(layout) self.setCentralWidget(container) self.detector DetectorThread() self.detector.frame_signal.connect(self.update_frame) self.detector.stat_signal.connect(self.update_stat) # 定时刷新 self.timer QTimer() self.timer.timeout.connect(self.on_timeout) self.btn_start.clicked.connect(self.start_detection) self.btn_stop.clicked.connect(self.stop_detection) def start_detection(self): self.detector.start() self.timer.start(30) def stop_detection(self): self.detector.stop() self.timer.stop() def update_frame(self, frame): # 将OpenCV BGR帧转RGB并显示到QLabel rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape bytes_per_line ch * w from PyQt5.QtGui import QImage, QPixmap qimg QImage(rgb.data, w, h, bytes_per_line, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg)) def update_stat(self, counts): self.stat_label.setText( f统计吸烟{counts[smoking]} 喝水{counts[drinking]} 打电话{counts[calling]} ) def on_timeout(self): pass # 可做额外状态刷新 if __name__ __main__: app QApplication(sys.argv) win MainWindow() win.resize(1000, 700) win.show() sys.exit(app.exec_())这段代码是可用骨架但有几个细节值得注意self.detector.start()只调用一次但检测循环会持续运行直到running置False。视频帧转换成QImage时bytes_per_line不能直接用ch * w吗在大多数情况下可以但如果图像宽度不是4的倍数Qt显示可能出现错位。稳妥做法是用frame.strides[0]赋值bytes_per_line frame.strides[0]。界面刷新用QLabel.setPixmap在帧率不高时够用但如果FPS超过30建议考虑QLabel换成QPainter绘制或者降低刷新频率否则UI会卡。导入本地视频与RTSP串流UI界面上需要给用户选择视频源。视频文件直接用文件选择对话框拿到路径传给cv2.VideoCaptureRTSP网络摄像头地址则用一个输入框让用户填写如rtsp://192.168.1.100:554/stream1但由于OpenCV默认对某些RTSP格式兼容不佳建议在打开视频流时设置缓冲区和超时参数os.environ[OPENCV_FFMPEG_CAPTURE_OPTIONS] rtsp_transport;udp|timeout;3000这个参数在很多网络摄像机下能明显减少连接卡死的问题。如果设备只支持TCP传输把udp改成tcp即可。4.4 实时推理的速度优化实时推理最怕的就是掉帧和卡顿。在PC上YOLOv8m模型在1080P视频上通常能做到15-30 FPS但在低配设备上可能需要做几个取舍降分辨率推理时把输入帧长边缩放到640或960检测完成后把检测框坐标再映射回原图。这个操作能显著提升速度代价是远处的小目标可能丢失。跳帧策略摄像头30FPS的流每2帧做一次检测中间一帧直接用上一次的检测结果。对于行为检测这种低频动作场景完全足够。TensorRT导出在NVIDIA GPU上把模型导出为TensorRT引擎实测推理速度可以提升1.5-3倍。命令是yolo export modelbest.pt formatengine device0然后在predict时指定engine文件路径即可。不过TensorRT引擎和CUDA版本绑定换机器后需要重新导出这点必须在交付说明里写清楚。半精度推理predict时加上halfTrue在一些新卡上能减少显存带宽占用速度略有提升但old GPU可能不支持建议先测试再启用。5. 常见问题与排查技巧实录5.1 训练阶段数据问题怎么排查问题一loss不下降mAP曲线趋平现象训练到50个epoch后mAP50一直卡在0.3以下。排查路径先检查数据集——我遇到过标签txt里类别ID写错比如smoking是0但标注工具输出成1整个类别错位。写一个脚本逐项打印标签和图片预览通常能一眼发现问题。检查数据增强是否过强特别是mosaic如果概率太高小目标烟头在mixup后被洗没了。检查类别平衡——如果calling标注了500张drinking只有80张模型对drinking的学习能力会很差。问题二验证集mAP高但现场/测试视频效果差这个现象我见得太多了。原因基本可以归结为训练数据与测试数据分布不一致。公开数据集的图片大多是高清晰的、正对视角的、光线充足的但监控视频通常角度是俯拍、光线昏暗、画面有噪点。解决方案只有一条把现场场景数据补充进去重新训练。这属于数据工程问题模型换得再好也解决不了。在项目启动时务必先确认部署场景的摄像头安装位置和视频分辨率再去针对性采集数据。5.2 推理阶段误检和漏检怎么优化漏检烟头/矿泉水瓶先把conf阈值降低到0.25看一下模型输出的原始结果如果降低后能看到目标框只是置信度低说明模型其实学到了特征可以适当调低推理阈值或者通过增大imgsz提升小目标召回。如果降低阈值后依然没有检测框那就是训练数据里这类场景太少回到数据层补样本。误把手机壳/口袋物品判成打电话这类误检往往出现在侧面角度。可以增加负样本——标注手持手机但没有通话的图片为背景类或者在后处理代码里加规则过滤比如检测框中心和人体头部中心距离大于某个阈值时置信度降权。这两条路线可以并行。喝水动作误检成吸烟这是三个类别之间最典型的混淆。本质原因是手部靠近头部这个特征太接近。方法一是加大仰头饮水状态的样本量方法二是训练后用测试集统计混淆矩阵专门针对drinking行里误判成smoking的那批样本做针对性增加。别嫌这个操作繁琐实际效果往往立竿见影。5.3 UI界面常见崩溃问题摄像头打不开/视频流黑屏优先检查设备号是否存在。Windows下用脚本遍历一次cv2.VideoCapture(0)到cv2.VideoCapture(10)找到第一个能打开的设备把设备号显示在界面上。RTSP则先跑一次OpenCV自带的cap.isOpened()判断。界面点击开始后无响应几乎都是因为在主线程做了推理。务必使用QThread把检测循环移到子线程参考上文的DetectorThread写法。另外线程停止时一定要做资源释放——cap.release()和线程wait()否则第二次启动检测时摄像头资源会冲突报错。打包exe后检测正常但界面图标丢失PyQt5的资源文件打包是个老大难。用pyinstaller -F --windowed打包时记得把模型文件、界面图标一并添加到--add-data里。我建议直接加上pyinstaller -F --windowed main.py --add-data runs/detect/train/weights/best.pt;runs --add-data icons;icons另外如果目标机器没有NVIDIA驱动那么卷积计算会异常缓慢甚至报错。交付时最好确认目标机器的GPU环境或者在后备方案中提供CPU版本的模型YOLOv8n虽然慢一些但至少能跑。5.4 数据集标注的独家避坑清单再补充一份我在标注过程中的自检清单每张图片的标签写入前先确认类别ID和数据yaml中的索引一致——这是最容易修又最容易出的错。不要标经验性膨胀框——看到目标后凭习惯框得特别大把背景都包含进去。YOLO的框学出来会非常不稳。负样本没有检测目标的图片一定要加入训练集。我见过大量新手训练集里全是正样本结果模型面对空背景时乱框乱报。一个批次内的图片方位多样性要覆盖正面、侧面、背面、远距离、特写否则模型泛化能力极差。这里多说一句经验行为检测项目的大头永远在数据工程部分模型训练和调参最多占两三天时间。如果准备这部分做扎实一点后面所有流程都会顺畅得多。6. 项目交付与扩展建议6.1 你的交付物应该包含什么一个可交付的项目至少应该包含这些部分标注好的数据集图片labelsdata.yaml训练完成的权重文件best.pt同时导出ONNX格式做后备完整的Python项目代码数据检查脚本、训练配置、UI入口使用说明文档环境配置步骤、模型替换说明、常见问题解答演示视频或演示图片集另外我建议你做一个requirements.txt锁定所有依赖版本这样换机器复现时不会因为某个库升级导致代码跑不起来。6.2 扩展方向从单一检测到行为理解这个项目本身是一个单帧检测系统但如果后续想往更智能的方向扩展有几个不错的进阶方向时序判定与告警联动把每一帧的检测结果写入环形队列当一个行为持续N帧比如吸烟动作持续超过3秒时触发一次告警抓拍当前帧存盘并记录时间戳。这样能极大降低单帧误检对告警的干扰。多人同时检测YOLOv8本身就支持多目标检测当画面中出现多人且不同的人有不同行为时需要把检测框和人员进行跟踪关联这个可以接入ByteTrack跟踪器实现。区域联动结合摄像头标定当检测到吸烟行为且人员位于禁烟区域时触发不同级别的告警策略。如果是工地戴安全帽识别也可以把安全帽检测和行为检测组合在一个模型里一起做多任务训练减少部署负担。这些扩展方向对于毕设或商业项目来说都是很好的加分项能显著拉开和普通YOLO入门演示的差距。6.3 我对这个项目实操的一点体会坦诚地说这类行为检测项目我前后做过几个版本最大的感受是YOLOv8本身只是工具真正衡量项目价值的标准是这个系统在实际场景中能不能稳定跑起来。很多同学训练完模型看到测试视频里检测框画得漂亮就松一口气了但距离真正能交付给甲方使用中间还差着很多现场测试和反馈循环。数据补了又补、阈值调了又调、界面改了几版这些才是实际项目中耗时最久的部分。我在实际项目中还发现一个小技巧多保留几个版本的权重文件别只留best.pt。因为不同场景下best.pt不一定总是最合适的——比如在光线很暗的工地上可能某个次优的权重因为对低照度有更好鲁棒性反而现场效果好于官方best。把这个经验留给你后续做同类项目的时候会有大用处。