基于EfficientNetV2B0的深度学习吸烟者图像检测模型

作者:技术拾光

时间:2026-07-07

浏览:136

基于EfficientNetV2B0轻量化架构构建吸烟者检测模型,利用包含类间混淆样本的数据集和卷积神经网络提取指尖烟蒂等核心语义,结合动态图像增强与早停机制,实现非接触式实时预警,为智慧城市等禁烟区域提供工程化算法底座。

文章目录

公共场所控烟与个人健康管理的双重需求,正在推动计算机视觉领域攻克一个相当棘手的难题:如何高效、实时地识别吸烟行为。传统的禁烟管理主要依赖人工巡查或烟雾传感器,前者成本高、存在盲区,后者往往需等待烟雾扩散到一定浓度才会触发,缺乏即时性。随着深度学习技术日趋成熟,智能监控摄像头结合图像识别算法,已能在吸烟行为发生的瞬间捕捉关键动作特征,实现非接触式的自动预警。

这套方案的核心,是基于 EfficientNetV2B0 轻量化架构构建吸烟者检测模型。吸烟识别的真正难点在于动作的细微性,以及大量高度相似的干扰项——喝水、打电话、使用吸入器、咳嗽这些手势,在视觉特征上与持烟姿势极其接近,容易混淆。

为突破这一瓶颈,我们构建了一个包含大量类间混淆样本的数据矩阵,利用卷积神经网络的深层特征提取能力,精准剥离出“指尖烟蒂”与“特定面部交互”等核心语义。从数据流的异步预取、动态图像增强,到迁移学习与早停机制的配合,本文展示的是一套完整的实战方案,为智慧城市、医院、校园等禁烟区域的数字化监管提供了具备工程落地价值的算法底座。

1. 数据集介绍

实验所用数据集来自Kaggle,包含1120张图像,两类各560张——吸烟者和非吸烟者。数据通过搜索引擎输入多个关键词(包括吸烟、吸烟者、人、咳嗽、使用吸入器、打电话、喝水等)收集而来。为了产生一定程度的类间混淆,数据集中两类图像都力求内容多样化。“吸烟”类包含多个角度的吸烟者图像,手势各不相同;“非吸烟”类则刻意收录了一些与吸烟图像手势略有相似的动作,如喝水、使用吸入器、拿着手机、咳嗽等。

所有图像已预处理并调整至250×250分辨率。我们用80%的数据进行训练和验证,20%用于测试。

2. 技术工具

Python版本:3.9
代码编辑器:Jupyter Notebook

3. 实验过程

3.1 导入数据

在构建模型之前,首先要搭建标准化的数据预处理流水线。我们集成了TensorFlow环境下的EfficientNetV2专用组件,并引入图像增强层来应对光照和拍摄角度的干扰。考虑到原始数据集的存储结构与深度学习框架的读取习惯可能存在差异,我们用shutil和os模块在工作空间内动态构建了标准化的类目文件夹。通过image_dataset_from_directory接口,将散落在磁盘上的图片直接映射为高效的tf.data.Dataset对象,并统一调整尺寸为224×224。

这种“预洗数据、统一标准”的操作,不仅为后续迁移学习打下基础,还通过分批次加载确保在显存有限的情况下仍能实现平滑的训练迭代。

# --- 1. 基础工具与路径管理模块 ---
import numpy as np
import pandas as pd
import shutil
import os
import matplotlib.pyplot as plt
import cv2 as cv
from tqdm import tqdm
# --- 2. 深度学习核心模块 (TensorFlow & Keras) ---
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
from tensorflow.keras.preprocessing import image_dataset_from_directory
# --- 3. 模型架构与数据增强专用层 ---
from tensorflow.keras.applications import EfficientNetV2B0
from tensorflow.keras.applications.efficientnet_v2 import preprocess_input
from tensorflow.keras.layers import RandomFlip, RandomRotation, RandomZoom, GlobalA veragePooling2D, Dense, Dropout
# --- 4. 数据目录初始化与重构 ---
train_dir = "/kaggle/input/datasets/sujaykapadnis/smoking/Training/Training"
val_dir = "/kaggle/input/datasets/sujaykapadnis/smoking/Validation/Validation"
test_dir = "/kaggle/input/datasets/sujaykapadnis/smoking/Testing/Testing"
temp_dir = "/kaggle/working"
temp_train_ds_dir = os.path.join(temp_dir, 'training')
temp_val_ds_dir = os.path.join(temp_dir, 'validation')
temp_test_ds_dir = os.path.join(temp_dir, 'testing')
for path in [temp_train_ds_dir, temp_val_ds_dir, temp_test_ds_dir]:
os.makedirs(os.path.join(path, 'smoking'), exist_ok=True)
os.makedirs(os.path.join(path, 'notsmoking'), exist_ok=True)
# --- 5. 构建数据流管道 (TF Dataset) ---
train_ds = image_dataset_from_directory(
temp_train_ds_dir, batch_size=32, image_size=(224, 224), shuffle=True, seed=12)
val_ds = image_dataset_from_directory(
temp_val_ds_dir, batch_size=32, image_size=(224, 224), shuffle=True, seed=12)
test_ds = image_dataset_from_directory(
temp_test_ds_dir, batch_size=32, image_size=(224, 224), shuffle=True, seed=12)

数据载入完成后,非结构化的图像文件已转化为标准化的张量流。我们在加载过程中锁定了随机种子(Seed=12),保证了实验的可重复性。这种自动化的目录结构重组不仅避免了路径引用错误,更为后续用preprocess_input进行特定架构归一化做好准备。对于吸烟检测这类对局部特征(如指尖的香烟烟雾或动作)极为敏感的任务,严谨的预处理是模型能从复杂背景中提取关键动作语义的前提。

3.2 数据可视化

为验证数据管道的读取逻辑是否正确,我们随机提取了训练集第一个Batch中的12张图像进行可视化展示。通过train_ds.take(1),可以看到模型视角下的原始输入:图像被统一缩放至224×224像素,并附带了对应的smoking或notsmoking标签。在此环节,可以重点观察“非吸烟”类别中是否包含容易产生误导的动作(如摸嘴、吃东西),以及“吸烟”类别中香烟实物的视觉占比。这种感官确认有助于评估是否需要引入更强的数据增强手段,以强化模型对微小烟雾特征的捕捉能力。

# --- 1. 获取类别名称映射表 ---
class_names = train_ds.class_names
# --- 2. 图像矩阵展示:构建 3x4 可视化网格 ---
plt.figure(figsize=(12, 9))
plt.suptitle("吸烟者检测数据集:训练样本随机抽检", fontsize=16, y=0.95)
for image_batch, label_batch in train_ds.take(1):
for i in range(12):
plt.subplot(3, 4, i + 1)
label_index = int(label_batch[i])
plt.title(f"Label: {class_names[label_index]}", fontsize=10)
plt.imshow(image_batch[i].numpy().astype('uint8'))
plt.tight_layout()
plt.axis('off')
plt.show()

3.3 特征工程

为充分榨取硬件的推理性能,我们首先通过AUTOTUNE开启了异步预取和缓存机制。这一步确保CPU在处理数据增强的同时,GPU始终处于满载计算状态,彻底消除数据IO造成的训练瓶颈。

在增强策略上,我们构建了一个包含随机水平翻转、旋转与缩放的组合算子。这种设计并非为了做简单的图像花样变换,而是为了模拟监控摄像头在不同安装高度、不同行人视角下的视觉反馈。通过在每一轮迭代中产生细微差异的变体,模型对复杂动作语义的提取鲁棒性会显著提升。

# --- 1. 构建高性能数据加载闭环 ---
autotune = tf.data.AUTOTUNE
train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=autotune)
val_ds = val_ds.cache().prefetch(buffer_size=autotune)
test_ds = test_ds.cache().prefetch(buffer_size=autotune)
# --- 2. 动态图像增强层配置 ---
data_aug = Sequential([
RandomFlip('horizontal'),
RandomRotation(factor=0.2),
RandomZoom(height_factor=0.1, width_factor=0.1)
])

通过这套深度集成的特征工程方案,静态的数据集被转化为一股“活水”。利用prefetch机制,模型在训练当前Batch时,下一组增强后的图像已准备在内存中待命。更重要的是,RandomRotation和RandomZoom的组合人为扩充了样本在空间特征上的广度。这种策略对于识别吸烟这种“小目标、细动作”的任务至关重要——它能防止模型死记硬背某几个特定的像素点,转而学习吸烟行为中跨越空间变换的不变性特征。

3.4 构建模型

此阶段,我们首先实例化了不含分类顶层的EfficientNetV2B0预训练骨架,并将其设置为不可训练状态,锁定它已成熟的权重。在模型结构上,我们构建了一条层级分明的流水线:从输入层开始,依次经过预定义的Data Augmentation增强层和preprocess_input归一化层,随后进入卷积基座提取深度特征。为将多维特征图转化为一维的概率预测,我们引入了GlobalA veragePooling2D进行降维,并配合256个神经元的ReLU密集层做非线性映射。针对可能出现的过拟合,我们不仅加入了Dropout(0.5)随机失活策略,还配置了EarlyStopping监控机制,确保验证集损失不再下降时自动提前终止并回溯至最优权重。

# --- 1. 实例化预训练骨架 ---
conv_base = EfficientNetV2B0(weights='imagenet', include_top=False, input_shape=(224, 224, 3))
conv_base.trainable = False
# --- 2. 配置早停机制 ---
from tensorflow.keras.callbacks import EarlyStopping
early_stop = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)
# --- 3. 端到端模型组装 ---
model = Sequential([
layers.Input(shape=(224, 224, 3)),
data_aug,
layers.Lambda(preprocess_input),
conv_base,
layers.GlobalA veragePooling2D(),
layers.Dense(256, activation='relu'),
layers.Dropout(0.5),
layers.Dense(2, activation='softmax')
])
model.summary()

这一套组合拳构建了一个既保留“大模型”视觉深度、又具备“小模型”灵活性的检测器。值得特别关注的是Dropout(0.5)的应用——在吸烟检测中,人脸特征通常占主导,模型容易忽略细小的香烟特征。高比例的随机失活能强制模型寻找更多元的判断依据,比如从手部姿势甚至烟雾轨迹中提取分类特征,而不是仅仅依赖面部轮廓。这种“不把鸡蛋放在一个篮子里”的逻辑,是保证模型在光照多变的监控场景下仍保持高灵敏度的关键。

3.5 训练模型

训练阶段,我们通过compile接口为模型注入了逻辑内核。考虑到标签以整数索引形式(0或1)存储,我们采用了Sparse Categorical Crossentropy损失函数,既节省内存,又简化了数据预处理流程。在fit过程中,最大迭代轮次设为50轮,但实际上由于EarlyStopping的介入,模型会在验证集损失趋于平稳后的5轮内自动止步。这种“动态截断”策略有效防止过拟合,确保最终保留的权重是对泛化性能贡献最大的那组最优解。

# --- 1. 模型编译 ---
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# --- 2. 启动训练 ---
history = model.fit(
train_ds,
validation_data=val_ds,
callbacks=[early_stop],
epochs=50
)

训练过程中,观察控制台输出的loss与val_loss的下降曲线是判断模型是否健康的关键。在吸烟者检测场景下,如果训练集准确率上升极快而验证集停滞不前,通常意味着模型过度关注了背景中的无关特征。得益于EfficientNetV2B0的渐进式学习能力,通常在10到15轮左右,模型就能在验证集上达到一个相对稳定的高位精度。此时早停机制自动介入,保存下那组对“吸烟动作”最具辨识力的卷积核权重。

3.6 模型评估

我们通过Matplotlib绘制了双指标监控图表。左侧的准确率曲线展示了模型识别能力的进化过程,理想状态下训练与验证曲线应紧密交织上升;右侧的损失曲线反映了预测误差的收敛情况。完成视觉校验后,我们引入完全独立的测试集进行终极考核——这些数据是模型在训练和验证阶段从未见过的全新吸烟场景。通过model.evaluate输出的指标,最终定格这套轻量化检测器在实战环境下的性能基准。

# --- 1. 获取训练历史 ---
accuracy = history.history['accuracy']
val_accuracy = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
# --- 2. 绘制监控看板 ---
plt.figure(figsize=(12, 6))
plt.suptitle("EfficientNetV2B0 吸烟检测模型训练全周期监控", fontsize=15)
plt.subplot(1, 2, 1)
plt.plot(accuracy, label='训练集准确率 (Training)', color='#1f77b4', lw=2)
plt.plot(val_accuracy, label='验证集准确率 (Validation)', color='#ff7f0e', lw=2)
plt.title('准确率演进 (Accuracy)')
plt.xlabel('迭代轮次 (Epochs)')
plt.ylabel('精度 (%)')
plt.grid(axis='y', linestyle='--', alpha=0.6)
plt.legend(loc='lower right')
plt.subplot(1, 2, 2)
plt.plot(loss, label='训练集损失 (Training)', color='#1f77b4', lw=2)
plt.plot(val_loss, label='验证集损失 (Validation)', color='#ff7f0e', lw=2)
plt.title('损失函数收敛 (Loss)')
plt.xlabel('迭代轮次 (Epochs)')
plt.ylabel('误差值')
plt.grid(axis='y', linestyle='--', alpha=0.6)
plt.legend(loc='upper right')
plt.tight_layout()
plt.show()
# --- 3. 终极核验 ---
score = model.evaluate(test_ds, verbose=0)
print(f"测试集准确率 (Test Accuracy): {score[1]:.4f}")
print(f"测试集损失值 (Test Loss): {score[0]:.4f}")

3.7 模型预测

最后,我们构建了一个端到端的predict管道,实现了从原始图像像素到结构化标签的转化。通过对测试集随机采样,生成了一个3x3的预测看板,同步展示了真实类别、AI预测结果以及预测置信度。这种可视化对冲能清晰地暴露模型在极端样本下的表现——例如当吸烟者手部遮挡面部,或背景中存在细长物体(如笔或吸管)干扰时,模型给出的置信度波动将直接反映其特征提取的鲁棒性。这种“透明化”的预测展示,是评估算法是否具备落地禁烟区域实时预警能力的最有力证据。

# --- 1. 定义推理引擎 ---
from tensorflow.keras.preprocessing.image import img_to_array
def predict(model, img):
img_array = img_to_array(img)
img_array = tf.expand_dims(img_array, 0)
prediction = model.predict(img_array, verbose=0)
predicted_class = class_names[np.argmax(prediction[0])]
confidence = round(100 * (np.max(prediction[0])), 2)
return predicted_class, confidence
# --- 2. 随机采样预测 ---
plt.figure(figsize=(12, 12))
plt.suptitle("EfficientNetV2B0:吸烟行为实时预测实战看板", fontsize=18, y=1.02)
for image, label in test_ds.take(1):
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
img_raw = image[i].numpy().astype('uint8')
plt.imshow(img_raw)
predicted_class, confidence = predict(model, img_raw)
actual_class = class_names[label[i]]
plt.title(f"True: {actual_class}\nPred: {predicted_class}\nConf: {confidence}%",
fontsize=11, pad=10)
plt.tight_layout()
plt.axis('off')
plt.show()

4. 总结

本实验基于Kaggle提供的1120张高质量图像,成功构建并验证了一个高精度的吸烟者自动识别系统。数据集中特意引入了喝水、打电话、咳嗽及使用吸入器等与吸烟姿态极度相似的干扰样本,人为增加了类间混淆度,强迫模型学习更深层次的动作语义特征。核心架构采用了EfficientNetV2B0,利用其渐进式学习能力对250×250分辨率的预处理图像进行特征提取。在80%训练验证集与20%测试集的科学划分下,模型展现出了极强的泛化鲁棒性,最终在完全陌生的测试集上取得了94.20%的准确率,损失值仅为0.1838。

这一成果充分证明了迁移学习在处理细微动作检测任务中的优越性。它不仅为智慧城市的禁烟区域监控提供了可靠的算法支撑,也为未来开发能够区分复杂手势交互的实时视频流分析系统奠定了技术基础。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›