最近一段时间我终于把“基于YOLOv8的吸烟、喝水和打电话行为检测系统”从头到尾完整搭了一遍——从收集图片、标注YOLO数据集到训练模型、封装Python项目再到最后写UI界面接上摄像头实时预览整套流程跑通的时候心里确实挺有成就感。这个项目本质上是把目标检测模型落地到具体监控场景里用YOLOv8识别画面中的三类行为再通过一个可视化界面实时显示结果并做提示。它能解决的问题很直接比如工厂车间、办公区、机房这类场景人工盯监控容易疲劳系统可以自动标记“有人抽烟”“有人喝水”“有人打电话”并持续统计事件。适合的人群也很明确刚接触目标检测的开发者想做毕业设计或者课程项目的同学以及需要在业务场景里做行为预警的工程人员。1. 项目设计这套行为检测系统到底怎么搭1.1 为什么我选择YOLOv8而不是传统视觉方案在最开始构思的时候我也认真考虑过要不要用传统图像处理比如肤色检测、形态学滤波、HOG特征加SVM分类器。但实际测下来传统方法的鲁棒性真的撑不住现场环境。肤色检测对光照、肤色、摄像头色彩偏差非常敏感稍微换个场景阈值就失效抽烟这种类别因为烟雾形态变化太大单纯靠颜色和纹理几乎没法稳定识别打电话检测更麻烦手机是一个小尺寸物体传统特征难以概括所有型号和手持角度。YOLOv8解决的问题恰好是这一块。它是端到端的目标检测模型输入的图像直接输出每个目标的类别、置信度和边界框不需要我手工设计特征。对比更早的YOLO版本YOLOv8在主干网络和训练策略上做了不少调整整体精度和收敛速度都有改善。尤其重要的是它的官方实现把数据集配置、训练、验证、导出都封装得比较完善一个命令就能跑起来对工程落地非常友好。还有一个不能忽略的原因是生态。YOLOv8可以很方便地导出ONNX、TensorRT格式后面做UI界面和实时推理时我不需要在部署阶段重新写一堆底层逻辑。整个项目从训练到推理的链路都是通的这对我这种更关注业务逻辑和最终效果的人来说省了非常多时间。1.2 吸烟、喝水、打电话三个类别的检测难点表面上看这三类都是“人手持物体”的动作但真正标注和训练的时候每个类别都有自己的坑。吸烟行为的难点在于“烟”体积小、烟雾没有固定形态而且很多时候烟支只出现几秒钟稍纵即逝。如果摄像头安装角度偏高人物脸部的烟支特征会非常弱。早期我的模型经常把手指夹着的白色细长物体误认为烟后来才发现需要在训练数据里增加大量“手指夹烟但没有点燃”“烟放桌上”这类场景才能降低误报。喝水行为的难点在于“杯子”种类太多。保温杯、纸杯、矿泉水瓶、玻璃杯外观差异大不说很多人喝水的动作持续时间短容易漏检。好在喝水通常伴随一个明显的抬手动作只要训练数据里覆盖足够多的人体姿态和杯子形态模型还是能学到一些关键特征。打电话的难点在于手机品牌、手机套、握持姿势、遮挡情况的组合太丰富。有的人喜欢用耳机手机放在口袋有的人拿着手机但没在打电话还有人手拿着其他物品贴到耳边这种最容易误检。我在实际项目中加入了一个重要的判断思路检测框背后代表的是“手持手机且靠近头部”这个静态特征真正判断是不是打电话还需要通过连续帧的逻辑去去抖。1.3 项目模块划分这个系统整体可以拆成四个模块数据准备模块、模型训练模块、推理检测模块、UI交互模块。数据准备模块负责采集图像、整理YOLO格式的标签、划分训练集和验证集模型训练模块负责配置类别、超参数并产出训练好的权重文件推理检测模块负责读入视频或摄像头画面调用模型进行预测并把检测结果整理成坐标框、类别和置信度UI交互模块负责把画面和结果展示给用户同时提供业务开关、置信度阈值调节、事件统计等操作。这样拆的好处是每一层可以独立调试。比如模型效果不行我不会去动UI代码只调整数据或者训练参数界面卡顿我只优化推理线程不用重新训练模型。如果你也想复现建议先按这个层次把目录建好再逐步填充逻辑不然很容易被零散的代码拖垮。2. YOLO数据集的收集、标注与预处理2.1 数据来源与合规性做行为检测数据集的质量直接决定模型上限。我这次使用的是模拟项目X的采集数据三个类别各准备了约1000张图片另外加了300张不包含目标类别的背景图作为负样本总计3300张。为什么不只靠公开数据集因为公开数据集的场景、摄像角度和分辨率往往跟我的目标环境不完全一致模型迁移过去以后泛化效果会打折扣。采集方式很简单用普通USB摄像头在办公室环境里录制了几段视频让同事分别模拟吸烟、喝水、打电话的动作然后按一定帧间隔抽帧。录制时尽量覆盖不同角度、不同距离、不同光照条件比如顺光和逆光都要有。这里特别想强调合规性采集过程要提前说明用途并且对画面中的人员做模糊化处理避免出现可识别的个人信息。公开数据集也要注意授权协议不要直接拿来商用。负样本同样很关键。很多人训练检测模型只关注正样本结果模型在实际场景中疯狂误报。最简单的做法是截取大量没有目标物体的空背景图比如墙面、桌面、走廊标注文件里不需要生成任何目标框训练时模型自然会学会“这里什么都没有”。2.2 标注工具与YOLO格式我用的标注工具是LabelImg它对YOLO格式支持比较完整。标注时用矩形框框住目标注意框要尽量贴合物体轮廓但不能为了贴合某个局部而丢掉整体语义。比如打电话场景如果只框住手机而不是框住“人手持手机”的部分模型学到的信息会变得很残缺后期误报率会很高。所以我在标注时尽量框到目标物体的完整可见区域在遮挡严重时宁可漏标也不标一个边界混乱的框。YOLO格式的标签非常轻量每一行对应一个目标类别编号、归一化后的中心点x坐标、中心点y坐标、宽度、高度。假设一张分辨率为1920x1080的图片里有一个手机中心点在像素坐标(960, 540)宽为200像素高为300像素那么对应txt文件的一行就是2 0.5 0.5 0.1041 0.2778这里的2代表打电话类别x_center和y_center是0.5width和height是相对图像宽高的归一化结果。目录结构上我会把图片和txt文件放在同一级文件夹内并且保持文件名一一对应比如img_001.jpg对应img_001.txt。LabelImg会自动生成txt文件不需要手写。类别映射建议固定下来我使用0代表smoking1代表drinking2代表phone_call。这个顺序要和后面训练配置文件里的names保持一致否则模型输出的类别索引就乱了。2.3 数据划分与增强策略数据集准备好以后按8:2划分训练集和验证集。划分的时候要保证同一段视频抽出来的帧不要同时出现在训练集和验证集里否则两边画面太相似验证结果会虚高。最简单的方法是按视频文件来划分取其中80%的视频的所有帧做训练剩下20%的视频的所有帧做验证。数据增强方面YOLOv8训练时会自动启用Mosaic、随机翻转、HSV色彩变化等策略这个机制能显著提高泛化能力。不过我还在离线阶段额外做了两类增强一是随机调整亮度对比度用来模拟摄像头在不同时段、不同逆光条件下的效果二是加入少量高斯模糊模拟实际视频流压缩和解码带来的画质损失。要注意增强幅度不要过猛否则目标形态变形太大模型反而学不到真实特征。类别平衡同样要关注。如果吸烟类图片有1500张喝水类只有500张模型几乎一定会对多数类过拟合。三类样本数量我尽量保持在1000张左右如果某个类别收集困难就用数据增强多生成一些变体确保训练阶段不会出现明显的类别倾斜。3. 基于YOLOv8的模型训练与性能调优3.1 环境搭建与依赖版本训练环境我建议准备一块支持CUDA的NVIDIA显卡显存至少6GB这样跑YOLOv8s会比较从容。操作系统用Windows或者Linux都可以我是在Windows上完成的Python版本选择了3.10。安装依赖时最核心的几个包是torch、torchvision和官方YOLOv8实现。安装命令比较简单pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics注意CUDA版本和PyTorch的对应关系。如果显卡驱动版本不够新即使安装了CUDA 11.8对应的PyTorch运行时也会提示找不到CUDA。装好后可以先用下面这段代码确认推理环境可用import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果打印结果是False优先检查驱动版本和PyTorch的CUDA版本是否匹配再考虑重装。3.2 模型结构与训练配置数据集准备好以后我建了一个data.yaml文件内容大致如下train: ./dataset/images/train val: ./dataset/images/val nc: 3 names: [smoking, drinking, phone_call]训练命令用官方YOLOv8的CLI就能跑yolo detect train datadata.yaml modelyolov8s.pt epochs120 imgsz640 batch8 device0选择yolov8s是综合考虑了精度和速度yolov8n太快但精度稍微吃亏yolov8m精度更好但推理帧率会下降。对于实时行为检测这个场景yolov8s是一个不错的起点。如果你显卡显存足够且对精度更敏感可以换成yolov8m并适当降低batch size。训练过程中最重要的几个参数是epochs、imgsz和batch。epochs设到120轮左右实测在90轮以后mAP提升已经很小imgsz用640这是YOLOv8的默认输入尺寸再往上加会拖慢推理速度batch在8GB显存下设为8如果显存不足可以减少到4并开启混合精度。训练结束后最优权重会保存在runs/detect/train/weights/best.pt这个文件就是我们后续要用的核心模型。3.3 训练过程监控与过拟合处理训练过程中我主要看两个指标一个是训练集loss是否平滑下降另一个是验证集上的mAP50是否持续上升。如果训练loss很低但验证mAP停滞大概率是过拟合了这个时候先把epochs减半同时加大数据增强幅度特别是Mosaic和翻转的比例。如果验证集loss还在下降但mAP没有提升往往是类别不平衡或标注质量问题需要回头检查数据集。还有一个容易被忽略的点是置信度阈值。训练完以后默认推理使用conf0.25但这个阈值在真实场景里不一定合适。阈值调高到0.4可以减少误报但也会漏掉一些遮挡严重的对象阈值调低到0.15能提高召回率但误报会更多。我在UI里增加了一个滑动条让使用者可以根据现场环境实时调整而不是把阈值硬编码在代码里。3.4 模型评估与导出训练结束后官方实现会输出各类别的precision、recall和mAP指标。我这边一组实验的最终结果大致是这样类别精确率召回率mAP50smoking0.920.880.94drinking0.890.910.95phone_call0.870.840.92整体mAP50在0.94左右单帧推理速度在普通显卡上能达到45帧每秒以上基本满足实时画面的需求。不过这组数据是在验证集上的结果不能完全代表现场表现。真实环境里光照、遮挡、摄像头安装角度都会有影响后续需要通过UI调试来继续优化。导出模型用于推理时我一般先导出成ONNX再转成TensorRT engine这样推理速度会更快。命令如下yolo export modelbest.pt formatonnx imgsz640导出以后Python代码里可以直接加载ONNX文件也可以继续加载原始的.pt文件。对于UI界面来说加载.pt文件最方便不需要额外的环境配置所以我最终选择保留这一份。4. Python项目结构与UI界面落地4.1 项目文件组织整个Python项目我按下面这个结构组织behavior_detection/ ├── main.py # 入口启动UI ├── ui_main.py # 主界面逻辑 ├── detector.py # 模型推理封装 ├── models/ │ └── best.pt # 训练好的权重 ├── config/ │ └── data.yaml ├── requirements.txt └── assets/ └── icon.pngdetector.py负责加载模型和对图像做预测暴露一个predict(frame)方法给UI调用。ui_main.py负责所有界面控件和信号槽连接。main.py只做一件事创建应用对象并启动窗口。这样分层以后如果你想换成ONNX模型或者TensorRT模型只需改动detector.py内部实现界面完全不用动。4.2 UI界面功能与框架UI我用的是PySide6也就是Qt的Python绑定。界面左侧放实时视频预览右侧放控制按钮和事件统计底部可以展示当前检测的类别、置信度和框坐标。控制按钮至少要有“开始检测”“停止检测”“清空统计”。为了防止误操作开始检测以后“开始”按钮要变成灰色只有停止后才可再次点击。这里最重要的一点是UI线程不能直接做推理。摄像头读取和模型预测都是耗时操作如果直接在按钮点击事件里调用界面会立刻卡死拖动窗口都成问题。正确处理方式是开一个QThread在子线程里循环读取摄像头帧、执行预测通过信号把结果传回主界面。detector.py里的核心推理方法大概长这样import cv2 from ultralytics import YOLO class Detector: def __init__(self, model_path): self.model YOLO(model_path) def predict(self, frame): results self.model.predict(frame, conf0.25, verboseFalse) return results[0]UI端通过线程调用这个类。关键代码框架可以这样写from PySide6.QtCore import QThread, Signal import cv2 class DetectThread(QThread): frame_ready Signal(object) def __init__(self, detector, source): super().__init__() self.detector detector self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running and cap.isOpened(): ok, frame cap.read() if not ok: break result self.detector.predict(frame) self.frame_ready.emit((frame, result)) cap.release()主窗口拿到信号后在槽函数里绘制检测框和类别文字再显示到QLabel上。这样即使检测速度跟不上摄像头帧率界面也不会崩溃最多表现为画面有点延迟。4.3 检测结果的后处理与事件统计推理模型输出的是每一帧的目标框但业务上需要的是“行为事件”比如抽了几次烟、喝了几次水、接了几次电话。单纯的逐帧统计会重复计数一个持续5秒的电话如果摄像头是30帧每秒就会累计150次。我采用的方案是按时间窗口去重在UI里维护一个字典记录每个类别上一次触发时间只有当距离上次触发超过2秒才计一次新事件。对于风险提示我会把吸烟和打电话设为需要告警的行为喝水作为普通记录。当检测到吸烟或打电话且置信度超过阈值时界面会在对应统计卡片上标红并可能叠加一条文字提示。后处理还有一个细节多个检测框同时存在时为了显示清楚我会用类别对应的颜色区分红色给吸烟蓝色给喝水橙色给打电话。4.4 从视频文件到摄像头实时流的切换做UI时最好支持两种输入源一种是打开本地视频文件方便测试模型效果另一种是连接摄像头方便现场部署。DetectThread里的source参数可以传整型索引0代表默认摄像头也可以传视频文件路径。读取之前先用cap.isOpened()检查是否成功失败时要在界面弹出提示而不是让线程静默退出。摄像头实时检测时会有一定的固有延迟因为读取、推理、显示、刷新这几个环节都占用时间。我在实测中普通720p摄像头配合YOLOv8s整体端到端延迟在0.2秒到0.4秒之间。如果你需要更快可以把推理图像尺寸从640降到480或者开启ONNX的FP16推理但精度会有些许损失。5. 常见问题与排查技巧实录5.1 训练和推理时常见的环境问题训练阶段遇到最多的就是显存不足。如果你的显卡只有6GB跑batch8很可能会爆显存解决办法是把batch size降到4或者把imgsz从640降到512再开启混合精度训练。命令里加一个ampTrue即可默认情况下也是开启的但如果你手动关闭过记得打开。推理阶段更常见的问题是加载模型很慢或者首次预测非常慢。这通常是因为模型要初始化CUDA上下文。解决办法是在程序启动时先做一次预热比如只加载模型后拿一张黑色空图调用一次predict这样后续检测就不会在关键界面操作时卡顿。5.2 实时检测卡顿和延迟卡顿的根源往往不在模型本身而是UI线程和检测线程没有解耦。我见过有同学把cv2.VideoCapture.read()和模型推理都写在一个按钮点击事件里结果界面明显假死。务必按照4.2节的方式使用QThread。如果线程已经拆开了还是卡那多半是每一帧都做了一次图像格式转换比如把BGR转RGB又转回BGR。YOLO官方实现内部会处理颜色格式你的代码里不要再重复转换。还有一个常见性能瓶颈是绘制检测框的频率。如果每帧检测后都立刻刷新QLabel界面绘制压力会很大特别是在画面分辨率很高的情况下。我采用的办法是限制UI刷新频率比如每3帧只刷新一次显示但统计事件仍然逐帧计算这样画面体验会更顺滑CPU占用也低一些。5.3 误报和漏报的排查建议误报和漏报永远不可能完全消灭只能通过工程手段压低。如果你的系统频繁把桌面上的水杯识别成喝水把办公室里的白色管道识别成吸烟先不要急着改模型第一步是检查置信度阈值。把conf从0.25提高到0.4多数弱误报会被过滤。如果误报依然存在需要针对性收集难例把当前检测错误的图片保存下来累积到一定数量后加入训练集重新训练。漏报的情况则不同。模型漏掉目标通常是因为目标外观与训练集差异太大比如现场摄像头是仰角拍摄模型几乎没有见过这个角度。我的建议是采集现场数据做二次微调具体做法是用现有权重作为预训练模型在新增数据上继续训练50到80轮这样比完全从零训练快得多而且效果往往更好。对于打电话检测最容易出现“拿任何东西贴耳边都算打电话”的问题。我后来在训练集中增加了一个单独类别来标注“手拿水杯到耳边”这种负样本虽然这个动作很少见但加入以后模型对手机和杯子的区分能力明显上升。如果你希望检测更严谨可以进一步结合人体关键点判断手与头的距离关系但这已经超出纯YOLO的范畴了。5.4 UI界面和摄像头方面的坑PySide6窗口无反应的另一个常见原因是误用了print调试。在子线程里打印大量日志会拖慢线程执行并可能影响信号传递。建议把日志改写为信号发送到UI线程再通过状态栏展示或者只保留关键日志。否则并发打印会导致输出终端卡顿让人误以为是程序死锁。摄像头无法打开时先检查cap.isOpened()同时确认没有其他程序占用摄像头。笔记本自带摄像头一般传0但如果你的设备被虚拟摄像头软件占用索引可能变成1或2。可以写一个小工具循环尝试索引0到9找到能打开的索引再启动检测。5.5 最后一个关于经验的小技巧我在实际使用中发现最影响行为检测系统体验的不是模型mAP而是“可调节性”。现场环境千变万化系统上线后一定要留出可调的置信度阈值、画面缩放比例、事件冷却时间。把这些参数暴露到UI界面上远比把模型训练到完美更重要。你给用户一个阈值滑块用户自己调一调就能适配现场你如果全部写死哪怕模型很强换一个场景也会被抱怨难用。另外如果你准备把系统用到更严格的业务场景里建议为每一个检测到的事件保存一张截图或一段短视频方便事后追溯。这个功能我在UI里实现得非常轻量每次触发事件后用cv2.imwrite保存当前帧到指定目录文件名带上时间和类别。看起来不起眼但在真实使用时这个回溯能力能省下大量的沟通和确认时间。