恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
YOLOv8烟头识别实战:从数据标注到部署优化全指南
首页
资讯中心
/
YOLOv8烟头识别实战:从数据标注到部署优化全指南
YOLOv8烟头识别实战:从数据标注到部署优化全指南
发布时间:2026/9/10 14:10:57
简介YOLOv8烟头识别项目代码面向目标检测学习者与算法开发者提供一套从数据到部署的完整烟头检测工程。项目基于YOLO系列算法针对城市环境烟头污染监测场景实现快速准确定位YOLOv8作为该系列新成员在速度与精度上均有良好表现适合小目标实时检测。压缩包共473个文件以Python源码130个py为主覆盖数据预处理、模型训练与推理227个Markdown文档详细说明环境搭建与使用流程43个YAML配置便于调整模型结构和训练参数。此外还包含C推理接口、多平台Dockerfile部署脚本支持CPU、ARM64、Jetson等、预训练权重及工程化配置文件规范完整。资源大小仅25.07MB轻量便携已有141人学习下载。通过研读项目可系统理解YOLOv8目标检测的完整落地流程掌握如何组织训练数据、配置模型、评估效果并部署到实际环境对开展烟头识别或类似小目标检测任务有直接参考价值。1. 烟头识别为什么非要用 YOLOv8 来做监控画面里最让人头疼的检测目标烟头绝对排得上号。它小、快、容易跟背景融在一起一个烟头在 1080P 画面里往往只有十几个像素人眼都得仔细找更别说让模型稳定识别。YOLOv8 把检测头换成了 Anchor-Free 结构去掉了预设锚框的尺寸限制这对尺寸不固定的烟头来说天然更友好同时它的 C2f 结构在保持轻量的前提下提升了梯度反向传播的效率训练收敛更快在小目标数据集上比前几代更容易调到理想效果。这篇文章要讲清楚的不只是把 YOLOv8 训练起来而是围绕烟头识别这个具体任务把数据标注规格、训练参数设置、部署推理优化这几个环节一次性讲透。无论你是刚接触目标检测的新手还是已经在用 YOLOv8 做其他项目的工程师都能从中找到可以直接用的配置和踩坑经验。2. YOLOv8 的模型结构和烟头检测的适配性分析2.1 C2f 模块和 Anchor-Free 检测头对烟头目标的意义YOLOv8 的主干网络继续沿用 CSPNet 的思路但把原来的 C3 模块换成了 C2f。C2f 的特别之处在于它让每一层的输出同时被后续多个分支利用梯度回传路径更多网络在训练早期就能更快地学到有效的特征表达。这个特性放到烟头识别上直接体现就是同样的 epoch 数下模型对暗光、模糊背景中的烟头召回率更高。另一个关键的架构改动是检测头完全去掉了 Anchor。YOLOv5 及以前的版本需要提前用 K-Means 在训练集上聚类出锚框尺寸如果你换了一个数据集忘记重新聚类收敛速度会明显变慢。YOLOv8 直接用特征图上的每个点预测目标的中心点到四条边的距离烟头的长宽比变化很大——横着扔在桌上和竖着插在烟灰缸里完全两个形状——Anchor-Free 这种回归方式能覆盖更多的形状变化不需要事先假设尺寸分布。# 查看 YOLOv8 模型结构的核心部分确认 C2f 模块存在 from ultralytics import YOLO # 加载一个最小的预训练模型 model YOLO(yolov8n.pt) # 打印模型结构可以看到 C2f 模块 for name, module in model.model.named_modules(): if name.endswith(cv1) or name.endswith(cv2): print(f{name}: {module}) if C2f in type(module).__name__: print(f{name}: {type(module).__name__})这段代码不是为了改网络而是让你直观看到 C2f 模块在整个结构里的分布位置。named_modules()遍历了模型的所有子模块打印结果里会在第 3、4、5 个 stage 看到连续的 C2f 层这就是特征提取的主力模块。如果以后你想做模型改进比如换成 GFPN 结构需要动的地方就是这些 C2f 的输出连接方式。2.2 烟头识别应该选哪个尺寸的模型YOLOv8 有 n、s、m、l、x 五个尺寸很多人的第一反应是越大越准直接上 x。但在烟头识别这个任务上你需要先想清楚部署场景再选。如果场景是边缘计算盒子或者 RK3588 这类开发板n 和 s 的算力开销是几十 GFLOPs 级别配 NPU 加速能跑到实时如果跑在普通 PC 的 GTX 1660 Ti 上m 是性价比最好的选择1080P 画面单帧推理在 30ms 到 50ms 之间浮动。模型尺寸参数量权重体积推理耗时参考烟头小目标召回率YOLOv8n3.2M6.3MB5-10ms低YOLOv8s11.2M22.5MB10-25ms中YOLOv8m25.9M52MB30-50ms中高YOLOv8l43.7M87MB60-100ms高YOLOv8x68.2M136MB120-200ms最高训练阶段用小模型先验证数据和标注质量有没有问题这个思路是对的。但真实部署时别迷信小模型烟头属于小目标n 模型的前 4 层下采样倍数太高到检测头的时候烟头特征可能已经不足 2 个像素了特征直接消失。这种时候你需要的不是更大尺寸的模型而是更浅层的特征融合——后面章节会讲具体怎么做。2.3 环境配置的完整步骤和 GPU 要求配置 YOLOv8 训练环境核心是 PyTorch 版本和 CUDA 版本的匹配。如果你用的是 RTX 30 系及以后的显卡驱动带的 CUDA 版本一般都能向上兼容如果你拿着一张 GTX 1660 Ti老一点的显卡装 CUDA 11.8 通常是最稳的PyTorch 对应装 2.x 版本即可。# 创建独立的 conda 环境避免污染其他项目的依赖 conda create -n yolov8-smoke python3.10 -y conda activate yolov8-smoke # 安装 PyTorch注意 cuda 版本要用 11.8兼容性覆盖面最广 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装 YOLOv8 官方包ultralytics 会自动带上 OpenCV、NumPy 等依赖 pip install ultralyticsGPU 是不是必需品答案取决于你的数据量。几千张图的小数据集CPU 训练也不是不能用只是每个 epoch 可能要十几分钟甚至更久但如果你要跑几百个 epoch 做超参实验没有 GPU 的时间成本没法接受。显存方面8GB 是底线batch-size设为 8、imgsz设为 6408GB 显存跑 YOLOv8s 不会爆显存。显存不够的时候把batch-size减半而不是降低imgsz——图幅变小以后烟头的像素直接缩水前功尽弃。3. 烟头数据集的标注规格和训练参数设置3.1 标注烟头时要特别注意的细节烟头识别的难点不在标注本身而在标注的边界划分。一个烟头有三种状态完整烟头、燃烧中的烟头、烟蒂。我一般建议三个状态都归为同一个类别因为实际监控场景里模型需要在烟头刚出现时就识别出来而不是等它烧完了才报警。如果分类过细反而会稀释每个类别的样本量导致训练不充分。标注时用 LabelImg 或 Labelme 都可以导出的格式需要转换成 YOLO 的 txt 格式。YOLO 格式每行是一个目标五个数字依次表示类别编号、归一化中心点 x、归一化中心点 y、归一化宽度 w、归一化高度 h。# 一个典型的烟头标注文件内容 0 0.523 0.416 0.021 0.015 0 0.312 0.688 0.018 0.0120 0.523 0.416表示这是类别 0 的烟头中心点位于图片宽度的 52.3%、高度的 41.6% 处。宽度和高度是归一化的数值如果烟头在画面中只占 2% 的宽度这里就是0.02。注意 YOLO 格式的归一化是相对于整张图片的不是相对于锚框的。标注烟头数据容易犯的错是把烟头周边的烟灰一起框进去。烟灰的颜色、纹理和烟头完全不一样混在一起会让模型学习到错误的特征。框要紧贴烟头主体宁可稍微窄一点也不要留太多背景。3.2 用 YOLOv8 训练自己的烟头数据集训练之前先把数据目录组织好。YOLOv8 用起来最顺手的方式是标准的数据集目录结构即训练集和验证集分开放每张图片对应一个同名的 txt 标注文件。# smoke.yaml # 数据集配置文件放在任意路径都可以训练时指定路径即可 path: /home/user/smoke-data # 数据集根目录 train: images/train # 训练集图片目录 val: images/val # 验证集图片目录 nc: 1 # 类别数量这里只有烟头一类 names: [cigarette] # 类别名称列表这个配置文件里path是绝对路径或者相对路径都可以但用绝对路径最不容易出错。train和val填的都是相对path的子目录YOLOv8 会自动拼接。nc后面的数字必须和names列表的长度一致不一致会直接报错。# 训练 YOLOv8 烟头识别模型 from ultralytics import YOLO # 使用预训练权重做迁移学习不用从零开始训练 model YOLO(yolov8s.pt) # 启动训练这里的关键参数根据显存和数据量调整 model.train( datasmoke.yaml, epochs200, imgsz640, batch8, patience30, workers4, device0, cos_lrTrue, lr00.005, augmentTrue, seed42 )训练参数有几处值得展开说。epochs200是针对小数据集的保守设定如果用了迁移学习100 个 epoch 左右就能看到一个比较稳定的效果。patience30是早停机制连续 30 个 epoch 验证集指标没有提升就自动停止省时间用的。imgsz640是输入图片的尺寸训练时会把所有图片 resize 到这个大小烟头如果太小可以考虑后面做多尺度训练。3.3 训练时的数据增强策略YOLOv8 默认开启了一部分数据增强但对烟头识别来说有两项增强一定要打开Mosaic 和随机透视变换。Mosaic 会把四张图拼成一张让模型在训练时看到更多样化的背景环境透视变换则能模拟摄像头安装角度变化带来的视差效果。model.train( datasmoke.yaml, epochs200, imgsz640, batch8, hsv_h0.015, # 色调变化范围值越大颜色变异越强 hsv_s0.5, # 饱和度变化范围模拟不同光照环境 hsv_v0.3, # 亮度变化范围烟头常常出现在昏暗环境 degrees10.0, # 旋转角度范围正负10度 scale0.3, # 缩放比例范围模拟不同距离下的烟头大小 fliplr0.5, # 左右翻转概率水平方向的场景才有意义 )hsv_v0.3这个参数值得多说一句。烟头识别场景经常是楼道、卫生间这种光线偏暗的地方训练数据如果没有足够的低亮度样本模型的泛化能力会差。scale0.3则能让模型学习到不同大小烟头的特征防止过拟合到大尺寸样本上。如果训练集的图片分辨率很高比如 2000 像素以上标注的烟头在原始尺寸下还好但在 640 的输入下可能就变成了几个像素。针对这种情况训练时可以调大imgsz到 1280 或 1536代价是显存占用直线上升训练时间翻倍。这个选择需要你自己权衡。3.4 损失函数曲线怎么看训练完成后runs/detect/trainX/目录下会生成results.csv和损失曲线图。你要重点看的是train/box_loss和val/box_loss两条曲线之间的差距。如果训练损失持续下降但验证损失不再下降说明过拟合了这种情况要么增加数据量要么加大patience值配合早停来选择最佳权重。from ultralytics.utils.plotting import plot_results # 生成损失函数曲线图 plot_results(runs/detect/trainX/results.csv)还有一个容易忽略的指标是metrics/mAP50-95(B)它反映的是模型在不同 IoU 阈值下的综合表现。烟头识别因为目标太小mAP50-95 往往不会太高但 mAP50 应该能轻易到 70% 以上。如果 mAP50 也很低问题大概率出在标注质量上——某个标注框偏移了几个像素对普通目标来说无所谓对烟头这种小目标来说可能就是致命的误差。4. YOLOv8 烟头识别模型的推理部署和实时检测4.1 用显卡还是 CPU 部署差距有多大很多人有一个误区认为部署环节一定要用 GPU。烟头识别这种场景多数是 7x24 小时运行的监控系统功耗和成本可能是首要考虑的问题这时候 CPU 部署其实是有意义的。用torch直接推理CPU 上一个 640x640 的帧率大概能跑到 10 FPS 到 15 FPS换成 GPU即使是入门级的 GTX 1660 Ti也能到 30 FPS 以上。部署方案的选择逻辑应该是这样的如果摄像头路数多比如同时分析 8 路视频流GPU 是硬性要求因为 CPU 连两路都吃力如果只有单路画面、对延迟不太敏感CPU 也能满足需求关键是别在 Python 里做太多图像预处理尽量把 Opencv 的操作向量化。# 用训练好的权重进行批量推理 from ultralytics import YOLO model YOLO(runs/detect/trainX/weights/best.pt) results model.predict( sourcetest-video.mp4, conf0.25, iou0.5, device0, saveTrue, streamTrue )这段代码里的conf0.25是置信度阈值低于这个值的目标直接丢弃。烟头识别场景下conf不建议设得太低烟头误检的后果可能很严重——把红色文件夹、手指尖之类的误会报成烟头报警系统会整天响。streamTrue是关键参数它会以生成器的方式逐帧返回结果不会一次性把整个视频读入内存处理长视频或实时流时必须开启。4.2 导出 ONNX 模型做跨平台推理如果部署环境是 C 写的服务框架或者要用 OpenCV DNN 模块跑推理需要把 PyTorch 模型导出成 ONNX 格式。# 导出 ONNX 格式的模型同时启用优化与动态尺寸 model.export( formatonnx, imgsz640, dynamicTrue, simplifyTrue )dynamicTrue允许输入尺寸动态变化这样在家用摄像头 1920x1080 分辨率和工业相机 640x480 分辨率之间切换时不用重新导出模型。simplifyTrue会对计算图做常量折叠和节点合并模型体积会更小推理速度会快一点。ONNX 模型跑起来用的是 OpenCV 自带的 DNN 模块或 ONNX Runtime不再依赖 PyTorch。这种部署方式更轻量但如果你的场景是实时的摄像头视频流我建议直接用 TensorRT——在 NVIDIA 平台上TensorRT 对 YOLOv8 这类模型的加速效果最明显尤其是消去了 PyTorch 的动态图开销后推理延迟能再降 30% 到 50%。4.3 实时视频推理的完整实现一个可用的烟头实时检测程序核心逻辑是读取视频帧、推理、画框、显示逻辑本身很简单真正的关键是处理好推理速度和时间戳。import cv2 from ultralytics import YOLO model YOLO(smoke.onnx) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while cap.isOpened(): ret, frame cap.read() if not ret: break # 单帧推理tracker 参数设为空表示不追踪 results model(frame, conf0.25, iou0.5, verboseFalse) # 在每个检测框上画矩形和置信度 for box in results[0].boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) conf box.conf[0].item() cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText( frame, fcigarette {conf:.2f}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2 ) cv2.imshow(Cigarette Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()关于verboseFalse如果不关闭YOLOv8 在每一帧推理时都会往控制台刷一条日志视频跑几分钟以后日志文件会变得巨大排查问题时真正有用的日志都被埋掉了。这段代码里的循环就是实际监控的逻辑骨架。如果想要做到“运动物体经过摄像头只识别一次”就涉及到两帧之间的目标去重做法是记录上一帧所有检测框的位置当前帧检测到的目标如果和上一帧重叠面积超过某个阈值就视为同一个目标不重复报警。4.4 推理参数调优的 3 个核心手段实时推理场景下imgsz对延迟的影响大于模型本身。做视频流测试时我从 640 降到 416帧率从 23 FPS 提到 35 FPSmAP 只损失了大概 2 个百分点。烟头识别场景里2 个百分点的精度换 50% 的速度提升大多数情况下是划算的。# 将输入尺寸从 640 降到 416观察速度和精度的变化 model YOLO(best.pt) results model(frame, imgsz416, conf0.3, iou0.45)另一个值得调的是iou阈值。conf控制在框里“有没有目标”的置信度iou控制在两个框重叠度多高时算同一个目标iou 太高会让两个靠近的烟头被合并成一个iou 太低则会让一个烟头被画出两个框。烟头面积小框之间的距离本来就近建议把iou设在 0.4 到 0.45 之间比默认的 0.5 稍微低一点避免吞掉并排的烟头。5. 烟头识别的小目标优化技巧5.1 用 SAHI 切片推理提升小目标的召回率当烟头在画面中占比低于 1% 时直接把整张图丢给模型效果会明显变差。SAHISlicing Aided Hyper Inference的思路是先按预设的切片大小和重叠率对原图做切割在每个切片上独立推理再把检测结果映射回原图坐标。这个方案简直是针对烟头识别定制的。pip install sahi ultralyticsfrom sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction model AutoDetectionModel.from_pretrained( model_typeultralytics, model_pathbest.pt, confidence_threshold0.25, devicecuda, ) result get_sliced_prediction( test.jpg, model, slice_height256, slice_width256, overlap_height_ratio0.2, overlap_width_ratio0.2, )slice_height和slice_width设为 256 意味着原图被切成若干个 256x256 的小块分别推理烟头在切片里的相对尺寸变大特征更明显。overlap_height_ratio和overlap_width_ratio是切片之间的重叠率建议设 0.2——如果设成 0烟头恰好在切片边缘时会被切成两半导致漏检。切片推理的代价是计算量翻倍一个 1080P 的画面会被切成 16 个切片以上。一片 4080 Super 可以做到 5 FPS 到 10 FPS在边缘设备上基本跑不动这个方案只能在后台做分析时用。5.2 更根本的办法是改网络结构从 P5 层引出检测头YOLOv8 的默认检测头从 P3、P4、P5 三个层引出特征P5 层的下采样倍数是 32 倍一个 640x640 的输入信号到 P5 层只有 20x20 的分辨率烟头那种本来就只有不到 10 个像素的目标在这条路上特征基本没了。改进思路是新增一个 P2 输出头下采样只有 4 倍让模型在小目标的特征层上直接做预测。以 YOLOv8s 为例修改model.yaml将头部分支的from指到第 2 层即 160x160 特征图同时给这个新检测头分配更小的 anchor 尺寸。要注意的是加了 P2 层以后算力开销大约增加 40%如果部署在边缘设备上需要重新评估帧率。# yolov8s-p2.yaml 关键修改部分 # 让 head 从更浅层的特征图引出检测分支 head: - [ -1, 6, Conv, [ 512 ] ] - [ -1, 1, nn.Upsample, [ None, 2, nearest ] ] - [ [ -1, 6 ], 1, Concat, [ -1 ] ] - [ -1, 3, C2f, [ 512 ] ] - [ -1, 6, Conv, [ 256 ] ] - [ -1, 1, nn.Upsample, [ None, 2, nearest ] ] - [ [ -1, 2 ], 1, Concat, [ 1 ] ] - [ -1, 3, C2f, [ 256 ] ]5.3 评估指标怎么定才合理烟头识别不能只看 mAP。如果只在监控画面的一小块区域检测烟头mAP 可能是虚高的因为大量负样本本身就不在评估范围内。建议在测试集上单独统计小目标面积小于 32x32 像素的召回率和误检率这两个指标比 mAP 更能代表真实场景的表现。from ultralytics.utils.metrics import DetectionMetrics # 按尺寸拆分统计结果 metrics model.val() for box in metrics.box: area (box.xyxy[0][2] - box.xyxy[0][0]) * (box.xyxy[0][3] - box.xyxy[0][1]) if area 32 * 32: # 单独累加小目标的 TP 和 FP pass跑验证集的时候加上plotsTrueYOLOv8 会把每个类别的混淆矩阵和 P-R 曲线图存下来。P-R 曲线的膝盖位置就是最优的conf阈值——不需要盲目相信验证集报告里那张表直接把曲线图上拐点对应的conf值拿来部署即可。本文还有配套的精品资源点击获取