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

基于YOLOv8的行为检测实战:吸烟、喝水、打电话识别系统构建

基于YOLOv8的行为检测实战:吸烟、喝水、打电话识别系统构建 ★ FEATURED ARTICLE
1. 这个项目到底要解决什么问题三类行为的识别难点拆解1.1 为什么行为检测比人车识别更难我第一次接到这个需求时第一反应是这不就是目标检测嘛框三类目标而已。实际做下来才发现行为检测跟传统意义上的物体检测完全是两码事。拿人车识别举例车有固定的外形、固定的尺寸比例人虽然在姿态上有变化但整体结构仍然是头躯干四肢类别之间的差异非常明显。但你让模型去识别一个人正在吸烟、一个人正在喝水、一个人正在打电话这三个目标有两个共同特点第一关键物体都很小——烟头可能只有几十个像素手机和杯子在远距离监控里也不大第二行为本身的画面特征不在物体本身而在物体和人体部位的相对关系上——烟不在手上而是在嘴附近杯子举起来倾向头部手机贴在耳边。这就意味着模型不能只学会认出烟还得学会认出烟出现在特定位置、特定姿态下。1.2 三类行为各自的坑先说吸烟。烟支本身是细长条颜色通常是白色或黄色在手部背景复杂的情况下特征非常弱。更难的是吸烟动作是一个连续过程手持烟自然下垂、抬手、放进嘴里、放下。真正需要判定为吸烟行为的其实是烟靠近嘴部这个关键帧但视频里大部分时间是手拿着烟在别的位置。如果模型把手持烟也判成吸烟误报率会高到没法用。喝水的问题在于杯子的多样性——纸杯、玻璃杯、保温杯、透明塑料杯材质不一样视觉特征差异极大。透明玻璃杯装水后几乎就和背景融为一体模型很容易漏检。再叠加一个因素喝水动作通常很快从举起杯子到放下可能不到两三秒如果摄像头帧率低或者推理速度慢这个动作可能直接被跳过。打电话相对好一些因为手机本身形状比较固定但问题在于遮挡——手机贴在耳朵上时手部会遮挡屏幕的大部分区域模型看到的其实是一只手捂在脸旁边。这时候手机的特征丢失模型容易被误导成手摸脸或者手抱头等其他动作。1.3 破局思路检测行为关键区域而不是完整行为想明白这些问题之后我调整了策略不去搞姿态估计算手到嘴距离也不去试图框出整个行为过程的完整状态而是把目标定义成行为关键区域。具体来说吸烟检测目标为嘴部附近的烟支而不是任意位置的烟喝水检测目标为手部举起并倾斜的杯子打电话检测目标为贴在耳边的手机这样做有一个很实际的好处标注难度和训练目标都变得清晰。YOLO系列模型天生擅长的是定位分类你把行为关键区域定义清楚了它就能学会。后续在部署时我还在后处理里加了一个连续帧确认逻辑进一步过滤掉单帧误检这个后面细讲。2. 算法选型为什么最终锁定YOLOv82.1 候选方案横向对比做行为检测市面上能用的开源方案其实不少我当时的挑选范围包括传统视觉方案、Faster R-CNN、SSD、YOLOv5、YOLOv7、YOLOv8。把这些放在一起对比方案推理速度训练难度生态成熟度在这个场景里的实际表现传统视觉肤色检测背景差分快低低光照一变化就崩误检率极高Faster R-CNN慢中中精度尚可但实时性不达标SSD中中中小目标检测能力偏弱烟头很容易漏YOLOv5快低高能达到要求但收敛过程略折腾YOLOv7快低中速度快部署资料不如v8齐YOLOv8快低高精度、速度、部署体验综合最优传统视觉第一个被淘汰原因是室内外光照一变化肤色阈值就失效了。Faster R-CNN的精度确实好但单帧推理时间太长摄像头实时检测基本没法用。SSD对小目标不友好而我们的烟头、远处的手机恰恰都是小目标直接排除。剩下的几个YOLO系列版本里我最后选了YOLOv8。2.2 YOLOv8在这个场景中的四个关键优势第一是anchor-free检测头。YOLOv5和更早版本需要预设一组锚框尺寸如果预设的尺寸跟你的目标物大小不匹配就得做聚类来调整。YOLOv8改成了直接预测中心点和宽高省掉了锚框调参这一步。我当时标注完数据后没做任何锚框聚类直接训练效果就很稳。第二是C2f模块和梯度流设计。这个结构上的改进让深层特征提取更充分。我自己的实测感受是在同样迭代轮数下YOLOv8的收敛速度比v5明显更快训练到后期loss曲线也更平滑。第三是正负样本分配策略。YOLOv8在训练时会动态决定哪些anchor算正样本不像老版本那样完全靠阈值一刀切。我标注的时候有一些边界框其实画得并不完美比如烟头框稍微偏大了一点YOLOv8的动态匹配机制会把它当作可以学习的正样本来处理容错能力明显更好。第四是生态省心。ultralytics这个库把训练、验证、导出、推理全封装好了一条命令就能跑训练模型能直接导出成ONNX和TensorRT格式。对于我这种不想跟底层细节死磕的人来说太关键了。做这种小目标行为检测时间应该花在数据和调优上不应该花在复现算法上。2.3 模型规格的取舍nano还是smallYOLOv8系列按参数量从大到小有x、l、m、s、n几个规格。我当时在nano和small之间纠结了很久。nano在CPU上跑得更快但小目标检测精度会差一些small精度好但对硬件要求高。最后的决定是开发阶段用small做实验、跑精度部署阶段根据目标设备的性能决定。如果目标设备是带GPU的直接上small如果是纯CPU先用nano跑通再考虑量化压缩。这个开发用大模型、部署用小模型的思路我觉得在行为检测项目里是通用的——先确保功能做对了再考虑速度。3. 数据集从零到可训练的全流程3.1 数据来源与采集覆盖维度模型能不能用数据是决定性因素。我整理数据时主要走了两条路一是收集公开的抽烟和手机使用相关图片数据二是在实际场景里自采照片。自采部分我总结了一套覆盖维度清单照着采集能显著减少后期某类场景下识别不稳定的问题光照维度室内日光灯、室外晴天、傍晚弱光、逆光角度维度摄像头平视、俯视、斜俯视距离维度近处约3米、中等距离约5到8米人的维度不同体型、不同肤色、是否戴帽子/眼镜道具维度香烟不同长短、水杯纸杯、透明杯、保温杯、手机不同大小、是否带壳这套维度看起来繁琐但非常值得做。我第一版数据只顾着收集动作标准的照片结果模型在逆光和戴帽子的情况下误报率明显上升后来补了这些难例数据才压下来。3.2 YOLO格式标注每一行文本背后是什么YOLO数据集的标注格式是这样的每一张图片对应一个txt文本文件文件名跟图片同名文本里每一行代表一个目标物体。一行包含五个数值类别id 中心点x坐标 中心点y坐标 目标宽度 目标高度注意坐标全部是归一化的取值范围0到1也就是用坐标值除以图片宽高。比如一张1280x720的图片里一个目标中心点在(640, 360)宽为200高为300那么这一行就是0 0.5 0.5 0.1562 0.4167我第一版标注就犯了个低级错误小目标框太大。标注烟头时下意识把整个手部一起框进去导致模型学习到的特征里手的权重过大后来推理时发现手靠近嘴但没有烟也会被误判成吸烟。正确做法是框贴着目标物边界走宁可稍微紧一点也不要松。如果有些目标是半遮挡状态比如手机被手挡住一半就按可见部分来框。3.3 数据增强策略多不等于乱YOLOv8的ultralytics框架内置了Mosaic增强、HSV色彩扰动、随机翻转等策略。Mosaic能把四张图拼成一张训练变相增加了每张训练图里的目标数量对学习小目标特别有帮助——因为我们场景里的烟头和手机在远处确实非常小。但增强不能无脑全开。我踩过一个坑喝水杯子里有透明玻璃杯HSV色彩增强往偏蓝偏亮方向拉太多之后模型把很多反光物体误判成了杯子。后来我把HSV增强强度调低并且没有开垂直翻转——因为我们采集的数据几乎全是平视或俯视角度垂直翻转会把上方和下方的概念搞乱杯子原本在嘴下面翻过去就跑到眼睛上面了这反而不符合实际语义。类别不平衡也必须处理。实话说打电话的照片最好收集吸烟其次喝水的关键区域动作杯子倾斜、靠近嘴样本最少。我的解决办法是对喝水类别做过采样——在训练配置里给这个类别更高的采样权重同时在增强时对它应用更强的随机旋转把有限的样本通过几何变化扩充出更多变体。4. 训练实战超参设置、指标分析与模型导出4.1 环境准备与训练命令环境部分其实没什么门槛我用的配置大致是Python 3.9PyTorch 2.x显卡只是普通的入门级8G显存。安装ultralytics一步搞定pip install ultralytics训练数据准备成一个标准目录结构datasets/ ├── data.yaml ├── train/ │ ├── images/ │ └── labels/ └── val/ ├── images/ └── labels/data.yaml里面写清楚类别名称和路径train: datasets/train/images val: datasets/val/images nc: 3 names: [smoking, drinking, calling]然后一条命令启动训练yolo detect train datadata.yaml modelyolov8s.pt epochs100 imgsz640 batch16 patience204.2 关键超参为什么这样设置有几个参数我需要单独解释一下。imgsz输入图片尺寸我选的是640而不是1280。很多人觉得大分辨率必然更好确实大图能保留更多小目标细节但显存占用是平方级增长的推理速度也会显著下降。在监控场景下摄像头分辨率通常是1080p模型训练时用640学习到的特征推理时对原始大图做缩放小目标信息仍然有损失。一个更实用的做法是训练用640推理时对画面按区域切块tile把大图切成小块分别检测这个方案后面详细说。batch批大小设置成16配合8G显存刚好合适。如果显存不足优先降低batch而不是降低imgsz。我的经验是batch过小比如2到4会导致训练收敛不稳因为梯度估计噪声太大。patience20表示验证集指标连续20个epoch不提升就提前停止训练。这个参数非常重要——YOLO训练到后期非常容易过拟合如果没有early stopping模型会从泛化不错慢慢退化成只认识训练图而你自己很难察觉。4.3 结果指标怎么看不要只盯着mAP50训练结束ultralytics会输出一组指标mAP50、mAP50-95以及每类的precision和recall。很多初学者只看mAP50但在这个项目里mAP50高但实际视频中频繁误检的情况非常常见。我的经验是重点看两个东西一个是每类的recall召回率。recall低意味着该类别大量目标根本没检出来。我训练时drinking类的recall一度只有0.62远低于另外两类的0.8以上。这不一定是模型的问题很可能是喝水样本太少或者标注框不一致。后来把样本补上、标注统一后recall明显回升。另一个是混淆矩阵。YOLO训练完会在验证集上生成confusion_matrix.png。我第一次看到结果时发现smoking类别的大量误检其实被分到了background类里面——也就是说模型把很多背景区域误判成了烟。这说明负样本没有目标的普通图片不够。往训练集里加入一批纯背景图再标注成empty也就是不标注任何目标这个误判立刻少了很多。4.4 模型导出从PyTorch到ONNX训练好的best.pt可以直接在Python里用ultralytics加载推理但对实际部署来说导出成ONNX更通用加载速度也更快。导出命令非常简单yolo export modelbest.pt formatonnx opset12导出后的ONNX文件可以用onnxruntime加载import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(best.onnx) input_name session.get_inputs()[0].name在CPU上ONNX Runtime比直接跑PyTorch会快不少。如果你手里的设备是GPU还可以继续导出成TensorRT格式但那一步对环境和显卡驱动要求比较苛刻属于锦上添花不必一开始就折腾。5. UI界面与实时推理把模型变成能用的工具5.1 界面框架选型PyQt5还是Gradio模型训完之后真正的挑战来了怎么让一个非技术用户顺畅地使用这个系统这就轮到UI界面出场。我当时对比了两种方案Gradio和PyQt5。Gradio是Python里非常方便的开发库几行代码就能起一个网页界面摄像头画面也能直接传上去看起来很有现代感。但它的定制能力有限如果我想做每一路摄像头独立配置告警区域检测结果按小时统计报表这类功能Gradio会变得很别扭。PyQt5做起来复杂得多但适合真正做产品的场景。最后的选型是根据使用场景定的对比维度GradioPyQt5开发速度快几小时能跑通慢需要设计界面和信号槽定制自由度中高摄像头多路接入需要额外适配天然支持QThread多线程管理方便打包分发相对简单需要PyInstaller处理适合场景快速演示、实验室验证现场部署、长期运行的安防工具如果只是给导师或同学演示一下模型效果Gradio够了。但如果你想把这套系统放到机房或者值班室让它连续跑几天不出乱子PyQt5才是更稳的选择。这篇文章的场景是后者。5.2 推理主循环与线程设计用PyQt5做实时摄像头UI一个最大的原则是模型推理绝对不能放在界面主线程里。PyQt5的界面是事件循环驱动如果在界面刷新过程中去跑模型推理界面会直接卡死摄像头画面一帧一帧地跳。我的结构是这样的一个线程专门读摄像头帧一个线程跑模型检测主线程只负责把检测结果画到界面上。摄像头读帧和推理之间用一个队列传递数据如果推理速度跟不上摄像头帧率就丢掉当前帧直接处理下一帧——这比让程序越积越慢要好得多。核心代码骨架大概是这个思路class VideoThread(QThread): frame_ready pyqtSignal(object) def run(self): cap cv2.VideoCapture(0) while self.isRunning: ret, frame cap.read() if ret: self.frame_ready.emit(frame)推理线程拿到帧后调用ONNX模型推理得到检测框和类别把结果通过另一个信号传回主线程。主线程用QPainter在QLabel上绘制矩形框和标签文字。这个分离结构还有一个好处如果后面要接多路摄像头每个摄像头对应一对读取线程推理线程互不干扰。5.3 告警与统计逻辑如何避免假报警模型输出的是每一帧的检测结果但如果直接把每个检测框都当作一次行为告警系统会非常吵——因为模型偶尔会在某个角度下把手机框错判成烟头。我的处理方式是加入连续帧确认机制同一个目标框在连续的N帧内如果都检出同一类别且中心点偏移不大IoU阈值判断是同一个目标才判定为一次有效行为。比如抽烟动作连续5帧都检出嘴部附近有烟这时候才在界面上弹告警。喝水和打电话同理。此外我还在界面里加了两个统计维度一个是各类行为的总次数另一个是按时间段生成的行为频次表。这对实际场景非常有用——比如管理者最关心的可能不是某人某刻喝了口水而是某个区域连续三个小时里打电话行为的高峰时段。这种结构化记录能帮决策者发现真正的管理问题而不是给一堆乱七八糟的单帧检测框。6. 实测部署中的坑与解决记录6.1 光照和反光的干扰第一个坑来自光照。现场摄像头装在室内靠窗位置下午两三点阳光直射进来人的脸部在逆光下几乎是一片暗。模型对吸烟的检测率骤降——因为烟支在阴影里变成了灰黑色跟背景融到一起了。我用的解决办法分两步。第一步是在图像预处理环节对低亮度帧做自适应直方图均衡化CLAHE把暗部细节提亮。第二步是回到数据层面专门补了一批逆光和暗光环境下的训练样本。双管齐下之后暗光场景的漏检率明显降低。这个经历也说明了一个道理行为检测系统的鲁棒性一多半要靠模拟真实场景的数据来支撑光调算法是不够的。6.2 类间混淆的修正第二个典型问题是喝水和打电话互相误判。从画面上看当一个人把杯子举到嘴边时手部位置在脸的下方打电话时手贴在耳朵附近。但某些角度下比如斜俯视摄像头两个位置在二维画面里非常接近。模型的目标特征是手部附近有一个物体物体类别特征不明时就会混淆。我在后处理里加了位置先验修正如果检出的是drinking类别且目标框中心点位于图片上部靠近耳朵区域则降低它的置信度如果检出的calling类别目标中心点位于图片下部同理降低。这种基于先验知识的后处理代码量不大但对精度提升很有帮助。6.3 小目标漏检与切块推理第三个坑是最顽固的远距离小目标漏检。摄像头装在车间或者走廊角落时人的脸部可能在整幅1080p画面里只占很小的区域烟头更是只有十几个像素。YOLOv8在640分辨率下训练对这种极小目标天然不敏感。尝试过直接提高推理分辨率到1280效果有提升但速度几乎减半。最后采用的方案是切块推理tiling先在全图上做一次低分辨率检测找到可能有人脸/手部出现的候选区域再把这些区域切出来放大单独过一遍模型检测小目标。这个两级策略在工程上很常见性能开销相对可控而且烟头和手机的检测率提升非常明显。6.4 CPU推理性能的兜底策略最后说说性能。这套系统如果是跑在带GPU的工控机上完全不用操心帧率问题。但实际部署中很多现场只有普通办公电脑CPU推理就成了瓶颈。在纯CPU环境下我最后的配置是YOLOv8n ONNX Runtime 隔帧处理每2帧处理1帧 显示线程与推理线程分离。这样在普通电脑上能保持画面流畅且检测结果基本连续。如果检测响应速度还不够可以考虑把画面缩放后再送模型推理或者用更多线程跑ONNX Runtime需要按实际情况测试线程并不是越多越快。这个优化思路的核心是接受不完美行为检测任务对单帧的精确度容忍度较高当前帧没检测到下一帧再补上配合前面说的连续帧确认机制最终效果依然可用。这也是我在这个项目里学到的一个重要经验——实时检测系统不是做单帧竞赛而是要在整个时间序列上做稳定判断。最后分享一个个人体会。这套系统从训练到部署真正花时间的地方其实不在模型本身而在认清场景中的难点这一步。行为检测的关键是你得先想明白你的摄像头装在什么位置、灯光怎么样、目标离得有多远、检测结果要用来做什么。这些信息决定了你要怎么定义检测目标、怎么采集数据、怎么做后处理。把这些想透了YOLOv8这样一个成熟的框架加上一份用心整理的数据集就已经足够撑起一个能落地使用的系统。后续如果想扩展可以考虑加入行为时序语义比如用短时间窗口内的检测序列来判断更复杂的连续动作也可以把单摄像头换成多视角融合。先把现在这套跑稳再谈更大也来得及。
阅读完成 · 觉得有帮助?
咨询建站