ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于YOLOv5的吸烟行为检测实战:优化小目标与部署防误报

基于YOLOv5的吸烟行为检测实战:优化小目标与部署防误报 简介基于Yolov5的吸烟行为检测资源面向计算机视觉开发者与安防监控领域从业者提供从模型训练到实时检测的完整工程方案。资源包共100个文件涵盖41个Python脚本、31个YAML配置文件、2个预训练模型权重以及Dockerfile、Jupyter Notebook教程、说明文档等既有可直接调用的推理代码也有用于二次开发的配置与容器化部署支持。压缩包整体约25.74MB体积精简易用已有334人学习下载。内容覆盖数据预处理、模型加载训练、实时检测全流程可帮助读者理解Yolov5架构与吸烟行为特征学习过程支持调整检测阈值、扩展行为类别适用于智慧工地、公共安全等场景。1. 拿到“基于yolov5的吸烟行为检测源码模型.zip”先想清楚要改什么这个标题在高校毕设和个人项目中非常常见一个压缩包里放着yolov5工程目录、一份标注好的数据集配置、一个训练好的best.pt权重以及若干推理脚本。很多人解压后第一件事是python detect.py跑通demo看到画面里框出“smoke”就算项目完成——但这样交付的项目到现场十个有九个会翻车。吸烟检测和普通的目标检测不一样烟头是极小目标在1080p画面里可能只有10×10像素手和烟的组合形态又和打火机、笔、手指高度混淆更麻烦的是摄像头视角通常不是正对桌面而是斜上方或远处目标形变严重。真正决定这个项目成败的不是yolov5本身能不能跑通而是你如何针对“小目标、高误报、连续帧”这三个特性去做数据标注和推理策略调整。本文就按“数据准备→训练调参→部署优化→误报治理”这条线把这个zip变成一套能扛住真实摄像头场景的方案。2. 先把数据关过了标注边界、小目标增强与类别平衡2.1 标注策略先定下来框烟头而不是框手、框嘴吸烟检测有一个常见的误区觉得检测目标是“人吸烟”这个动作于是去框整只手、框嘴附近区域甚至框整个上半身。这样训练出来的模型有两个毛病一是框太大导致定位粗糙二是类别语义不干净。吸烟动作是一个连续过程烟头和嘴、手的相对位置关系随时变化真正稳定的视觉特征是“燃烧的烟头”和“夹烟的手型”其中烟头因为是亮橙色、高温区域在图像上有强对比度是最好学、最稳定的锚点。我一般会把标注策略定为只标注烟头同时把类别名从smoking改成cigarette。如果你手里只有一份已经按“人”或“手”标注的数据集也不要急着重标可以在训练时把类别合并但推理时仍然只输出烟头框再用手部检测或人脸位置做二次规则判断。这样做的理由是yolov5对类别数量的变化非常敏感一个类别和两个类别的训练收敛速度和误报率差很多。标注工具建议用labelImg或labelmeyolov5原生支持这两种工具导出的格式。labelImg导出的是VOC XML需要转成yolo txt格式labelme导出json需要写脚本转。贴一个常见转换脚本片段import os import json from PIL import Image def labelme_to_yolo(json_path, out_dir, class_map): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w, img_h data[imageWidth], data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue # labelme 用多边形或矩形这里统一取外接矩形 xs [p[0] for p in shape[points]] ys [p[1] for p in shape[points]] x1, y1, x2, y2 min(xs), min(ys), max(xs), max(ys) # 归一化到 0-1同时防止越界 cx ((x1 x2) / 2.0) / img_w cy ((y1 y2) / 2.0) / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h lines.append(f{class_map[label]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(out_dir, os.path.basename(json_path).replace(.json, .txt)), w) as f: f.write(\n.join(lines))脚本的核心逻辑是把labelme的json标注转成yolo格式的txt文件每个txt文件一行一个目标格式是“类别id 中心点x 中心点y 宽 高”全部归一化到0~1。需要注意的点是越界处理烟头贴近画面边缘时外接矩形可能超出图片边界yolov5训练时这类边框会被忽略或产生异常loss最好是先裁剪掉越界部分再归一化。2.2 小目标增强是这类的命门Mosaic和Copy-Paste怎么取舍吸烟检测的训练样本里烟头在整张图中的占比通常不到2%yolov5默认的Mosaic增强对这类极小目标反而是负担。Mosaic把四张图拼成一张相当于把每个目标再缩小一倍对小目标检测器来说等于雪上加霜。这是yolov5在COCO上效果好但在吸烟检测这种极端小目标场景下效果平平的直接原因。一个实用的做法是关掉Mosaic或把mosaic概率调低同时开启Copy-Paste增强。Copy-Paste能把烟头区域从原图裁剪出来随机粘贴到其他图的任意位置既增加了目标的出现频次又模拟了不同背景下烟头的形态变化。在yolov5的hyp.scratch-low.yaml里这样改mosaic: 0.5 # 默认1.0调低到0.5 copy_paste: 0.3 # 默认0.0开启 mixup: 0.0 # 小目标场景不建议开mixup容易丢细节 hsv_h: 0.015 # 色相增强幅度保持小值烟头橙色调不能乱偏 hsv_s: 0.7 hsv_v: 0.4这些超参数的调整逻辑是mosaic和mixup都会改变目标在图像中的尺度分布对小目标不友好而copy-paste对尺度没有影响只是换了背景。hsv增强里最敏感的是色相hsv_h烟头的橙色是重要特征色相偏移过大会让模型学到错误的颜色关联反而增加误检。除了增强策略另一个关键参数是训练分辨率。默认的imgsz640对烟头来说太低了建议至少用到img1280。在显存够的情况下把推理分辨率从640提高到1280小目标的mAP通常能提升10个点以上。代价是训练时间变长、显存占用翻倍如果你只有一块8G显存的卡可以把batch降到8或者用yolov5n这种轻量结构。2.3 类别不平衡和数据清理的土办法自己采集的数据集最常见的问题是正样本有烟头的帧远多于负样本没有烟头的帧。训练时负样本不能完全不加否则模型会疯狂把打火机、笔芯、白色反光点都当成烟头。负样本的配比我一般控制在正负比 3:1 到 2:1。负样本不需要一张张标只要保证没有遗漏目标图片放到images目录下就行。另外值得做的一步是把模糊帧、严重过曝的帧直接删掉。吸烟检测要用在室内监控场景过曝会直接把烟头的高温特征抹平模型学到了这种坏样本会让白天和晚上的表现差异变大。清理时写一个简单脚本统计每张图的亮度均值亮度分布跨越很大的帧优先检查一遍。3. 模型选型与训练流程从yolov5n到yolov5s别一上来就跑大模型3.1 yolov5网络结构里哪些层对吸烟检测最敏感yolov5的网络结构看似统一但针对不同任务选择不同depth_multiple和width_multiple会得到截然不同的效果。这两个参数决定了CSPDarknet的深度和通道数。具体到吸烟检测影响最大的是检测头部分的anchor尺寸。默认anchor是针对COCO的较大目标设计的最小尺寸是10×13也就是在640分辨率下一个目标至少要有10×13像素才能被第一层检测头感知。烟头的像素占比远低于这个值。所以拿到的zip里如果self._model.pt是直接拿官方预训练权重不做任何修改去训练自己的数据集效果一定会差。正确路径是加载coco预训练权重在自定义数据集上微调。在yolov5目录下执行python train.py \ --data smoking.yaml \ --weights yolov5s.pt \ --img 1280 \ --batch 16 \ --epochs 150 \ --cache ram \ --hyp hyp.scratch-low.yaml \ --device 0参数说明--data指向你写好的smoking.yaml数据集配置--weights使用coco预训练权重可以大大缩短收敛时间比从零训练减少一半以上的epoch--img 1280是把输入分辨率抬高到1280这是针对小目标最直接有效的一步--cache ram把图片缓存到内存训练速度能快很多前提是你的内存大于16G--hyp选择增强配置较低的超参文件配合前面说的关闭mosaic、开启copy_paste。yolov5会自动做autoanchor重新聚类anchor不需要手动改anchor配置。但训练开始后建议看日志里anchors的收敛值如果聚类结果里最小的anchor仍然大于6×6说明数据集里烟头的标注框还是偏大需要回头检查标注是否框得比烟头大太多。3.2 用yolov5n换实时性用yolov5m换准确率如果最终要跑在Jetson Nano或者树莓派这类边缘设备上就别用yolov5s了。yolov5s在Jetson Nano上用TensorRT加速也只能跑到10~15 FPS而yolov5n可以稳定跑30 FPS。准确率差距在小目标场景下确实存在但可以用一个技巧弥补把yolov5n的检测结果做多帧融合用一个滑窗去判断连续5帧里同一位置是否都有烟头检出有才触发报警。这样单帧准确率的损失被时间维度的置信度补偿了实时性和准确率都能保住。用yolov5n重训的命令如下python train.py \ --data smoking.yaml \ --weights yolov5n.pt \ --img 960 \ --batch 32 \ --epochs 150 \ --device 0 \ --exist-ok这里把分辨率从1280降到960换取n模型在小显存设备上的推理速度。--exist-ok是允许覆盖已有训练结果适合反复调参的场景。3.3 训练完必看的三个指标文件训练结束后不要只盯着P曲线和R曲线看小目标检测场景最容易出现的是precision高但recall低也就是模型倾向保守只检确定性极高的目标导致漏报。打开runs/train/exp/下的confusion_matrix.png重点关注cigarette这一类有多少真实样本被预测成了background。如果这个比例超过15%说明模型对烟头的特征学习不够充分优先回去调整增强策略而不是加训练轮数。另一个必看的是results.csv里的mAP_0.5:0.95这个值对烟头这种小目标非常苛刻通常只有0.3~0.5比mAP_0.5能低一半还多。只要mAP_0.5能到0.85以上对于报警场景就算合格不要追求mAP_0.5:0.95的数值。4. 部署推理与连续帧判定把“检测到烟”变成“判定正在吸烟”4.1 导出成ONNX再上TensorRT别直接跑python推理训练得到的best.pt直接用torch推理在GPU上效果尚可但边缘设备上性能很差。常见做法是先导出ONNX再转TensorRT引擎或者直接用自带的export.py转torchscriptpython export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --img 1280 \ --batch 1 \ --opset 12 \ --simplify导出参数中--simplify会调用onnx-simplifier做计算图优化把常量折叠、冗余节点删掉对推理性能有直接帮助。--opset 12是兼容性和算子的平衡点太高的opset在旧版onnxruntime上跑不了。导出完成后可以用onnxruntime自带的session.run做一次前向验证确认输出shape和检测数量是否符合预期。如果你要上TensorRT建议直接用trtexec离线转engine/usr/src/tensorrt/bin/trtexec \ --onnxbest.onnx \ --saveEnginebest.engine \ --fp16 \ --minShapesimages:1x3x1280x1280 \ --optShapesimages:1x3x1280x1280 \ --maxShapesimages:4x3x1280x1280这套配置里--fp16是性能翻倍的关键但前提是烟头检测对精度损失不敏感——实际测试中fp16在吸烟检测上的掉点通常在0.5%以内可以放心开。--minShapes和--maxShapes要按实际推理batch来设动态shape的引擎启动会变慢如果固定单路视频流推理直接固定shape能省去动态shape的额外开销。4.2 单帧检测不够滑窗判定才是吸烟行为的本质吸烟是一个持续数秒钟的动作单帧检测的抖动和误报在视频流里会被放大。部署推理时维护一个关键点缓存队列对每个烟头目标记录它的检测框中心点和置信度当连续三帧中出现位移小于阈值的目标就判定为同一目标并累加计数。这个逻辑用python伪码表示class SmokingDetector: def __init__(self, frame_buffer5, score_thres0.5): self.frame_buffer frame_buffer self.score_thres score_thres self.history [] # 每帧的检测结果 self.confirmed {} def update(self, frame_id, detections): # detections: list of (cx, cy, score) self.history.append((frame_id, detections)) self.history [h for h in self.history if frame_id - h[0] self.frame_buffer] # 跟踪同一目标的简易办法按位置距离聚类 current_hits {} for tid, (fid, dets) in enumerate(self.history): for cx, cy, score in dets: if score self.score_thres: continue # 用前一帧最近距离匹配这里简化为网格哈希 key (round(cx / 50), round(cy / 50)) current_hits[key] current_hits.get(key, 0) 1 return current_hits这段代码的思路是用网格哈希对连续帧中的烟头位置做粗匹配然后在网格上统计命中次数。超过3次命中即可确认吸烟行为并触发报警。好处是不需要引入ByteTrack之类的跟踪器在算力受限的板卡上能跑得动缺点是对目标移动速度敏感烟头移动过快时网格哈希会断裂此时可以把网格调大到80像素或者改用欧氏距离最近邻匹配。4.3 CPU推理条件下的替代方案并非所有场景都有GPU。如果只能跑CPU上的onnxruntime要把输入分辨率从1280降回640同时把模型换成yolov5n。此时精度损失比较明显但配合上面的滑窗判定依然能保证一定的实用性。用onnxruntime跑CPU推理的命令很简单但有一个容易被忽略的配置项import onnxruntime as ort sess ort.InferenceSession( best.onnx, providers[CPUExecutionProvider], sess_optionsort.SessionOptions() ) # 关键参数线程数设成物理核心数而不是逻辑核心数 sess.set_session_configs(intra_op_num_threads4)intra_op_num_threads设置得太高反而会因为线程切换开销导致延迟上升4~6个物理核是常见甜点值。另一个优化是开启ORT的图优化级别为ORT_ENABLE_ALL可以在加载时自动融合部分算子。这两项叠加通常能把CPU推理速度提升20%~40%。5. 现场误报调优三板斧降阈值、做RoI、查数据分布5.1 用小目标专项数据把置信度阈值拉回0.3训练完的模型默认置信度阈值是0.25这在一般目标检测里没问题但在吸烟检测场景会造成大量误报。打火机的火苗、白墙反光、烟灰缸里的烟头残留都会被框出来。正确做法是把置信度阈值提高到0.5~0.6然后用针对性数据补充训练把真正烟头的置信度顶上去。如果补充数据不方便就在推理脚本里对宽度小于32像素的检测框额外加一个惩罚因子def adjust_score(box, raw_score): w, h box[2] - box[0], box[3] - box[1] area_ratio (w * h) / (1280 * 1280) if area_ratio 0.001: return raw_score * 0.7 # 小框降权 return raw_score这个惩罚因子的逻辑是烟头虽然是极小目标但训练充分的模型对它的置信度普遍在0.6以上。如果一个框面积占比不到千分之一且置信度在0.5以下大概率是背景噪声降权后过不了阈值能有效清理一部分零散误报。5.2 置信度阈值与NMS的联动yolov5在导出模型后通常会把NMS放在后端实现。推理脚本里的NMS参数对结果影响很大。默认的IoU阈值是0.45吸烟检测场景建议调到0.5~0.6因为烟头目标小两个烟头距离很近时容易被NMS合并掉一个。而score_thres建议放在0.3而不是0.25配合上面的面积降权整体误报能下降一大截。另外一个隐蔽细节yolov5的detect.py默认每帧独立做NMS如果你在滑窗判定里已经做了多帧去重单帧NMS的IoU阈值就可以放得更宽保留更多候选框给后续时序判定。5.3 只在RoI区域做识别把误报半径缩小摄像头画面里不是所有区域都需要检测吸烟行为。把画面三分天下左上区域通常是过道中间区域是工位右下角是窗户——窗户上的光斑和外面移动的车辆是天然干扰源。部署时画一个多边形RoI检测框的中心点不在RoI内就直接丢弃。这个处理能直接砍掉40%以上的室外场景误报。RoI的配置不要写死在代码里用一个json文件存多边形顶点坐标现场调试时用鼠标在画面上点几个点就能完成配置。这是整个系统里性价比最高的一个功能但很多从网上下载的zip源码里没有实现。自己补上后尤其适合工厂车间、工地出入口这类固定机位场景。输出几行负载日志能很直观地看到指标提升RoI启用前单路视频每10分钟平均误报6.2次启用后变成1.5次再配合滑窗三次命中判定最终基本能做到两到三天零误报。先看误报日志再调参比盲目调置信度阈值靠谱得多。5.4 批量验证视频回放而不是看几张测试图训练时随手抽几张测试集图片看效果觉得不错就上线后面大概率会返工。更实际的做法是准备一段30分钟的现场视频跑完推理后把每一帧的检测结果叠加的框画到一个新视频里人眼快速浏览同时导出每条报警记录对应的时间戳和置信度。回放时重点关注三个时间段中午阳光直射、傍晚光线变暖、晚上灯光开启这三个时间段的颜色分布差异是吸烟检测误报的三大来源。比如暖色灯光下人的肤色会偏橙与烟头颜色接近——这也是前面hsv增强不让色相偏太多的原因。用视频回放的方式做验收一次就能看清模型的色彩鲁棒性到底行不行。本文还有配套的精品资源点击获取
返回列表