ARTICLE DETAIL

资讯详情

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

多场景抽烟行为数据集构建与标注实战指南

多场景抽烟行为数据集构建与标注实战指南 简介面向多场景抽烟行为识别的深度学习数据集专供目标检测与行为识别模型训练使用适合计算机视觉方向的开发者、研究人员及数据标注工程师。资源包约984.58MB文件总数超过一万内含5318张JPG图像与5318个XML标注文件另有images与annotations分类目录XML文件中提供吸烟区域边界框坐标可直接配合PyTorch、TensorFlow等框架完成数据加载与模型训练。数据集覆盖多种真实环境下的吸烟动作包含不同光照、拍摄角度、人物姿态与背景干扰有助于提升模型跨场景泛化与鲁棒性。目前已有423人学习下载。借助这份标注完整的数据集使用者无需从零收集和标注图像可专注于网络结构调优、检测阈值调整与抽烟行为算法验证也能用于智能监控、健康行为分析等应用的前期预研。1. 多场景抽烟行为数据集先想清楚“场景”再动手不然标注白做“多场景抽烟行为数据集”这个标题看着像在求一个现成的数据集下载地址但真正做深度学习落地的人会明白这个标题背后是两类完全不同的需求一类是找现成数据集直接训练另一类是打算自己从零构建一套抽烟行为识别方案。如果你在搜索这个词大概率是后者——因为公开的抽烟行为数据集要么场景单一要么标注粗单靠下载根本喂不饱一个要在工地、加油站、室内办公区同时生效的检测模型。这篇笔记我按自己踩过的路子把多场景抽烟行为数据集的构建、标注、清洗、训练和验证讲透新手能跟着步骤把数据管线跑通熟手可以直接对照着看自己的数据哪里出了问题。核心结论先放在这抽烟行为数据集难的不是采集而是“场景”和“行为”这两个词的边界定义定义不清后面所有的模型迭代都是在错误标注上做无用功。2. 先定义清楚抽烟行为数据集到底在标注什么2.1 抽烟不是目标检测是“目标行为”的复合标注第一次接这个需求的人最容易犯的错是把抽烟行为当成普通的物体检测来做——画个框把烟框住就行。实际跑起来你会立刻发现单框一根烟是完全不可用的烟在手指间、在嘴里、在烟灰缸里形态差异极大而且一支点燃的烟在远处看可能只有两三个像素。真正能在业务里落地的做法是标注“人烟手部动作”的组合关系而不是只标烟这个物体。我一般把抽烟行为拆成三个层级来考虑。第一层是目标层人的头肩区域、手部区域、烟支如果有清晰的第二层是行为层手部是否在面部附近、是否出现持续的手到嘴的重复动作第三层是时序层单帧图片只能看到“手在嘴旁”要确认是抽烟而不是挠痒、打电话需要连续几帧的动作特征。所以在设计数据集的时候你就要问自己一个问题你要做的是单帧识别还是视频片段识别这个决定直接改变标注格式。如果只做单帧识别那么标注对象就是“正在吸烟的人”的边界框属于二分类目标检测如果做视频识别那么标注单位是一段视频片段需要标开始帧、结束帧和动作类别。两种方案没有绝对好坏但你的标注工具和标注人员培训方案完全不同。我在实际项目里发现单帧方案对标注员友好标注速度快但误检率偏高视频方案标注量高三倍以上但模型学到的特征鲁棒性好。预算有限的团队先从单帧方案起步是合理的后续再逐步升级到时序方案。2.2 三种标注方案的取舍边界框、关键点、时序标签具体到标注方案我见过三种主流做法各有适用边界。第一种是边界框方案也是最常见的。给每一帧里正在抽烟的人画一个矩形框类别写smoking。这个方案的前提是你已经把“正在抽烟”定义成了明确状态不包含那种烟夹在耳朵上、手拿烟放在桌上等非动作场景。优点是标注工具成熟LabelImg、CVAT都能做缺点是无法表达“手到嘴”的动态过程模型容易学到“手靠近脸”就误判。第二种是关键点方案。在边界框的基础上额外标注左肘、左腕、右肘、右腕、嘴部、鼻尖六个关键点然后通过计算手部关键点到嘴部关键点的距离来判断抽烟行为是否发生。这个方案的标注成本高但能从几何关系上提供可解释的判定依据而且数据可以复用到喝水、打电话等其他手部动作识别任务里。需要注意的是关键点方案对图像分辨率有要求——人的头部区域至少需要40像素以上的高度关键点才有标注意义。第三种是时序标签方案标注单位是一段视频而不是单张图。每个片段记录主体人物ID、开始帧、结束帧、动作类别抽烟/不抽烟/不确定、以及动作的连续性标注是否中断。这种方案最接近真实业务需求——监控场景里你是对连续视频流判定而不是对单帧截图判定。但时序标注的痛点也很突出标注工具的帧级定位效率太低一个小时的视频可能需要一个标注员做整整一天。我个人的建议如果你要跑通一个最小系统先用边界框方案做初版同时把关键点方案的接口预留好等模型在单帧上的准确率到了瓶颈再考虑引入时序信息。2.3 场景维度的拆分室内/室外/光线/摄像头角度标题里的“多场景”三个字是这个数据集真正的分水岭。很多人把多场景理解为“多拍几个不同背景的图”这是不够的。我做了几十轮实验后总结出场景至少要在四个维度上做交叉拆分少一个维度模型就可能在某个暗角翻车。第一个维度是室内室外。室外场景的光照变化剧烈太阳直射、树荫、夜间路灯都会影响烟支和手部的检出效果室内场景相对稳定但日光灯频闪和背光窗户会造成局部过曝。第二个维度是光线条件我把光照拆成强光、正常光、弱光、混合光四档每档至少要占到总数据量的15%否则模型会在光照切换时断层。第三个维度是摄像头角度。俯视角度监控杆、平视角度考勤机、仰视角度低矮摄像头看到的抽烟姿态完全不同。俯视角下人的手部与嘴部会产生严重遮挡平视角最友好仰视角下烟支基本不可见。如果业务场景是工地监控那么俯视角数据至少要占40%以上因为工地摄像头的安装高度决定了绝大多数画面是俯视。第四个维度是人物姿态。站姿、坐姿、蹲姿、行走中抽烟这四种姿态的动作幅度和手部位置分布差异很大。行走中抽烟的检测最难因为身体晃动会让行为判定窗口变短。建议坐姿和站姿各占30%蹲姿和行走各占20%这个比例是我试下来漏检率最均衡的配置。3. 采集与数据清洗从原始视频到可训练数据集一条务实管线3.1 从视频抽帧按场景比例抽别按时间均匀抽数据采集最常见的做法是找一批监控视频或自己拍摄视频然后抽帧产生图片数据集。但“抽帧”这两个字里藏着大坑如果按固定时间间隔抽帧比如每秒抽1帧你会得到一个数量均衡但场景比例完全失控的数据集——视频里如果前10分钟是空旷办公室后2分钟才有人抽烟那么空旷画面会占据数据集的83%模型根本学不到东西。正确做法是先人工浏览一遍视频把视频按场景片段切分然后在每个片段内部按目标密度抽帧。我通常写一个简单的抽帧脚本支持按时间间隔抽帧和按片段抽帧两种模式import cv2 import os from pathlib import Path def extract_frames(video_path, output_dir, modeinterval, interval5, scene_time_listNone): 从视频中抽帧 :param video_path: 输入视频路径 :param output_dir: 输出图片目录 :param mode: interval按固定间隔抽帧; scene按场景片段抽帧 :param interval: 间隔秒数默认5秒抽1帧 :param scene_time_list: 场景片段列表, 格式为[(起始秒, 结束秒, 场景标签), ...] Path(output_dir).mkdir(parentsTrue, exist_okTrue) cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) total_frames int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) if mode interval: frame_indices list(range(0, total_frames, int(fps * interval))) for idx in frame_indices: cap.set(cv2.CAP_PROP_POS_FRAMES, idx) ret, frame cap.read() if ret: cv2.imwrite(f{output_dir}/frame_{idx:06d}.jpg, frame) elif mode scene: # 按场景片段抽帧每个片段内部按间隔抽 for start_sec, end_sec, scene_label in scene_time_list: start_frame int(start_sec * fps) end_frame int(end_sec * fps) for frame_idx in range(start_frame, end_frame, int(fps * interval)): cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) ret, frame cap.read() if ret: cv2.imwrite(f{output_dir}/{scene_label}_{frame_idx:06d}.jpg, frame) cap.release() # 使用示例把一条视频切成走廊抽烟和工位抽烟两个场景片段 scene_list [ (0, 120, corridor), (120, 300, desk) ] extract_frames(raw_video_01.mp4, dataset/frames, modescene, interval3, scene_time_listscene_list)这段脚本的核心逻辑是先把视频的fps算出来再把时间转换成帧索引避免直接用时间单位去操作视频流因为cv2里的时间定位不够精确。按场景抽帧时我把开始时间和结束时间做成列表每个元素带了场景标签这样抽出来的图片文件名里自带场景信息后续清洗和数据分析时一眼就能看出每张图属于哪个场景。参数上需要注意两点。interval值的选择和目标的运动速度强相关抽烟动作中手到嘴的过程大约持续0.5到1秒如果间隔超过3秒就会漏掉动作中间态如果低于1秒又会产生大量高度相似的相邻帧造成数据冗余。我一般设2到3秒既能捕捉动作过程又不至于让数据膨胀到无法管理。另外一个容易忽视的参数是实际输出尺寸如果原始视频是1920x1080建议在抽帧时直接缩放到1280x720或更小来节省存储空间因为标注和训练阶段还会再做一次缩放提前降分辨率不会损失模型精度但能把数据集体积砍掉一半以上。3.2 清洗脏数据的规则模糊、遮挡、重复帧抽帧产生的原始图片里真正能用于训练的往往只有六成。剩下的四成里模糊、遮挡、重复帧是三个最大的数据污染源。模糊图像的成因主要是运动模糊和摄像头失焦。抽烟的人手部动作快在25fps的视频里单帧曝光时间长的话手部和烟支会拉出残影。这种模糊图放进数据集模型会去学残影的特征推理时遇到速度稍快的动作就容易误判。清洗模糊图没有特别智能的办法我通常的做法是用人工抽查加一个简单的图像清晰度评分来辅助过滤。有一种常见的做法是使用拉普拉斯算子计算图像的边缘强度边缘强度低于阈值的判为模糊图import cv2 import numpy as np def is_blurred(image_path, threshold80): 基于拉普拉斯方差判断图像是否模糊 :param image_path: 图片路径 :param threshold: 方差阈值低于该值判定为模糊 :return: True模糊, False清晰 img cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: return True # 拉普拉斯算子计算二阶导数响应越强烈说明边缘越锐利 laplacian_var cv2.Laplacian(img, cv2.CV_64F).var() return laplacian_var threshold # 批量清洗 import os from pathlib import Path dataset_dir Path(dataset/frames) blurred_list [] for img_path in dataset_dir.glob(*.jpg): if is_blurred(str(img_path), threshold60): blurred_list.append(img_path) print(f共检测到 {len(blurred_list)} 张模糊图像) # 实际使用时先打印名单人工确认再移动文件而不是直接删除threshold这个值的选取和图像分辨率强相关。1280x720的图像正常场景下laplacian方差在80到300之间如果阈值设60会把一些本来清晰但纹理简单的纯净背景图误判为模糊如果阈值设120又可能放过一些轻微运动模糊的图。我建议先用脚本批量算一遍所有图的方差分布画个直方图取低谷处的值作为阈值比拍脑袋定数字靠谱得多。遮挡问题是比模糊更隐蔽的污染源。抽烟时人手可能被衣领、口罩、桌子边缘挡住如果数据集里混入大量“只看到手没看到脸”的半遮挡图模型就会学到不完整的特征。我的清洗规则是分三档完全无遮挡、轻度遮挡嘴部可见、重度遮挡手嘴不可见。只有前两档进入训练集重度遮挡图按比例进入验证集——因为推理时确实会遇到遮挡场景验证集保留少量遮挡样本可以评估模型的真实鲁棒性。重复帧清洗最容易忽略。因为抽帧间隔短相邻两帧可能内容几乎一致。如果数据集里大量重复帧存在训练时会被反复计入损失等效于给这些重复样本加了权重。最简单的做法是计算相邻帧的像素差如果差异低于阈值就删掉后面一张。也可以用图片哈希方法感知哈希能忽略轻微光照差异而保留结构相似性适合做动作类数据的重复检测。3.3 划分训练/验证/测试集按视频切不按帧切数据集划分这个环节最反直觉的坑是按帧随机划分绝对不能用。如果一条视频里第100帧进了训练集、第102帧进了验证集这两帧几乎是一个画面验证集就泄露了训练集的答案最终评估指标会虚高10到20个百分点部署上线后被现实场景打回原形。正确做法是先把视频文件切分成互不相交的片段再整体分配片段。我习惯按“视频文件→场景片段→帧”三级结构来管理dataset/ ├── raw_videos/ │ ├── site_a_20250310.mp4 │ └── site_b_20250312.mp4 ├── scene_clips/ │ ├── train/ │ ├── val/ │ └── test/ └── labeled/ ├── train/ ├── val/ └── test/划分逻辑在代码里实现时我会按视频维度做分组保证同一个视频的所有帧只落入一个集合。如果视频数量不够可以在场景片段级别切分但前提是每个片段之间要有明显的时间间隔或场景切换不能是同一个连续动作硬切两半。训练集、验证集、测试集的比例我是按8:1:1来走但如果数据总量不大比如只有几千张图建议把验证集提高一些因为太少的话评估结果的方差太大改一个学习率都看不出效果差异。另外还有一个容易踩的细节测试集必须从模型从未见过的视频里抽取也就是说测试视频在训练阶段彻底不出现。这样才能模拟真实上线时的场景迁移。4. 标注规范与模型选型你的标注决定你的模型上限4.1 标注工具的选择与并行标注的一致性控制标注工具我使用过LabelImg和CVAT简单对比下适用边界LabelImg是单机软件安装轻量适合个人或小团队一两个人标注但多人并行没有版本管理能力CVAT是网页版工具支持多人协同、标签预定义、自动标注辅助更适合团队协作。如果你的标注量超过一万张图我强烈建议直接上CVAT因为后续的二次审核、跨标注员一致性校验、导出格式转换都方便得多。多人并行标注最大的坑是“标注员之间标准不一致”。同一个场景有人把“烟在手但手在腰部以下”标成了smoking有人把“烟在嘴中但已经熄灭”标成了non-smoking。这种不一致会造成标注噪声比模型本身的误差还难清理。我定的标注统一规范是这样的只有当烟支点着且手部或烟支处于面部区域附近时才标注smoking烟叼在嘴里但手不在附近不标烟夹在耳朵上不标手里拿着烟但手臂自然下垂不标。每一轮标注结束后让两个标注员交叉验证同一批图片用IoU和类别一致性两个指标来量化分歧IoU低于0.5的样本全部重新标注。4.2 模型选型从YOLO系起步但要知道边界在哪里数据集的最终归宿是喂给模型。多场景抽烟行为识别的模型选型我目前的经验是先跑通YOLOv8或者同级别的单阶段检测器把baseline拿到再决定要不要上更重的结构。YOLOv8的优势在于训练流程标准化程度高、数据增强机制完善、社区资料多出问题的排查路径短。用YOLOv8训练时最重要的一个配置项是图像分辨率。抽烟行为检测属于小目标检测烟支在某些视角下只有几个像素输入分辨率设成640x640会丢信息建议起步用1280x1280或者至少1024。代价是训练时间变长、显存占用增大但它带来的mAP提升非常显著。如果你用的YOLOv8是Python接口在训练脚本里加一行imgsz参数就行我习惯在配置阶段就把分辨率固定下来后面不再改。另一个要关注的配置是类别分布。如果你只有“抽烟”和“不抽烟”两个类别那么不抽烟样本里要包含大量干扰动作——打电话、喝水、摸脸、扶眼镜。这些动作在视觉上和抽烟高度相似如果负样本里只有空旷背景模型学到的判别边界会非常脆弱。我一般建议负样本中干扰动作类的数量不少于正样本的1.5倍尤其是在工地场景里工人戴安全帽经常调整帽檐这个动作和抽烟的手部位置高度重叠。4.3 数据增强策略多场景数据集的最后的补强手段即使采集了多场景数据某些组合夜间俯视行走抽烟仍然大概率样本不足。这时候依靠数据增强来补强比重新采集视频成本低得多。我常用的增强组合是随机旋转±30度、随机水平翻转、随机亮度对比度扰动、随机裁剪缩放。但有两个增强手段需要特别谨慎。第一个是马赛克增强就是把四张图拼在一起训练它在通用目标检测数据集上效果很好但在抽烟行为检测上会造成人物裁剪分裂模型学到的人体结构不完整反而掉点。第二个是色彩抖动夜间监控场景的色偏和黄绿色噪点有一定规律性如果你做了大幅度的随机色调变换会破坏真实监控画面的一致性导致模型在夜间数据上检测率反而下降。我一般建议基础增强全开但马赛克增强关闭或者把拼接比例调低到原来的四分之一色彩抖动幅度控制在很小的范围内。这样既保证了样本多样性又不破坏场景的真实分布。5. 避坑多场景抽烟行为数据集最容易翻车的5个地方5.1 现象模型训练loss正常下降但验证集mAP一直很低原因是训练集和验证集之间存在严重的数据分布差异常见于按帧切分数据集导致验证集里出现了和训练集几乎一模一样的画面也有可能是采集时间段过于集中白天数据占了90%验证集里夜间样本只有个位数。解决方法是先检查验证集里各个场景类别的样本数量分布把比例画出来看。如果某个场景类别低于5%不惜一切代价补充这个场景的数据或者把这个场景整体从验证集移到训练集用独立采集的数据来评估模型。我经历过最极端的情况是训练集mAP到了0.82验证集只有0.31最后发现验证集里70%是工地俯视角夜间数据而训练集只拍了办公室平视角白天。数据分布对齐是模型评估的第一性原理。5.2 现象模型把打电话误判为抽烟原因是在标注阶段没有把干扰动作打电话、喝水、摸脸、擦汗作为显式负样本收集训练数据导致模型的判别边界只学到了“手在嘴附近抽烟”。这是我做过最贵的返工之一几万张数据集重新整理了一轮才修复。解决方法是把负样本里的干扰动作单独打标签类别名可以叫phone_call、drink、face_touch让模型学会区分不同手部动作而不是在二分类里硬生生拔高阈值。干扰动作的收集来源很广直接从原始视频里去找打电话、喝水的片段抽帧即可不需要额外采集。5.3 现象模型在室内效果很好到室外工地几乎全部漏检原因是室外场景的光照强度、背景纹理复杂度、摄像头焦距都与室内差异巨大模型的泛化能力不够。多场景数据集并不是把场景数据杂糅在一起就能自然泛化的。解决方法是按场景维度做消融实验——分别用室内数据、室外数据、混合数据训练三套模型找出每个场景的独立baseline再看混合训练是否真的带来提升。如果混合训练反而掉点说明两个场景的特征分布冲突太大考虑用域自适应方法或干脆按场景部署不同的模型。5.4 现象标注了一个月的数据发现一份标注文件丢失了原因是标注工具的导出管理混乱标注文件和原始图片分别存储在不同目录没有做版本化归档。这个是数据管线的管理问题不是技术问题。解决方法是每周做一次标注文件的增量备份所有标注文件统一放到一个带时间戳的目录里目录结构做成annotations/20250310/这种格式。CVAT本身支持导出历史快照LabelImg就要靠人工备份了。我的习惯是每次导出后同步上传一份到独立的备份目录宁可多占点存储也不想重标一遍。5.5 现象训练好的模型在自己测试视频上表现很好部署到客户现场后误检络绎不绝原因是测试视频使用了和训练数据相同或相近的摄像头型号、安装位置、编码参数。客户现场的摄像头畸变、码率压缩、网络丢帧是训练数据里完全不存在的情况。解决办法是在采集阶段就引入至少三种不同的摄像头来拍摄手机摄像头、普通监控摄像头、鱼眼摄像头各采集一部分。如果预算允许最好把鱼眼和广角畸变校正集成到预处理管线里确保模型的输入特征空间和实际部署环境对齐。我最近一次项目返工就是因为客户现场全是超广角摄像头而我们训练数据里全是标准焦距畸变造成的边缘人物拉抻让模型的检测框完全偏离。6. 数据集的价值验证以yolov8为例跑通最小验证流程构建数据集的最终目的是训练出能用的模型。到了最后一步不要急着把全部数据灌进去跑大训练先跑一个最小验证流程来确认数据的可用性。我一般用这样的步骤来验证一个新构建的数据集首先从已标注的数据里随机挑出100张图做一次快速的模型训练迭代轮次设到最小只让模型跑通前向和反向传播确认输出目录生成正常。然后用这100张图里剩下的验证图片做推理画框可视化人眼判断框的位置和置信度是否符合预期。这一步主要排除的是标注格式问题——比如坐标归一化错误、类别编号错位、框的宽高为零等低级错误。很多团队一上来就全量训练结果训练几小时后报错才发现是标注文件里混进了空框数据白白浪费算力。接下来把这个最小验证流程固化成一个脚本模板后续每新增一批数据都要先过这个脚本再决定是否合入主数据集# 最小验证100张随机样本 20轮训练 推理可视化 yolo detect train datacustom.yaml modelyolov8n.pt epochs20 imgsz640 batch8 yolo detect predict modelruns/detect/train/weights/best.pt sourceval_samples/ --save-txt用yolov8n这个最小模型来做验证是有意的选择。n系列模型参数量小、训练速度快在消费级显卡上跑20轮通常只需要几分钟到十几分钟可以很快拿到结果。如果最小模型在小样本上连loss都不降问题一定出在数据格式上而不是模型容量上。等最小验证通过再用yolov8m或yolov8l、更大的输入分辨率来跑正式训练。正式训练的调参经验里我最想强调的还是学习率。多场景抽烟行为数据集通常正负样本不太均衡默认的学习率计划不一定合适。我一般把初始学习率从默认值调低一半让模型在前几个epoch缓慢适应数据分布再用余弦退火把学习率降下来。如果训练过程中发现训练loss下降但验证loss剧烈波动大概率是初始学习率偏高模型在最优点附近震荡此时不是改学习率计划而是应该检查数据增强是否过猛。跑完正式训练后还有一个不少人会忽略的验证步骤用一段完全没有标注过的原始视频整段输入模型做流式推理观察置信度的时序曲线。抽烟行为在真实场景中是持续数秒到数十秒的稳定状态置信度曲线应该是平滑的高值平台如果置信度产生频繁的跳变、闪烁说明模型对单帧的依赖过度帧间一致性太差。这类问题用单帧指标mAP、F1往往看不出来只能靠时序推理的稳定性来捕捉。对于构建数据集这件事我的最终建议是不要试图一次构建一个“全能数据集”。先从业务里最核心的2到3个场景做起把标注质量打磨到极致再逐步扩展场景维度。我自己的经验是模型准确率的提升曲线在数据量上是幂律关系但在数据质量上是指数关系——多花三周做数据清洗比多采集一倍视频更划算。这个教训是我在第一个抽烟行为项目里交了好几周的学费换来的希望帮到你少走这些弯路。本文还有配套的精品资源点击获取
返回列表