十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

基于YOLO的吸烟检测实战:小数据集训练、调参与部署全流程

基于YOLO的吸烟检测实战:小数据集训练、调参与部署全流程 简介这是一份面向 YOLO 系列目标检测算法的吸烟与烟盒识别数据集压缩包约 241.97MB共 2000 个文件。数据集已经划分好训练、验证、测试集并附带可直接加载的 data.yaml 配置文件适用于 yolov5、yolov7、yolov8、yolov9、yolov10、yolo11 等主流算法。标注信息同时提供 YOLO 格式 txt 文件与 VOC 格式 xml 文件分别保存在独立文件夹中txt 格式按类别、中心点坐标、宽高的归一化数值记录便于快速读取xml 格式则保留更完整的检测框信息便于格式转换或二次处理。对于正在搭建吸烟检测项目或需要标准数据集进行模型训练验证的开发者拿到后即可省去手动标注和划分数据集的繁琐步骤直接进行算法调试与效果对比。目前已有 132 人学习使用适合目标检测入门及工程实践参考。1. 吸烟检测比想象中难YOLO算法与2117张带标签图像能解决什么问题真正做过工地、厂区、加油站监控改造的工程师会有一个共识吸烟检测是所有行为识别需求里最容易被低估的。烟雾半透明、会扩散、没有固定边缘直接用YOLO算法识别烟雾云、雾气、水蒸气都会成为误报源。落地方案因此常把吸烟行为拆成稳定可检测的信号手边烟支、嘴边烟支、以及作为上下文线索的烟盒。2117张带标签图像量级不大但足够跑通YOLO算法从训练、评估、调优到导出的完整闭环。它适合刚上手目标检测、想用真实数据走一遍流程的人也适合做工地吸烟识别可行性评估的团队。这个量级训练不出通用模型但足以验证流程、看清效果水位。2. 拿到吸烟烟盒数据集后先做这四步数据准备数据解压之后不要急着找训练脚本。先检查解压出来的目录结构images里是jpg或png原图labels里每个txt对应一张同名图片txt里没有额外注释、没有逗号分隔符只有纯数字行。这是标准YOLO目录结构但下载来的数据并不保证干净类别命名、标签边界、图像尺寸都可能不一致。我们的目标是建立一份训练和验证都能稳定复现的结构哪怕目录里已经带了train/val子文件夹也要自己过一遍。2.1 读懂YOLO标签格式每个txt是归一化坐标YOLO标签的一行描述一个目标class_id x_center y_center width height四个坐标值都是相对于图像宽高的归一化比例取值范围0到1。这和COCO的JSON格式、VOC的XML格式都不同。从公开数据集转包过来时最常见的错误是忘了做除法把像素坐标当成归一化坐标写入训练出来的模型置信度会异常低。看一个标签文件的内容# 读取单个标签文件并解析 label_path labels/train/000001.txt with open(label_path, r, encodingutf-8) as f: raw_lines f.readlines() print(f文件共 {len(raw_lines)} 个目标) for i, line in enumerate(raw_lines[:5]): parts line.strip().split() cls_id, x_c, y_c, w, h [float(p) for p in parts] # 中心点和宽高都应在 (0, 1) 区间 print(f目标 {i}: 类别 {int(cls_id)}, 中心 ({x_c:.3f}, {y_c:.3f}), 尺寸 ({w:.3f}, {h:.3f}))这段代码把每行拆成五个浮点数width或height小于0.001、大于1都属于异常需要回看原图确认。这里最难排查的情况是标签坐标数值正常但框只包住了烟盒一角中心点偏离真实位置。这种问题代码看不出来必须可视化标签。2.2 用脚本统计标签质量类别数、目标尺寸与异常框可视化标签放到后面统一做先跑一段全量统计判断数据整体面貌import os from collections import Counter label_dir labels/train cls_counter Counter() size_list [] for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue with open(os.path.join(label_dir, fname), r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(f格式异常: {fname} - {line}) continue cls_id int(float(parts[0])) w float(parts[3]) h float(parts[4]) cls_counter[cls_id] 1 size_list.append((w, h, fname)) print(类别统计:, cls_counter) small [s for s in size_list if s[0] 0.05 and s[1] 0.05] print(f宽高都小于5%的小目标数量: {len(small)}, 占比 {len(small)/len(size_list):.1%})统计输出的第一个信息是各类别数量级。如果烟盒类只有几十个烟支类一两千后续训练时模型会更倾向于学样本多的类。第二个信息是小目标占比宽高均小于0.05说明目标只占图像面积的0.25%在1080p监控画面里对应的像素不足100个这基本属于小目标检测范畴后面第4章的imgsz设置要针对它调整。2.3 划分训练集和验证集按场景不要按文件名随机2117张图像按85%和15%划分训练与验证就够。关键是划分维度。监控类数据通常来自不同时段、不同摄像头如果随机划分时正巧把同一个角度的帧同时分进训练集和验证集验证指标会虚高模型部署到新场景立刻崩。常见做法是数据来源划分维度说明连续视频帧按时间段切分同一场景前80%帧训练后20%帧验证多个摄像头按摄像头切分保证训练和验证的场景不重叠图片采集按拍摄日期或地点切分避免同一批次重复图片泄漏做好划分后把图像按train和val目录物理复制或软链。YOLOv8的YAML配置指向images/train和images/val即可不需要额外生成索引文件。如果数据里自带划分也要抽查是否满足上面三条规则。2.4 合并类别把行为拆到干净的两类再训练二手数据集里经常出现cigarette、smoker、smoking_box这类语义重叠的类别名。YOLO检测器不在意类别叫什么在意的是类别是否语义互斥。如果“烟支”和“手持烟”两类同时存在它们的框会大量重叠训练时梯度互相干扰。建议合并成两个干净类别smoke正在冒烟的烟支或烟头和pack烟盒。若数据集只有单一类别直接保留即可不要为了凑类别数去细分。合并过程先跑2.2的统计拿到旧类别ID再按映射重写标签import os # 旧类别ID到新类别ID的映射按上一段统计结果自行修改 cls_map {0: 0, 1: 1, 2: 0, 3: 1} for fname in os.listdir(labels/train): if not fname.endswith(.txt): continue path os.path.join(labels/train, fname) with open(path, r) as f: lines f.readlines() new_lines [] for line in lines: parts line.strip().split() old_cls int(parts[0]) if old_cls not in cls_map: continue new_lines.append(f{cls_map[old_cls]} { .join(parts[1:])}\n) with open(path, w) as f: f.writelines(new_lines)映射表的key是旧标签类别IDvalue是新类别ID。执行前把原始标签目录复制一份重写不可逆。合并后再次运行2.2的统计脚本确认类别数量从偏差过大变成可接受范围。到这里数据层面的坑基本清完可以进入训练。3. 用YOLOv8训练吸烟烟盒数据集环境、YAML与命令数据准备完毕进入训练。Ultralytics把数据加载、增强、训练、导出封得完整对2117张这种小数据集友好也支持v5、v11等模型权重迁移。如果你有旧项目依赖、坚持用YOLOv5本文命令同样适用只是yolo命令要换成对应的train.py。3.1 安装ultralytics并确认版本安装和验证pip install -U ultralytics yolo --version输出会显示ultralytics版本号和Python环境信息。建议在独立conda环境或venv里安装避免与生产环境冲突。显卡可用时先确认CUDA和PyTorch协作正常python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))这里输出True说明GPU可用输出False也别放弃2117张图用CPU能跑只是100轮可能要数小时。另外ultralytics会自动下载yolov8n.pt预训练权重网络受限时提前手动下载并放到项目根目录。3.2 写数据集YAML路径与类别映射Ultralytics用YAML描述数据集我一般这样组织# smoke_pack.yaml path: /data/smoke_pack train: images/train val: images/val names: 0: smoke 1: packpath是数据集根目录的绝对路径train和val是相对path的目录。names里的顺序必须和标签文件里的类别ID一致ID0对应smokeID1对应pack。如果数据集原本只有类别0names里只写0: smoke。一个容易被忽略的点图像尺寸不需要在YAML里声明ultralytics会读取原图尺寸并按训练时的imgsz参数做letterbox缩放。3.3 训练的最小命令与参数说明训练命令yolo detect train \ datasmoke_pack.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ patience40 \ projectsmoke_detection \ nameexp1 \ device0显存有限时batch降到8或4显存充足也可以batch32加速收敛。第一次训练建议先用nano模型yolov8n.pt把流程跑通确认数据无问题后再换yolov8s.pt提精度。参数本次任务推荐值说明epochs100-150小数据集加大迭代容易过拟合靠patience早停imgsz640或1280监控原图配小目标时优先1280batch4-32由显存决定越小越稳patience30-50验证集mAP连续N轮不提升即停止close_mosaic10-15最后若干轮关闭mosaic增强optimizerauto小数据集可显式指定AdamWfreeze0-10迁移学习时冻结backbone前若干层训练日志里每行一轮重点看val/box_loss和metrics/mAP50。多数情况下第1轮mAP50就有一定数值因为预训练权重带回了烟盒这类近似物体的特征如果第1轮mAP50是0优先怀疑标签格式和YAML类别映射是否对得上。3.4 用预训练权重还是随机初始化2117张图像属于典型的中小规模数据集强烈建议从yolov8n.pt或yolov8s.pt继续训练而不是从头随机初始化。预训练模型在COCO上学到的边缘、纹理特征能直接迁移到烟支、烟盒这种小物体上。若担心数据分布与COCO差异太大可以冻结前若干层只做微调yolo detect train ... modelyolov8n.pt freeze10freeze10表示冻结模型前10层参数只训练后面层。作用是在数据量不足时防止底层特征被破坏。训练后发现过拟合加大freeze层数欠拟合则逐步减少。冻结层数不是固定值和模型结构、数据规模都有关系实际以验证集指标为准。4. 调优与防误检吸烟检测的5个关键参数与标注复盘第一轮训练完的结果通常不够拿去演示。误检、漏检都会冒出来这一章集中解决提升精度和遏制误报的关键点。4.1 imgsz与原始分辨率的匹配小目标的天花板工地摄像头的常见画面是1920×1080人站在十米外烟盒在画面上只有20×30像素左右。imgsz640会把图像缩到三分之一烟盒只剩不到8像素检测器看到的是一团噪点。解决思路有两个方向把imgsz提到1280图像只缩小1.5倍目标从20像素涨到27像素mAP能提升好几个点。推理阶段用SAHI切片推理把大图切块每块放大后独立检测再合并结果。训练阶段继续用1280即可不需要切片。imgsz提高的代价是显存占用和推理耗时翻倍实际项目里先在验证集对比imgsz640和1280两版的mAP再决定是否长期用1280。4.2 数据增强参数这里最容易帮倒忙Ultralytics默认开启mosaic、mixup、hsv增强对自然场景目标检测很有帮助但吸烟检测场景里它们可能帮倒忙。mosaic把四张小图拼成一张烟支细长、烟盒小的时候拼图后产生大量半截目标假样本。mixup把两张图的像素叠加目标边缘更模糊。训练到后期mosaic需要关闭让模型适应真实分布这就是close_mosaic参数。小目标占比高时干脆从头把它关掉# 在训练命令中覆盖增强参数 mosaic: 0.0 mixup: 0.0增强参数默认值吸烟场景建议原因mosaic1.00.0或最后15轮关闭拼图切断细长目标mixup0.00.0像素叠加让边缘模糊hsv_h0.0150.02-0.03增强光照泛化fliplr0.50.5左右翻转对目标无影响hsv_h、hsv_s、hsv_v可以考虑保留。烟盒颜色在不同光照下差异本来就大多一点颜色扰动是好事。4.3 类别不平衡烟盒远少于烟支时的处理标注统计里最常见的比例是烟盒类只有烟支类的五分之一。YOLO的损失由分类损失和回归损失组成样本多的类会主导梯度方向。处理办法按优先级排只训练烟支一类。如果业务只需要识别吸烟行为烟盒作为上下文不是必须输出。对烟盒类做复制粘贴增强把同一张图里的烟盒复制到其他位置生成额外训练样本。使用类别权重。Ultralytics原生不在YAML里直接支持需要自定义采样器复杂度较高一般先用前两种。先试第1种。很多时候砍掉烟盒类只保留smoke整体mAP不降反升。4.4 推理时的置信度阈值与NMS设置训练后的模型默认conf0.25、iou0.7。在吸烟检测这类要求低误报的场景conf可以提到0.35甚至0.45。阈值越高误检越少但召回率会掉。调阈值时看验证集的PR曲线找precision和recall的平衡点。yolo detect predict \ modelruns/detect/smoke_detection/exp1/weights/best.pt \ sourcetest_video.mp4 \ conf0.4 \ iou0.7 \ imgsz1280iou是NMS的IoU阈值值越小抑制越强。同一目标附近出现大量重叠框时适当调低iou减少重复框。烟支目标细长、两个目标挨得近时iou设太高会把相邻目标合并掉降到0.55到0.6更稳。4.5 标注复盘用可视化脚本找出框错了的样本代码看不出标注语义用OpenCV批量画框抽查是必须的一步import cv2, os img_dir images/val label_dir labels/val out_dir check_visual os.makedirs(out_dir, exist_okTrue) for fname in os.listdir(img_dir): if not fname.endswith(.jpg): continue img cv2.imread(os.path.join(img_dir, fname)) h, w img.shape[:2] label_path os.path.join(label_dir, fname.replace(.jpg, .txt)) if not os.path.exists(label_path): continue with open(label_path) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) x_c, y_c, bw, bh map(float, parts[1:]) x1 int((x_c - bw / 2) * w) y1 int((y_c - bh / 2) * h) x2 int((x_c bw / 2) * w) y2 int((y_c bh / 2) * h) color (0, 255, 0) if cls_id 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imwrite(os.path.join(out_dir, fname), img)输出到check_visual目录后用看图软件滑动浏览。重点找这几类错误标注框只有烟头没有烟身、烟盒标签标成烟支、整只手被框进去。这些错误比参数错误更隐蔽因为它们不报错只是让模型学到错误特征。5. 吸烟检测模型验证与落地从mAP到摄像头实时推理训练完不要只在测试集上看两张截屏要用指标和实际推理两条路径验证再落到真实场景里跑。5.1 看指标而不是看心情mAP50与混淆矩阵跑一次验证集评估from ultralytics import YOLO model YOLO(runs/detect/smoke_detection/exp1/weights/best.pt) metrics model.val(datasmoke_pack.yaml, splitval) print(mAP50:, metrics.box.map50) print(mAP50-95:, metrics.box.map)mAP50是IoU阈值0.5下的平均精度工业场景看这个就够mAP50-95更严格学术对比才重点参考。对2117张的小数据集mAP50在0.85以上说明数据标注和训练基本可接受低于0.7就回到第2章重新检查标签。5.2 导出ONNX或TensorRT推理速度和体积一起看验证通过后导出到部署格式yolo export modelbest.pt formatonnx opset12 yolo export modelbest.pt formatengine device0 halfTrueTensorRT的engine格式在GPU上速度最快前提是目标机器的显卡算力和导出机器一致。导出后写一段推理代码验证精度没有掉from ultralytics import YOLO engine YOLO(best.engine) res engine.predict(camera_01.jpg, conf0.4, imgsz1280) for box in res[0].boxes: print(box.cls.item(), box.conf.item(), box.xyxy.tolist())注意TensorRT engine固定了输入尺寸导出时的imgsz要与推理时一致否则报invalid input shape。需要更换分辨率时用onnxruntime走CPU或导出时开启动态shape。5.3 从单帧检测到行为判定抽烟持续时间的统计技巧单帧检出烟不等于有人在抽烟更稳的做法是加时序判断。用ByteTrack跟踪目标ID统计连续N帧同一ID带有smoke类别的时长超过阈值才触发报警。Ultralytics一行命令能启用跟踪yolo detect predict \ modelbest.engine \ sourcertsp://user:pass192.168.1.64:554/stream1 \ trackerbytetrack.yaml \ conf0.4实际项目里我还会在推理结果外层叠一个状态机目标进入画面后每隔5帧采样一次若smoke类置信度超过0.4的帧占比超过60%判定为正在吸烟同时记录开始时间和持续秒数。这个逻辑直接用Python在检测结果外层实现不需要改动YOLO内部结构。本文还有配套的精品资源点击获取
返回列表