news 2026/9/12 22:48:01

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于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或labelme,yolov5原生支持这两种工具导出的格式。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', encoding='utf-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,烟头的橙色是重要特征,色相偏移过大会让模型学到错误的颜色关联,反而增加误检。

除了增强策略,另一个关键参数是训练分辨率。默认的imgsz=640对烟头来说太低了,建议至少用到img=1280。在显存够的情况下,把推理分辨率从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转torchscript:

python 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 \ --onnx=best.onnx \ --saveEngine=best.engine \ --fp16 \ --minShapes=images:1x3x1280x1280 \ --optShapes=images:1x3x1280x1280 \ --maxShapes=images:4x3x1280x1280

这套配置里--fp16是性能翻倍的关键,但前提是烟头检测对精度损失不敏感——实际测试中fp16在吸烟检测上的掉点通常在0.5%以内,可以放心开。--minShapes和--maxShapes要按实际推理batch来设,动态shape的引擎启动会变慢,如果固定单路视频流推理,直接固定shape能省去动态shape的额外开销。

4.2 单帧检测不够,滑窗判定才是吸烟行为的本质

吸烟是一个持续数秒钟的动作,单帧检测的抖动和误报在视频流里会被放大。部署推理时维护一个关键点缓存队列,对每个烟头目标记录它的检测框中心点和置信度,当连续三帧中出现位移小于阈值的目标,就判定为同一目标并累加计数。这个逻辑用python伪码表示:

class SmokingDetector: def __init__(self, frame_buffer=5, score_thres=0.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_options=ort.SessionOptions() ) # 关键参数:线程数设成物理核心数而不是逻辑核心数 sess.set_session_configs(intra_op_num_threads=4)

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增强不让色相偏太多的原因。用视频回放的方式做验收,一次就能看清模型的色彩鲁棒性到底行不行。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 22:46:54

开源轻量Markdown编辑器mdput实测:能否成为Typora的免费平替?

说实话&#xff0c;我是从 Typora 0.9 时代一路用过来的老用户。当时它靠实时渲染和极简界面吸引了一大波 Markdown 写作爱好者&#xff0c;我也顺手把笔记、博客草稿、项目文档全部丢进去了。后来 Typora 转成收费模式&#xff0c;官方提供免费试用&#xff0c;但过期后那个购…

作者头像 李华
网站建设 2026/9/12 22:45:18

基于STM32F103C8T6的OLED万年历:RTC闰年算法与DHT11传感器实战

简介&#xff1a;基于STM32F103C8T6单片机打造的智能万年历设计&#xff0c;涵盖OLED屏显示年月日时分秒、闰年自动调整&#xff0c;以及DS18B20与DHT11实时温湿度采集&#xff0c;面向单片机课程设计、毕业设计及物联网入门学习者&#xff0c;可直接作为数字时钟类项目的参考模…

作者头像 李华
网站建设 2026/9/12 22:44:49

家庭实验室HTTPS困境破局:自建根CA实现零配置内网证书管理

1. 为什么“自托管家庭实验室”正在集体陷入认证泥潭&#xff1f;“这款极简认证神器&#xff0c;拯救你的自托管家庭实验室&#xff01;”——标题里没提名字、没列参数、甚至没写一句功能描述&#xff0c;却让无数在NAS、树莓派、Home Lab里折腾了三年以上的人心头一震。不是…

作者头像 李华
网站建设 2026/9/12 22:44:47

ASP.NET实现汽车制造业文件批量上传解决方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 22:40:06

今日头条新闻文本分类数据集处理全流程:清洗、建模与泄漏验证

简介&#xff1a;今日头条中文新闻&#xff08;文本&#xff09;分类数据集是面向自然语言处理入门者的中文新闻分类语料&#xff0c;覆盖国际、国内、娱乐、体育等多个类目&#xff0c;适合用来练习文本分类全流程。整个压缩包共四份文件&#xff0c;含两份说明文档、一个数据…

作者头像 李华
网站建设 2026/9/12 22:35:42

Apache POI 替代 EasyExcel:Java Excel 复杂场景精准控制指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华