1.项目背景
在公共场所控烟与个人健康管理的双重诉求下,如何高效、实时地识别吸烟行为已成为计算机视觉领域的一项重要挑战。传统的禁烟管理模式主要依赖人工巡查或烟雾传感器,前者人力成本高且存在监管盲区,后者则往往在烟雾扩散至一定浓度后才能触发,缺乏即时性与空间覆盖力。随着深度学习技术的成熟,利用智能监控摄像头结合图像识别算法,可以在吸烟行为发生的瞬间捕捉到关键动作特征,从而实现非接触式的自动预警与精细化管理。
本项目旨在开发一套基于 EfficientNetV2B0 轻量化架构的吸烟者检测模型。吸烟识别的难点在于动作的细微性以及高度相似干扰项的存在——如喝水、打电话、使用吸入器或咳嗽等手势,在视觉特征上与持烟姿势极其接近。
为了突破这一技术瓶颈,本实验通过构建包含大量类间混淆样本的数据矩阵,利用卷积神经网络的深层特征提取能力,精准剥离出“指尖烟蒂”与“特定面部交互”等核心语义。从数据流的异步预取、动态图像增强,到利用迁移学习与早停机制进行的模型锻造,本实战展示了如何通过先进的视觉模型在复杂的动态背景中锁定吸烟者,为智慧城市、医院及校园等禁烟区域的数字化监管提供了具备工程化落地价值的算法底座。
2.数据集介绍
本实验数据集来源于Kaggle,该数据集包含1120张图像,平均分为两类:560张为吸烟(吸烟者)图像,另外560张为非吸烟(非吸烟者)图像。该数据集是通过在各种搜索引擎中输入多个关键词(包括吸烟、吸烟者、人、咳嗽、使用吸入器、打电话、喝水等)进行搜索而收集的。我们力求在两类图像中都包含多样化的内容,以产生一定程度的类间混淆,从而更好地训练模型。例如,“吸烟”类包含从多个角度拍摄的吸烟者图像,并包含各种不同的手势。此外,“非吸烟”类包含非吸烟者的图像,他们的手势与吸烟图像中的手势略有相似,例如喝水、使用吸入器、拿着手机、咳嗽等。未来的研究人员可以利用该数据集,开发用于自动检测和筛查吸烟者的深度学习算法,以保障绿色环境并在智慧城市中进行监控。数据集中的所有图像都经过预处理并调整大小,分辨率为 250×250。我们选取 80% 的数据用于训练和验证,20% 用于测试。
3.技术工具
Python版本:3.9
代码编辑器:jupyter notebook
4.实验过程
4.1导入数据
在进入模型构建之前,首要任务是搭建标准化的数据预处理流水线。本阶段我们集成了 TensorFlow 环境下的 EfficientNetV2 专用组件,并引入了图像增强层以应对光照和拍摄角度带来的干扰。考虑到原始数据集的存储结构可能与深度学习框架的读取习惯存在差异,我们采用 shutil 和 os 模块在工作空间内动态构建了标准化的类目文件夹。通过 image_dataset_from_directory 接口,我们将散落在磁盘上的图片直接映射为高效的 tf.data.Dataset 对象,并统一调整尺寸为 224 x 224。这种“预洗数据、统一标准”的操作,不仅为后续的迁移学习夯实了基础,更通过分批次加载(Batching)确保了在显存有限的情况下依然能实现平滑的训练迭代。
# --- 1. 基础工具与路径管理模块 ---import numpy as npimport pandas as pdimport shutilimport osimport matplotlib.pyplot as pltimport cv2 as cvfrom tqdm import tqdm# --- 2. 深度学习核心模块 (TensorFlow & Keras) ---import tensorflow as tffrom tensorflow.keras import layersfrom tensorflow.keras.models import Sequentialfrom tensorflow.keras.preprocessing import image_dataset_from_directory# --- 3. 模型架构与数据增强专用层 ---# 引入 EfficientNetV2B0:兼顾轻量化与高精度的卷积骨架from tensorflow.keras.applications import EfficientNetV2B0from tensorflow.keras.applications.efficientnet_v2 import preprocess_inputfrom tensorflow.keras.layers import RandomFlip, RandomRotation, RandomZoom, GlobalAveragePooling2D, Dense, Dropout# --- 4. 数据目录初始化与重构 ---# 定义原始数据集路径与 Kaggle 工作区缓存路径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')# 动态创建分类文件夹,确保符合 image_dataset_from_directory 的标准读取规范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) ---# 将图像统一缩放至 224x224,设定批大小为 32 以优化显存利用率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 进行特定架构归一化做好了准备。对于吸烟检测这类对局部特征(如指尖的香烟烟雾或动作)极为敏感的任务,这种严谨的预处理是模型能够从复杂背景中提取出关键动作语义的前提条件。
4.2数据可视化
为了验证数据管道的读取逻辑是否正确,我们从训练集的第一个 Batch 中随机提取了 12 张图像进行可视化展示。通过 train_ds.take(1),我们可以观察到模型视角下的原始输入:图像被统一缩放至 224x224 像素,并附带了对应的 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)# 将张量数据转换为 8 位无符号整型以便正常显示像素色彩plt.imshow(image_batch[i].numpy().astype('uint8'))# 优化布局:去除多余坐标轴并防止标题重叠plt.tight_layout()plt.axis('off')plt.show()
4.3特征工程
为了榨取硬件的推理极限,我们首先通过 AUTOTUNE 开启了异步预取(Prefetching)与缓存(Caching)机制。这一步确保了 CPU 在处理数据增强的同时,GPU 能够始终处于满载计算状态,有效消除了数据 IO 带来的训练瓶颈。在增强策略上,我们构建了一个包含 随机水平翻转、旋转与缩放 的组合算子。这种设计并非简单的图像变换,而是为了模拟监控摄像头在不同安装高度、不同行人视角下的视觉反馈,通过在每一轮迭代中产生细微差异的变体,显著提升了 EfficientNetV2B0 对复杂动作语义的提取鲁棒性。
# --- 1. 构建高性能数据加载闭环 ---# 开启 TensorFlow 自动性能优化配置autotune = tf.data.AUTOTUNE# 对训练集执行:缓存读取 -> 随机打散 -> 异步预取# shuffle(1000) 确保了样本间的独立同分布,降低模型对序列顺序的依赖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'),# 允许 20% 范围内的随机旋转,模拟非水平视角的抓拍场景RandomRotation(factor=0.2),# 引入 10% 的随机缩放,应对监控画面中行人距离远近不一的情况RandomZoom(height_factor=0.1, width_factor=0.1)])
通过这种深度集成的特征工程方案,我们成功地将静态的数据集转化为了一股“活水”。利用 prefetch 机制,模型在训练当前 Batch 时,下一组增强后的图像已经准备在内存中待命。更重要的是,通过 RandomRotation 和 RandomZoom 的组合,我们人为地扩充了样本在空间特征上的广度。这种策略对于识别吸烟这种“小目标、细动作”任务至关重要,它能防止模型仅仅死记硬背某几个特定的像素点,转而学习吸烟行为中跨越空间变换的不变性特征。
4.4构建模型
本阶段我们首先实例化了不含分类顶层的 EfficientNetV2B0 预训练骨架,并将其设置为不可训练状态(trainable = False),以锁定其成熟的特征提取权重。在模型结构上,我们构建了一个层级分明的流水线:从输入层开始,依次经过预定义的 Data Augmentation 增强层和 preprocess_input 归一化层,随后进入卷积基座提取深度特征。为了将多维的特征图转化为一维的概率预测,我们引入了 GlobalAveragePooling2D 进行降维,并配合 256 个神经元的 ReLU 密集层进行非线性映射。此外,针对训练过程中可能出现的过拟合问题,我们不仅加入了 Dropout(0.5) 随机失活策略,还配置了 EarlyStopping 监控机制,确保在验证集损失不再下降时自动提前终止并回溯至最优权重。
# --- 1. 实例化预训练骨架:加载 EfficientNetV2B0 核心权重 ---# include_top = False 表示剔除原有的 1000 类分类层,仅保留卷积特征提取能力conv_base = EfficientNetV2B0(weights = 'imagenet',include_top = False,input_shape = (224, 224, 3))# 锁定预训练权重,当前阶段仅训练新增的顶层分类器conv_base.trainable = False# --- 2. 配置早停机制:防止模型无效迭代与过拟合 ---from tensorflow.keras.callbacks import EarlyStoppingearly_stop = EarlyStopping(monitor='val_loss',patience=5, # 如果连续 5 个轮次验证损失没有改善,则停止训练restore_best_weights=True # 自动回滚并保存表现最好的那组模型参数)# --- 3. 端到端模型组装:构建序列化流水线 ---model = Sequential([# 定义标准输入维度layers.Input(shape=(224, 224, 3)),# 动态数据增强:提升模型对旋转、翻转样本的鲁棒性data_aug,# 执行架构专用的预处理逻辑(如像素值归一化与缩放)layers.Lambda(preprocess_input),# 卷积基座:执行深层视觉特征提取conv_base,# 全局平均池化:将特征图压缩为紧凑的向量表示layers.GlobalAveragePooling2D(),# 分类头:256 维隐藏层处理高阶特征融合layers.Dense(256, activation='relu'),# 强力正则化:随机丢弃 50% 的神经元连接,防止死记硬背训练样本layers.Dropout(0.5),# 输出层:根据任务类别进行概率分布预测layers.Dense(2, activation='softmax') # 注:原代码为 7 类,根据吸烟检测任务通常修改为 2 类])# --- 4. 打印网络快照:核验层级结构与参数规模 ---model.summary()
通过这一套组合拳,我们构建了一个既保留了“大模型”视觉深度,又具备“小模型”灵活性的检测器。值得关注的是 Dropout(0.5) 的应用,在吸烟检测中,由于人脸特征通常占据主导,模型容易忽略细小的香烟特征。高比例的随机失活能强制模型寻找更多元的判断依据,比如从手部姿势、甚至烟雾轨迹中提取分类特征,而不是仅仅依赖面部轮廓。这种“不把鸡蛋放在一个篮子里”的逻辑,是保证模型在光照多变的监控场景下依然保持高灵敏度的关键。
4.5训练模型
在本阶段,我们通过 compile 接口为模型注入了逻辑内核。考虑到标签是以整数索引形式(0 或 1)存储的,我们采用了 Sparse Categorical Crossentropy 损失函数,这不仅节省了内存空间,也简化了数据预处理流程。在 fit 过程中,我们将最大迭代轮次设定为 50 轮,但实际上由于 EarlyStopping 的介入,模型会在验证集损失(val_loss)趋于平稳后的 5 轮内自动止步。这种“动态截断”策略能有效防止模型在有限的训练样本上产生过拟合,确保最终保留的权重是对泛化性能贡献最大的那一组最优解。
# --- 1. 模型编译:注入优化器与损失计算逻辑 ---model.compile(# Adam 优化器:自动调整学习率,适合处理迁移学习中的微调任务optimizer='adam',# 损失函数:针对整数标签的稀疏交叉熵,避免了繁琐的 One-hot 编码转换loss = 'sparse_categorical_crossentropy',# 评价指标:实时监控分类准确率metrics = ['accuracy'])# --- 2. 启动训练流水线:执行数据拟合 ---# 引入之前配置的 early_stop 回调函数,实现自动化训练管理history = model.fit(train_ds, # 输入高性能训练数据集validation_data = val_ds, # 每个轮次结束后在验证集上进行“模拟考试”callbacks = [early_stop], # 激活早停监控,防止过拟合epochs = 50 # 设定最大探索上限)
训练过程中,观察控制台输出的 loss 与 val_loss 的下降曲线是判断模型是否健康的关键。在吸烟者检测场景下,如果训练集准确率上升极快而验证集停滞不前,通常意味着模型过度关注了背景中的无关特征。得益于 EfficientNetV2B0 的渐进式学习能力,通常在 10 到 15 轮左右,模型就能在验证集上达到一个相对稳定的高位精度。此时,早停机制会自动介入,保存下那组对“吸烟动作”最具辨识力的卷积核权重,为后续的实战预测做好准备。
4.6模型评估
本阶段我们首先通过 Matplotlib 绘制了双指标监控图表。左侧的准确率曲线(Accuracy)展示了模型识别能力的进化过程,理想状态下,训练与验证曲线应紧密交织上升;右侧的损失曲线(Loss)则反映了预测误差的收敛情况。在完成视觉校验后,我们引入了完全独立的 测试集(Test Dataset) 进行终极考核。这一步至关重要,因为测试集包含了模型在训练和验证阶段从未谋面的全新吸烟场景。通过 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)# 子图1:准确率演进对比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')# 子图2:损失函数收敛对比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. 终极性能核验:测试集表现 ---print("n" + "="*30)print("正在执行测试集终极评估...")score = model.evaluate(test_ds, verbose=0)print(f"测试集准确率 (Test Accuracy): {score[1]:.4f}")print(f"测试集损失值 (Test Loss): {score[0]:.4f}")print("="*30)
4.7模型预测
本环节我们构建了一个端到端的 predict 管道,实现了从原始图像像素到结构化标签的转化。通过对测试集进行随机采样,我们生成了一个 3x3 的预测看板,同步展示了 真实类别(Actual)、AI 预测结果(Predicted) 以及 预测置信度(Confidence)。这种可视化对冲能够清晰地暴露模型在极端样本下的表现:例如,当吸烟者手部遮挡面部,或背景中存在细长物体(如笔或吸管)干扰时,模型给出的置信度波动将直接反映其特征提取的鲁棒性。这种“透明化”的预测展示,是评估算法是否具备落地禁烟区域实时预警能力的最有力证据。
# --- 1. 定义推理引擎:封装单张图像处理逻辑 ---from tensorflow.keras.preprocessing.image import img_to_arraydef predict(model, img):"""输入原始图像,输出模型预测的类别及其对应的置信度百分比"""# 图像张量化:将图像对象转换为 (224, 224, 3) 的数值矩阵img_array = img_to_array(img)# 维度扩充:将 HWC 提升为 (1, H, W, C),满足模型对 Batch 输入的强制要求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):# 构建 3x3 展示矩阵ax = plt.subplot(3, 3, i + 1)# 将测试图像还原为可视化格式img_raw = image[i].numpy().astype('uint8')plt.imshow(img_raw)# 调用推理引擎获取 AI 判别结果predicted_class, confidence = predict(model, img_raw)actual_class = class_names[label[i]]# 结果看板标注:对比真实值与预测值,并显示 AI 的自信程度plt.title(f"True: {actual_class}nPred: {predicted_class}nConf: {confidence}%",fontsize=11, pad=10)# 优化视觉呈现plt.tight_layout()plt.axis('off')plt.show()
5.总结
本实验基于 Kaggle 提供的包含 1120 张高质量图像的吸烟者检测数据集,成功构建并验证了一个高精度的自动化识别系统。该数据集通过引入喝水、打电话、咳嗽及使用吸入器等与吸烟姿态极度相似的干扰样本,人为增加了类间混淆度,从而强迫模型学习更深层次的动作语义特征。实验核心采用了 EfficientNetV2B0 这一轻量化且性能强劲的架构,利用其渐进式学习能力对 250 x 250分辨率的预处理图像进行特征提取。在 80% 训练验证集与 20% 测试集的科学划分下,模型展现出了极强的泛化鲁棒性,最终在完全陌生的测试集上取得了 94.20% 的准确率,而损失值仅为 0.1838。
这一研究成果充分证明了迁移学习在处理细微动作检测任务中的优越性,不仅为智慧城市中的禁烟区域监控提供了可靠的算法支撑,也为未来开发能够区分复杂手势交互的实时视频流分析系统奠定了技术基础。
293