ARTICLE DETAIL

资讯详情

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

吸烟检测YOLOv8训练全流程:数据清洗、格式转换与调参避坑

吸烟检测YOLOv8训练全流程:数据清洗、格式转换与调参避坑 简介吸烟数据集带标注是一份面向人工智能与机器学习场景的监督学习数据集主要服务于目标检测、图像分类等模型训练任务。包内含1567个文件其中783个jpg图像与783个xml标注文件一一对应xml采用通用标注格式记录吸烟目标类别与位置信息可直接用于YOLO、Faster R-CNN等常见检测框架另有1个txt文件用于归纳类别或说明。压缩包整体大小31.71MB样本规模适中适合入门练习与算法验证。数据特征覆盖个人信息、生活习惯、健康状况、社会经济状态与环境因素等多维度信息样本已由专家或自动系统标记为吸烟者与非吸烟者便于开展监督学习。目前已有159人学习下载适合AI初学者、算法工程师及数据科学从业者用于吸烟行为识别、风险预测等方向的研究与实践也可帮助开发者快速验证模型性能。1. 拿到“吸烟数据集带标注”先别急着开训先确认这三件事做禁烟场景的人搜索“吸烟数据集带标注”十有八九是要给摄像头后面的模型补粮草。把数据集解压之后你会发现里面要么是 images 加 XML要么是 images 加 TXT运气好点还带一份 JSON。但带标注不等于能直接训练这个标题里的“带标注”只是说明标注文件存在没说明标注质量、格式和类别定义。拿到这份数据先确认三件事标注框住的是“烟体”还是“正在吸烟的人”这两条路对应的模型行为完全不同标注格式是 VOC、COCO 还是 YOLO TXT决定你要不要写转换脚本以及类别有没有统一我见过一份数据里 smoking 和 cigarette 混着标后面清洗花了两天。这篇笔记就把这三件事按实操顺序讲透格式转换、目录组织、超参数、数据清洗、高频坑和上线前的调参。2. 先看清标注格式VOC、COCO 与 YOLO TXT 的差异和转换脚本2.1 拿到目录结构先判断是哪一种标注格式解压数据集后第一步不是打开图片看效果而是扫一遍目录结构。标注格式决定了你后续要写多少转换代码也决定了能不能直接丢给训练框架。常见的三种格式判断方法都很直接。第一种是 Pascal VOC典型特征是每张图片对应一个同名 XML 文件XML 里有object节点节点下是name和bndboxbndbox里是xmin、ymin、xmax、ymax四个整数像素坐标。第二种是 COCO整个数据集打包成一个annotations.json里面categories数组定义类别annotations数组记录每个框的bbox和area。第三种是 YOLO TXT每张图对应一个同名 txt每行五个数字类别ID cx cy w h中心点和宽高都归一化到 0 到 1 之间的小数。判断方法不需要打开文件直接在终端里数文件就行。如果 XML 文件数量等于图片数量大概率是 VOC如果只有一个 JSON 文件是 COCO如果 txt 数量和图片数量一致且每行都是五个小数那就是 YOLO 格式。这里有一个很容易踩的坑COCO 的bbox是x, y, width, height的绝对值而 YOLO 的cx, cy, width, height是归一化相对值转换时容易把坐标原点搞混。另一个坑是类别 ID 的顺序YOLO txt 里的0到底对应cigarette还是smoking完全取决于你训练时 yaml 文件里的names列表顺序和 XML 里的类别字符串没有半毛钱关系。还有一个值得注意的点看 txt 里数值的范围。如果 txt 里出现0.5 0.5 0.02 0.02这种小数值是归一化后的 YOLO 格式如果出现234 567 12 34这种大整数大概率是某种不规范的自定义格式需要先查清楚坐标是不是像素值。我自己习惯先跑一个统计脚本把每张图的框坐标范围、框数量、宽高比都打出来一眼就能看出格式和数据质量问题。2.2 把 VOC XML 批量转成 YOLO TXT一份可以抄的 Python 脚本如果数据集是 VOC 格式而你又打算用 YOLOv8 或 YOLO11 训练转换脚本绕不开。这里的转换逻辑不复杂从 XML 读出像素坐标算出中心点坐标和宽高再分别除以图片宽高做归一化。需要注意两个细节一是 XML 里的坐标可能越界裁剪到图片尺寸范围内再算二是类别名称必须映射到训练时定义的 ID一个字符都不能错。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_path, classes): tree ET.parse(xml_path) root tree.getroot() # 读取图片宽高用于归一化 size_node root.find(size) img_w int(size_node.find(width).text) img_h int(size_node.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue class_id classes.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 防止坐标越界把框裁剪回图片范围内 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) box_w xmax - xmin box_h ymax - ymin cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h n_w box_w / img_w n_h box_h / img_h lines.append(f{class_id} {cx:.6f} {cy:.6f} {n_w:.6f} {n_h:.6f}) if lines: with open(out_path, w) as f: f.write(\n.join(lines))脚本的核心逻辑是遍历 XML 里的每个object跳过不在classes列表里的类别把像素坐标转成归一化坐标最后写入 txt。参数里最关键的是classes这个列表它的顺序必须和你后续训练 yaml 里的names顺序一致。比如classes [cigarette, smoking]那 ID 0 就是 cigaretteID 1 就是 smoking。顺序搞反了训练出来的模型输出的类别标签就是反的查错会查到你怀疑人生。批量调用的时候要注意源目录和目标目录的对应关系。常见做法是保持相对路径一致比如把datasets/voc/images下的每张图对应的 XML 转换成datasets/yolo/labels下的 txt。转换完成后务必抽查 5 到 10 个 txt用一个可视化脚本把框画回图片上确认框位置没有发生平移或拉伸。这一步看着笨但能救回你后面几天的训练时间。2.3 标注类别只有一类怎么办用标注工具补样本很多吸烟数据集的类别定义只有一条比如只有smoking一类框住的是完整的人或人的上半身。这种标注策略有一个结构性问题模型学到的是“人的姿态”而不是“烟”。当你部署到真实场景只要有人把手抬到嘴边哪怕手里拿的是笔也会被误判成吸烟。更麻烦的是smoking框住整个人时正样本的尺寸和位置变化极大模型要花大量参数去拟合人体形状留给烟体细节的容量就少了。常见做法是拆成两类cigarette框住烟体smoking框住吸烟行为或者干脆只保留cigarette。如果你拿到的数据集只有一类就需要自己补标一部分。标注工具我一般用 CVAT 和 X-AnyLabeling两个都是开源工具。CVAT 适合需要多人协作、按团队流程管理的场景导出格式可以直接选 YOLO 或 Pascal VOCX-AnyLabeling 更轻适合单机快速补几百张它能直接加载 YOLO 格式目录标注完保存就是 txt省去二次转换。补标时记住一个原则标注烟体时宁缺毋滥。烟体被手遮挡超过一半、模糊到人眼都看不清、或者距离远到只有十几个像素这些都不要标。标注框的噪声比漏标更伤训练模型会被部分遮挡的框带着走学到错误的中心点。另外如果数据集里本身没有负样本图片也就是完全没有烟的正常场景你需要单独准备一个negative目录放背景图。这个目录不需要标注文件但要在训练配置里作为背景来源参与训练否则模型在真实场景里的误检率会非常难看。3. 用 YOLOv8 训练自己的吸烟数据集目录结构、配置与超参数3.1 数据集目录怎么摆train/val 分离和 names 顺序的坑格式转换完了下一步是把数据集整理成 YOLOv8 能直接识别的目录结构。这个结构有两套方案我推荐用带images和labels双目录的方案因为它的路径清晰也符合大多数开源项目的惯例。结构长这样datasets/smoking/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/关键是images/train和labels/train下的文件名必须完全一致YOLO 训练时按文件名前缀去找标签文件。如果文件名对不上训练日志里会刷一堆警告但不会报错终止最后你会发现某个类别一个框都没参与训练。整理目录时用脚本批量重命名不要手动拖文件。然后写smoking.yaml配置文件这是训练时的入口。常见错误是把路径写成绝对路径换台机器就废了。我一般用相对path加train/val子路径的组合训练时只要在数据集根目录下执行命令路径就一定能对上。配置文件内容如下# smoking.yaml path: . # 相对于当前工作目录 train: images/train # 训练图片目录 val: images/val # 验证图片目录 names: 0: cigarette 1: smokingnames的顺序就是类别 ID 的映射表。前面 VOC 转换时classes的顺序必须和这里的names顺序一致否则标签错位。还有一个容易被忽略的细节YOLO 格式的 txt 文件里不能有空行也不能有多余空格某些标注工具导出的 txt 会在末尾带一个空行训练时虽然不报错但会影响数据加载效率。清洗阶段顺手把空行去掉后面会省很多事。3.2 训练命令与四个必调参数imgsz、batch、epochs、patience目录和配置就绪后训练命令本身不复杂。需要根据显卡显存和场景特点调整几个关键超参数。我用的是 YOLOv8 官方训练命令模型用yolov8s作为起点在标注质量和数据量都一般的情况下s 级模型已经能覆盖大多数禁烟监控场景没必要一上来就跑 x 级训练时间成倍增加精度提升却有限。yolo detect train \ datasmoking.yaml \ modelyolov8s.pt \ epochs120 \ imgsz960 \ batch16 \ patience30 \ device0四个参数里imgsz对吸烟检测的影响最大。烟体在监控画面里经常只有几十个像素imgsz640是 YOLO 默认值但对小目标不友好。我一般起步就设960显存足够的话可以试1024。代价是训练时间和显存占用上升batch16在 12GB 显存显卡上跑 960 分辨率基本是极限。epochs设多少要看数据量。几千张图的数据集 100 到 120 轮足够收敛patience30表示验证集连续 30 轮指标不提升就提前终止。这个参数能避免过拟合同时省时间。如果你发现训练在 80 轮就自己停了也不用急着加 epochs先去看验证集的 loss 是不是已经平稳。batch如果调到训练卡顿或显存溢出最简单的做法是减半。小 batch 在 BN 层统计上噪声大但配合小学习率也能训出不错的结果。还有一个超参数容易被忽略就是mosaic增强。YOLOv8 默认开启 mosaic吸烟检测里烟体本来就小mosaic 把四张图拼在一起后烟体可能被裁掉一半或缩成一个点反而干扰学习。训练到后半程我习惯把 mosaic 关掉或者降到 0.5让模型在正常尺度下精调几轮。3.3 训练完先别看 mAP先看混淆矩阵和 F1-confidence 曲线训练结束后很多人第一件事是打开终端看 mAP 数值这是一个习惯性错误。mAP 是一个聚合指标它掩盖了类别间的差距。吸烟检测通常只有两三个类别更应该看results/confusion_matrix.png和results/F1_curve.png。混淆矩阵能告诉你每一类被误分成了什么。比如cigarette有一批框被分到了background说明烟体特征不够强可能需要更高输入分辨率或者标注框里混入了大量半截烟如果smoking被误分成cigarette说明两类框的空间关系重叠严重模型没能区分“烟体”和“吸烟行为”。F1-confidence 曲线更实用它能直接指导你部署时的置信度阈值。横轴是置信度纵轴是 F1 分数曲线最高点对应的阈值就是当前模型在训练集上的理论最优阈值。这个值后面部署时直接写进后处理代码。另外打开results.csv看最后一列metrics/precision(B)和metrics/recall(B)的差距。如果 precision 0.9 但 recall 只有 0.6说明模型漏检多宁可置信度调低一点先保召回反过来 recall 高 precision 低就要拉高置信度减少误报。实际禁烟场景里误报比漏报更容易让人烦躁因为你不想每天收到几百条对着水杯报警的消息。4. 吸烟数据清洗四道工序去重、查漏、调分布、补小目标4.1 去重用感知哈希把同一镜头的重复帧挑出来吸烟数据集里有大量从监控视频抽帧得到的图片连续帧之间内容高度相似。如果这些相似帧同时出现在训练集里模型会对这个固定场景过拟合验证集上指标虚高换一个现场就失效。去重最实用的方法是感知哈希。import os import imagehash from PIL import Image folder datasets/smoking/images/train seen {} for filename in os.listdir(folder): path os.path.join(folder, filename) try: img Image.open(path) except Exception: continue h imagehash.phash(img) for old_h, old_name in seen.items(): if h - old_h 8: print(fduplicate: {filename} vs {old_name}) break else: seen[h] filename感知哈希的原理是把图片缩成固定大小的灰度图计算像素均值后生成一串二值指纹汉明距离越小说明图片越相似。这里阈值取8能过滤掉同一镜头下的细微运动差异又不会把光照变化大的不同场景误判为重复。跑完脚本后把输出结果里成对出现的文件手动确认一遍删除其中一张。注意事项数据集如果是按视频抽帧得到的不要只做一层去重。抽帧间隔本身要检查如果每隔 1 帧抽一张即使感知哈希阈值调到 5重复率依然很高。常见做法是先按时间轴每 N 帧取一张再做哈希去重双保险。4.2 查漏标统计每张图的框数找出“应该有很多目标却只标了一个”的图漏标问题在吸烟数据集里太常见了。一张多人聚会的照片里三个人在抽烟标注员只框了一个人手里的烟或者远背景模糊处的烟头根本没标。模型看到标注过的正样本学出了特征同一张图上没标注的目标就成了隐性的负样本训练时会被当作背景逻辑上直接把模型搞混乱。查漏标靠肉眼逐张看太慢先写一个统计脚本把异常样本筛出来再人工复查。import os for split in [train, val]: img_dir fdatasets/smoking/images/{split} label_dir fdatasets/smoking/labels/{split} for name in os.listdir(img_dir): stem os.path.splitext(name)[0] txt os.path.join(label_dir, stem .txt) if not os.path.exists(txt) or os.path.getsize(txt) 0: print(fno label: {split}/{name}) continue with open(txt) as f: lines [line.strip() for line in f if line.strip()] # 单张图框数异常大概率是漏标或合并框 if len(lines) 1: print(fsingle box: {split}/{name}) if len(lines) 20: print(fmany boxes: {split}/{name}, {len(lines)})脚本会打印两类文件没有标注文件的图片和只有单个框的图片。没有标注文件的情况直接删图或者补标单个框的情况需要人工打开图片确认如果图里有好几个人但只标了一个框这就是典型漏标。框数大于 20 的图片通常有几个目标被合并成了一个框也需要打开看。另外检查一下 txt 里的归一化数值是否异常。比如宽度n_w小于 0.001那是一个只有几个像素的噪声框这种框会让模型去拟合噪声建议过滤掉。取值范围超出[0, 1]的框更直接说明转换脚本有 bug回头查一下 2.2 里的xmin/xmax数值类型是不是被处理成了字符串拼接。4.3 调分布白天/黑夜、室内/室外都要有别让模型只认识监控室数据量上去了分布不合理一样白搭。吸烟检测最常见的失败场景是训练数据全来自某写字楼走廊的白天监控结果模型拿到傍晚的食堂门口检测率直接掉一半。光照、背景、距离、姿态四个维度任何一个出现分布倾斜模型就会学到偏置而不是学到“烟”这个物体本身。清洗阶段可以按下面这个表检查覆盖面。它不是硬性指标而是在你决定要不要补数据时的一个对照维度覆盖场景经验建议光照白天、黄昏、夜间、逆光每个大项至少 20% 的样本背景室内、室外、车窗内、工地至少 4 种不同背景距离近景、中景、远景近景和远景各留一批避免全集中在中景姿态夹烟、叼烟、弹烟灰、打电话时夹烟尽量多覆盖姿态越丰富越好如果你手里的数据集在黑夜场景几乎为空有两个快速补法。一个是用公开的夜间吸烟视频抽帧手工标注几十张就够启动另一个是用图像增强工具把白天样本做色彩变换模拟黄昏和夜间效果包括降低亮度、加蓝色色偏。注意增强补出来的样本只能当辅助不能全信模型对真实夜间烟火点的敏感度还得靠真样本验证。4.4 补小目标烟体在画面里往往只有 20 像素切图或 mosaic 增强在 YOLO 模型中输入图片要经过 32 倍的下采样一个 20 像素的烟体在深层特征图上只剩不到 1 个像素几乎没有特征可言。这就是为什么很多模型在 mAP 上表现不错一到实际远距离场景就漏检。应对小目标的第一招是提高输入分辨率imgsz960或1024都值得试。第二招是切图。把 1920×1080 的监控帧切成四块 960×540 的图每块单独推理再按坐标映射回原图。切图推理时注意重叠区域避免目标被切成两半而漏检I OU 合并策略用 0.3 左右的重叠比较稳妥。SAHI 这类库就是干这个的省得自己写坐标映射。训练阶段的 mosaic 增强对小目标的效果要辩证看。它能把小目标和其他背景拼在一起增加小目标的训练曝光次数但切得太碎也会丢目标。我的习惯是前 80 轮开 mosaic最后 20 轮关掉让模型用完整的单图精调。同时把小目标样本的标注筛一遍框尺寸小于三四个像素的直接删掉这种标注即使保留也是噪声。5. 吸烟检测训练避坑五个高频问题现象、原因、解决5.1 坑一电子烟、烟头、未点燃的烟全部漏检现象训练时模型 val 集 mAP 不错部署后遇到有人抽电子烟模型完全没反应遇到会议桌上放着一根未点燃的烟或烟灰缸里立着一根烟头误检率反而飙升。原因数据集里绝大多数正样本是“点燃状态的白烟”有烟灰、火星点、烟缕这些强视觉特征。电子烟没有烟灰和火星点外形更像一支金属笔未点燃的烟和烟头则特征微弱尤其是烟头高光反射后看起来像一个白色小方块。模型学到的是点亮状态的模板而不是“烟形物体”的泛化特征。解决拆场景补样本。电子烟单独建立一个类别或者作为负样本如果你的业务明确要求识别电子烟至少补 500 张不同角度、不同光照的电子烟图如果只识别传统卷烟就把电子烟图放到负样本目录让模型学会把它们归为背景。未点燃的烟和烟头需要人工标注一批放在cigarette类别里让模型明白没有火星点的烟也是烟。补样后重新训练时建议把验证集里也加入等比例的这些难样本否则你根本不知道改进有没有生效。5.2 坑二小目标在特征图上下采样后被“吞掉”现象近景测试全部通过人离摄像头超过 8 米烟体缩小到 30 像素以内模型开始断断续续地漏检。单独剪出烟体的图片拿去推理却能检测出来。原因前面提过的下采样问题加上标注框太小导致正样本在ignore区域。YOLO 在计算损失时会忽略某些小框或让它们不参与损失计算默认配置下小目标的梯度贡献本来就弱。你单独剪图检测时烟体已经被放大到几百个像素当然能检出。解决最直接的是提高imgsz配合切图推理。我一般把训练分辨率提到 960训练完成后部署阶段再叠加切图逻辑。还有一个容易被忽略的参数anchor相关配置YOLOv8 已经自动学习 anchor不需要手调但如果你用的是 YOLOv5必须确认小目标的 anchor 预设没有被裁剪。另外在损失函数里可以考虑给小目标更高的权重但这属于进阶调参性价比不如切图来得快。5.3 坑三误检棒棒糖、铅笔和手指现象模型把叼在嘴边的棒棒糖、夹在指间的白色铅笔甚至一根弯曲的手指都当成香烟框了出来。置信度还不低能到 0.6 以上。原因数据集中缺少“看起来像烟但不是烟”的负样本。棒棒糖的白棒和白色烟体外形高度相似铅笔更是圆柱体加上深色笔芯和香烟的横截面特征接近。模型在训练时没见过这些负样本它的决策边界会无限制地扩到所有“细长白色物体”上。解决负样本策略要刻进工作流。整理一个negative目录放上大量正常手势、持笔、持棒棒糖、喝水等场景图片不需要标注文件但要在训练数据配置里让模型看到它们。另一个办法是在类别里加一个other类把误检对象框出来标成other让模型学会区分。加了负样本后重点看验证集上cigarette类别的 precision 是否提升如果只提升了 recall 而 precision 没动说明误检还是压在背后。5.4 坑四标注框漂移导致训练震荡现象训练 loss 曲线上下波动val mAP 在 60 到 70 之间徘徊怎么调超参都上不去。打开预测结果看框总是偏左或偏右半个烟身。原因标注框本身没有对准烟体中心。很多标注员习惯用框包住“烟加手指”框的中心点落在手指上而不是烟体上。YOLO 的回归目标预测的是框中心偏移中心点噪声会把学习方向带偏。加上烟体细长框的宽高比和真实目标差距大模型对边界回归的置信度自然低。解决清洗阶段用可视化工具把标注框画出来逐张抽查中心点是否落在烟体上。发现问题重新标注不要手动改 txt 里的数值。如果标注资源有限至少把那些框宽高比大于 5 的异常框挑出来复查这类框通常包含了手指或手掌。还有一个兜底方案训练时用更强的数据增强比如degrees10和translate0.1让模型对轻微位置偏移更鲁棒但这只能缓解治不了根。5.5 坑五验证集泄露val 曲线好看现场一测就翻车现象训练时验证集 mAP 到 0.9部署到现场实测召回率直接腰斩。很多人第一反应是环境差异大其实是数据划分出了问题。原因如果数据集来自连续视频抽帧随机划分 train/val 时同一段视频的相邻帧可能一边在训练集、一边在验证集。模型见过验证集里的画面相当于开卷考试分数自然虚高。更隐蔽的是同一摄像头拍摄的不同时间段画面背景相同、机位相同只是人物和动作不同随机划分也会让验证集包含训练集同场景的“影子”。解决划分数据前按视频片段分组。同一段视频的所有帧必须放在同一个集里绝不能跨集。如果数据集没有原始视频信息可以按文件名前缀分组监控视频抽帧通常文件名带时间戳或摄像头 ID先按摄像头 ID 划分再按时间段划分。划分完成后做个交叉验证用训练集里拉出的样本和验证集算一下特征相似度相似度太高就要调整划分粒度。这个坑不解决后面所有调参都是在自欺欺人。6. 模型上线前最后一调NMS 阈值、置信度阈值与热力图验证训练完不是终点部署后能不能在实际场景里稳跑最后一段调参决定了体验。先说 NMS 阈值。YOLO 默认的 IOU 阈值在 0.45 左右适合目标稀疏的场景。吸烟检测里经常几个人并排站在一起多个候选框叠在同一目标上IOU 阈值设太低会把重复框都放出来反过来远距离小目标之间重叠很小NMS 容易保留多个框这时候把 IOU 阈值调到 0.6 到 0.7 能有效合并重复检测。我实际项目中一般会跑两组对比测试一组 0.5一组 0.65看哪组在测试视频上的“框数量抖动”更小。置信度阈值不要凭感觉拍直接用训练时的F1_curve.png找最高点。YOLOv8 训练完后会在runs/detect/train目录下生成一组曲线图F1_curve横坐标是置信度纵坐标是 F1曲线最高点对应的阈值就是理论最优。真实部署时还要结合业务取舍禁烟告警场景容忍误报阈值可以比理论值低 0.05 到 0.1增加召回如果告警会直接触发人工处理阈值就调高一点减少无效工单。热力图验证是我保留的最后一个习惯。用 Grad-CAM 风格的工具跑几张典型测试图看模型关注的热区是不是落在烟体上。如果热区覆盖了手指而没有烟说明模型学到的还是“手部姿态”数据策略需要往烟体细节上压。这个步骤不用每次训练都做模型结构或数据集有重大调整时跑一轮就够。我自己的经验是模型上限在数据不在结构网络结构怎么换都只是锦上添花。标注质量差的吸烟数据集YOLOv8s 和 YOLOv8x 的精度差不了几个点但清洗过后的数据集哪怕用轻量模型也能稳稳超过别人没洗过的重模型。希望帮到你。本文还有配套的精品资源点击获取
返回列表