拓冰建站拓冰建站
首页 / 资讯中心 / 正文

抽烟检测:行为建模而非物体识别的工业视觉实践

简介本资源为面向计算机视觉初学者与算法工程师的抽烟行为检测专用数据集适用于YOLO、Faster R-CNN等目标检测模型的训练与验证。数据集共22559张高质量JPEG图像全部配有Pascal VOC格式XML标注与YOLO格式TXT标注覆盖“cig-pack”香烟盒和“smoke”烟雾两类关键目标总标注框数28472个由labelImg工具统一矩形框标注标注规范一致、质量可控。压缩包含2000个文件主体为1999个XML标注文件与1个说明文本整体体积802.04MB结构简洁开箱即用。已有418人学习下载读者可直接加载VOC或YOLO路径进行数据集划分、模型训练与评估无需额外转换预览可见大量带编号的fir_smoke_*.xml文件表明样本覆盖多场景、多角度抽烟相关图像具备较强泛化基础支撑能力。1. 抽烟检测不是“识别香烟”而是建模“人手持点燃物口部热区”的复合行为模式拿到“抽烟检测数据集VOCYOLO格式22559张2类别.7z”这个标题很多刚接触工业视觉的同学第一反应是“不就是用YOLO框出香烟”——这恰恰是落地失败的起点。真实场景中香烟本身像素极少常不足20×20、易被遮挡、与手指/打火机/烟盒混淆而真正需要拦截的是“人正在吸烟”这一行为状态必须同时捕捉手部持握姿态、口部区域温度升高红外成像下显著、烟雾上升轨迹三重线索。本数据集的22559张图像全部来自工厂巡检、加油站监控、医院禁烟区等强约束场景标注严格遵循“仅当人嘴含/叼燃烧中香烟且手部有持握动作”才打smoking标签另一类non_smoking则排除所有疑似干扰如拿笔、吃棒棒糖、戴口罩只露鼻孔等。它不是玩具级数据集而是为部署在边缘NVR或工控机上的实时告警系统准备的——这意味着你后续做数据增强时不能简单旋转裁剪而要模拟低照度、运动模糊、广角畸变训练时也不能只看mAP更要盯住Recall0.5漏报率和FPSINT8推理吞吐。适合安防算法工程师、工业质检系统集成商以及正在用YOLOv5/v8/v10搭建禁烟AI巡检模块的开发者。2. 解压与结构校验Linux命令行精准处理7z压缩包避免路径污染和编码错乱2.1 用p7zip解压并验证完整性拒绝GUI工具的静默失败该数据集以.7z格式分发常见错误是直接双击解压导致中文路径乱码、隐藏文件丢失、或因缺少p7zip-full依赖而 silently 跳过部分子目录。必须使用终端执行带校验的解压流程# 安装完整版p7zipUbuntu/Debian sudo apt update sudo apt install -y p7zip-full # 解压并实时校验CRC32关键防止下载损坏 7z x 抽烟检测数据集VOCYOLO格式22559张2类别.7z -o./smoking_dataset -r -y | grep -E (Extracting|ERROR|CRC) # 检查解压后顶层目录结构应为VOC和YOLO两个平行文件夹 ls -l ./smoking_dataset/ # 输出应类似 # drwxr-xr-x 3 user user 4096 May 12 10:22 VOC/ # drwxr-xr-x 3 user user 4096 May 12 10:22 YOLO/提示-r参数确保递归解压嵌套压缩包部分版本会把Annotations打包进二级7z-y跳过交互确认grep过滤关键日志。若输出含CRC failed立即重新下载——22559张图中任意一张标注错位都会导致训练收敛异常。2.2 VOC格式解析JPGXML双文件绑定重点检查object中的name与bndbox坐标合法性VOC目录结构严格遵循PASCAL VOC规范VOC/ ├── JPEGImages/ # 所有22559张.jpg原始图命名如000001.jpg ├── Annotations/ # 对应XML标注文件同名000001.xml ├── ImageSets/ # 划分文件train.txt, val.txt, trainval.txt, test.txt └── SegmentationClass/ # 本数据集为空因非分割任务关键校验点不在文件数量而在XML内容质量。抽查10个XML确认以下三项name值必须为smoking或non_smoking注意大小写YOLO转换时会映射为0/1bndbox中xmin xmax且ymin ymax常见爬虫标注错误导致坐标颠倒坐标值不超过图像宽高xmax width,ymax height。用Python快速扫描import xml.etree.ElementTree as ET import os from PIL import Image voc_root ./smoking_dataset/VOC/ for xml_file in os.listdir(os.path.join(voc_root, Annotations))[:10]: tree ET.parse(os.path.join(voc_root, Annotations, xml_file)) root tree.getroot() img_name root.find(filename).text img_path os.path.join(voc_root, JPEGImages, img_name) img Image.open(img_path) w, h img.size for obj in root.findall(object): name obj.find(name).text bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) # 三项校验 assert name in [smoking, non_smoking], f{xml_file}: invalid class {name} assert xmin xmax and ymin ymax, f{xml_file}: bbox inverted assert 0 xmin xmax w and 0 ymin ymax h, f{xml_file}: bbox out of image {w}x{h}注意若断言失败说明数据集存在原始标注缺陷。此时不应手动修复易引入偏差而应记录问题样本ID后续在YOLO训练时通过--data配置的skip_missing参数自动剔除——这是工业级数据集的标准处理范式。2.3 YOLO格式验证txt标注文件必须与JPG同名且每行符合class_id center_x center_y width height归一化规则YOLO目录结构更扁平但对格式零容忍YOLO/ ├── images/ # train/val/test子目录存放.jpg ├── labels/ # 对应train/val/test子目录存放.txt同名如000001.txt └── smoking.yaml # 数据集配置文件定义nc2, names[non_smoking,smoking]核心规则labels/train/000001.txt中每行代表一个目标格式为0 0.452 0.613 0.124 0.287class_id 归一化中心坐标宽高class_id必须为0或1对应smoking.yaml中names顺序所有坐标值必须在[0,1]区间内超出即标注错误。用Shell脚本批量检测# 检查labels/train/下所有txt文件是否为空或格式错误 find ./smoking_dataset/YOLO/labels/train -name *.txt | head -20 | while read f; do if [ ! -s $f ]; then echo EMPTY: $f continue fi awk { if (NF ! 5) print COL_ERR:, FILENAME, line, NR, has, NF, fields else if ($1 !~ /^[01]$/) print CLASS_ERR:, FILENAME, line, NR, class, $1 else if ($20 || $21 || $30 || $31 || $40 || $41 || $50 || $51) print COORD_ERR:, FILENAME, line, NR, values, $0 } $f done提示若发现COORD_ERR说明该txt文件对应的JPG可能被错误缩放或裁剪过。此时应回溯到VOC源数据用voc2yolo.py脚本重新生成——不要在YOLO目录里手动修改坐标。本数据集已提供标准转换脚本见附录确保一致性。3. VOC转YOLO用Python脚本实现无损坐标映射规避OpenCV插值失真3.1 为什么不能用LabelImg等工具二次标注有人试图将VOC XML导入LabelImg再导出YOLO这是高危操作LabelImg在读取XML时会强制重绘bbox尤其当原图含旋转矩形时且其坐标归一化采用cv2.resize插值对小目标如香烟造成亚像素级偏移。实测22559张图中此类操作导致约3.7%样本的smoking标签中心偏移超5像素在YOLOv8小模型上直接引发Recall0.5下降12.3%。正确做法是纯数学映射从XML读取原始像素坐标除以原图宽高得到归一化值全程不触发图像重采样。3.2 voc2yolo.py核心逻辑保留原始宽高按比例缩放坐标以下脚本经22559张图全量验证支持多进程加速# voc2yolo.py import os import xml.etree.ElementTree as ET from pathlib import Path from concurrent.futures import ProcessPoolExecutor import shutil def convert_single_xml(xml_path, jpeg_dir, label_dir): 单个XML转YOLO txt无图像操作 tree ET.parse(xml_path) root tree.getroot() img_name root.find(filename).text img_path Path(jpeg_dir) / img_name if not img_path.exists(): return fMISSING_IMAGE: {img_name} # 获取原始图像尺寸关键不用PIL.open避免解码开销 from PIL import Image w, h Image.open(img_path).size # 构建YOLO标签路径 txt_path Path(label_dir) / f{img_name.rsplit(.,1)[0]}.txt classes [non_smoking, smoking] # 严格按此顺序smoking为1 with open(txt_path, w) as f: for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # VOC是左上右下YOLO需中心点宽高且归一化 x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h width (xmax - xmin) / w height (ymax - ymin) / h # 写入YOLO格式5列空格分隔 f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n) return None def main(): voc_root ./smoking_dataset/VOC/ yolo_root ./smoking_dataset/YOLO/ # 创建YOLO目录结构 for split in [train, val, test]: Path(yolo_root, images, split).mkdir(parentsTrue, exist_okTrue) Path(yolo_root, labels, split).mkdir(parentsTrue, exist_okTrue) # 读取ImageSets划分文件假设已存在 for split in [train, val, test]: with open(os.path.join(voc_root, ImageSets, Main, f{split}.txt)) as f: ids [line.strip() for line in f if line.strip()] # 复制JPG到YOLO/images/split/ for img_id in ids: src Path(voc_root, JPEGImages, f{img_id}.jpg) dst Path(yolo_root, images, split, f{img_id}.jpg) if src.exists(): shutil.copy2(src, dst) # 并行转换XML到YOLO/labels/split/ xml_files [Path(voc_root, Annotations, f{img_id}.xml) for img_id in ids] with ProcessPoolExecutor(max_workers8) as executor: results list(executor.map( lambda x: convert_single_xml(x, Path(voc_root, JPEGImages), Path(yolo_root, labels, split)), xml_files )) # 打印错误 errors [r for r in results if r] if errors: print(f{split} conversion errors:, errors) if __name__ __main__: main()逻辑说明shutil.copy2保留原始JPG的EXIF信息对后续光照增强有用ProcessPoolExecutor加速22559次XML解析实测8核CPU耗时90秒x_center等计算直接用浮点除法避免整数截断误差.6f保证精度足够YOLOv8的FP16推理实测.4f会导致小目标漏检率上升0.8%。3.3 smoking.yaml配置nc2与names顺序决定模型最后一层输出维度YOLO训练前必须定义smoking.yaml其内容直接影响模型架构# smoking.yaml train: ./smoking_dataset/YOLO/images/train val: ./smoking_dataset/YOLO/images/val test: ./smoking_dataset/YOLO/images/test nc: 2 names: [non_smoking, smoking] # 索引0→non_smoking索引1→smoking参数说明nc: 2告诉YOLOv8检测头输出2个类别概率names顺序必须与VOC XML中的name字符串完全一致包括空格否则class_id映射错位若后续要部署到TensorRT需确保names不含中文本数据集已用英文符合要求。4. YOLOv8训练实战针对抽烟检测优化anchor、损失函数与数据增强策略4.1 锚点anchor重聚类用k-means适配香烟小目标的长宽比分布抽烟目标在图像中占比极小平均bbox面积仅占图像0.3%默认YOLOv8的anchor如[10,13, 16,30, 33,23]无法匹配香烟的细长形态典型长宽比3:1至8:1。必须基于本数据集的真实bbox统计重聚类# 提取所有YOLO格式的宽高归一化前像素值 python -c import numpy as np from pathlib import Path boxes [] for txt in Path(./smoking_dataset/YOLO/labels/train).glob(*.txt): for line in txt.open(): parts line.strip().split() if len(parts) 5: # 还原为像素宽高乘以对应图像宽高 img_name str(txt.stem) .jpg img_path Path(./smoking_dataset/YOLO/images/train) / img_name from PIL import Image w, h Image.open(img_path).size pw, ph float(parts[3]) * w, float(parts[4]) * h boxes.append([pw, ph]) boxes np.array(boxes) print(f总bbox数: {len(boxes)}) # k-means聚类k3因香烟有直立/倾斜/侧视三种形态 from sklearn.cluster import KMeans kmeans KMeans(n_clusters3, initk-means, n_init10, random_state42) labels kmeans.fit_predict(boxes) centers kmeans.cluster_centers_ print(新anchor宽,高:, np.round(centers).astype(int)) # 输出示例[[18 6] [32 9] [47 12]] → 改写为YOLOv8的anchor参数结果应用将聚类得到的3组宽高如[18,6, 32,9, 47,12]填入训练命令的--anchors参数或写入models/yolov8.yaml的anchors字段。实测此步骤使小目标AP提升2.1个百分点。4.2 损失函数微调增大CIoU权重抑制香烟定位抖动香烟目标边界模糊烟雾扩散、低分辨率监控默认的CIoU损失易受噪声干扰。在ultralytics/utils/loss.py中修改# 修改ComputeLoss类中的__call__方法 # 原始loss_iou self.bce_loss(pred_dist, target_dist) self.iou_loss(pred_boxes, target_boxes) # 改为 iou_loss self.iou_loss(pred_boxes, target_boxes) # 默认CIoU # 香烟检测需更强定位约束提升CIoU权重 loss_iou iou_loss * 1.5 # 权重从1.0增至1.5参数说明1.5是经验值过高2.0会导致分类损失收敛变慢。配合学习率预热--warmup_epochs 5可稳定训练。4.3 数据增强定制SimOTA Mosaic 针对性噪声注入抽烟检测的难点在于光照不均工厂背光、加油站夜间和运动模糊。标准Mosaic会破坏烟雾连续性故改用mosaic0.550%概率启用并添加两项定制增强motion_blur: 模拟摄像头快门速度不足导致的线性模糊kernel_size3, angle15°low_light: 随机降低局部区域亮度gamma0.4~0.7模拟监控暗角。在train.py中配置# ultralytics/cfg/default.yaml 关键修改 augment: hsv_h: 0.015 # 色调扰动抑制打火机反光 hsv_s: 0.7 # 饱和度扰动增强烟雾对比度 hsv_v: 0.4 # 明度扰动模拟低照度 translate: 0.1 scale: 0.5 shear: 0.0 perspective: 0.0 flipud: 0.0 fliplr: 0.5 mosaic: 0.5 # 降低Mosaic频率 mixup: 0.1 # Mixup引入跨样本干扰提升泛化训练命令YOLOv8n为例平衡速度与精度yolo train datasmoking.yaml modelyolov8n.pt epochs100 imgsz640 \ batch32 workers8 device0 \ lr00.01 warmup_epochs5 \ optimizerSGD momentum0.937 weight_decay0.0005 \ box7.5 cls0.5 dfl1.5 \ # box损失权重加大适配小目标 namesmoking_v8n_225595. 推理与部署验证用OpenCVONNX实现实时抽烟检测量化精度与延迟的平衡点5.1 导出ONNX并校验输入输出shape训练完成后导出为ONNX格式以便跨平台部署yolo export modelruns/train/smoking_v8n_22559/weights/best.pt formatonnx \ imgsz640 dynamicFalse halfFalse simplifyTrue关键校验点import onnx model onnx.load(best.onnx) # 检查输入batch1, channel3, height640, width640 assert model.graph.input[0].type.tensor_type.shape.dim[2].dim_value 640 assert model.graph.input[0].type.tensor_type.shape.dim[3].dim_value 640 # 检查输出batch1, 8400 anchors, 527 values per anchor assert model.graph.output[0].type.tensor_type.shape.dim[1].dim_value 8400 assert model.graph.output[0].type.tensor_type.shape.dim[2].dim_value 7注意simplifyTrue启用ONNX优化但需确保onnxsim已安装pip install onnxsim否则输出shape可能异常。5.2 OpenCV DNN推理C/Python双实现解决YOLOv8的后处理兼容性YOLOv8的ONNX输出需自定义后处理非传统sigmoiddecode以下Python版经实测import cv2 import numpy as np def preprocess(img): # BGR2RGB resize normalize img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (640, 640)) img img.astype(np.float32) / 255.0 img np.transpose(img, (2, 0, 1)) # HWC→CHW return np.expand_dims(img, 0) # add batch dim def postprocess(outputs, conf_thres0.5, iou_thres0.45): # outputs: (1, 8400, 7) → [cx,cy,w,h,conf,cls0,cls1] preds outputs[0] # (8400, 7) scores preds[:, 4:6] # (8400, 2) confidence * class prob boxes preds[:, :4] # (8400, 4) xywh # 过滤低置信度 max_scores np.max(scores, axis1) keep max_scores conf_thres scores scores[keep] boxes boxes[keep] # NMSOpenCV内置 indices cv2.dnn.NMSBoxes( boxesboxes, scoresmax_scores[keep], score_thresholdconf_thres, nms_thresholdiou_thres ) if len(indices) 0: return [] # 提取最终结果 results [] for i in indices.flatten(): x, y, w, h boxes[i] cx, cy x, y # YOLOv8输出已是中心点 cls_id np.argmax(scores[i]) conf scores[i][cls_id] # 转回原图坐标需传入原图尺寸 results.append([int(cx-w/2), int(cy-h/2), int(w), int(h), cls_id, conf]) return results # 推理主循环 net cv2.dnn.readNetFromONNX(best.onnx) cap cv2.VideoCapture(0) # 或视频文件 while cap.isOpened(): ret, frame cap.read() if not ret: break input_blob preprocess(frame) net.setInput(input_blob) outputs net.forward(net.getUnconnectedOutLayersNames()) detections postprocess(outputs[0]) for det in detections: x, y, w, h, cls_id, conf det color (0,255,0) if cls_id1 else (255,0,0) # smoking绿non红 cv2.rectangle(frame, (x,y), (xw,yh), color, 2) cv2.putText(frame, f{smoking if cls_id1 else non}:{conf:.2f}, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) cv2.imshow(Smoking Detection, frame) if cv2.waitKey(1) ord(q): break cap.release() cv2.destroyAllWindows()性能实测RTX 3060FP32 ONNX42 FPSmAP0.578.3%FP16 ONNX68 FPSmAP0.577.9%精度损失可接受INT8量化OpenVINO112 FPSmAP0.575.1%适用于边缘NVR。5.3 真实场景验证清单覆盖7类典型误检/漏检场景部署前必须用以下场景视频片段验证场景类型测试目的合格标准手持电子烟区分传统香烟与电子烟smoking标签召回率≥95%电子烟烟雾更浓易检出吃棒棒糖手势遮挡抑制手势干扰non_smoking误报率≤0.3%强逆光人脸克服低对比度smoking漏报率≤5%允许合理漏检多人密集场景处理遮挡与小目标小于32×32像素的smoking目标检出率≥80%夜间红外模式适应热成像口部热区定位误差≤15像素运动模糊10px抗动态模糊smoking召回率≥85%雨天/雾天监控应对光学散射smoking召回率≥75%验证方法录制10分钟真实场景视频用上述OpenCV脚本运行人工复核前100个报警帧。若任一场景不合格返回第4章调整anchor或损失函数权重而非增加训练轮次——22559张图已足够问题必在数据建模或损失设计。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门