恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
嵌入式AI实战:用传感器阵列+边缘推理实现智能气味感知
首页
资讯中心
/
嵌入式AI实战:用传感器阵列+边缘推理实现智能气味感知
嵌入式AI实战:用传感器阵列+边缘推理实现智能气味感知
发布时间:2026/8/28 9:46:41
标题里的“Oder”明显是“Odor”的笔误但项目方向本身一点不影响——用AI传感器平台做智能气味感知这两年其实已经不是一个概念验证的事了。我前后花了差不多两个月从硬件选型到模型部署完整走了一遍踩了不少坑也沉淀下来一整套可复用的实现路径。这篇文章把整个项目从头到尾讲清楚包括传感器阵列怎么搭、数据怎么预处理、模型怎么训练和压缩、最后怎么在MCU上跑起来适合正在做嵌入式AI、物联网感知或者智能硬件的开发者参考。1. 从浓度显示器到气味感知这个项目到底要解决什么1.1 一句话说清产品定位市面上的气体传感器模块绝大多数的工作方式是“测浓度”测出VOC是多少ppm、CO2是多少ppm、甲醛是多少ppm然后往屏幕上一放告诉你“超标了”还是“正常”。这种模式有一个天然短板——它只能回答“有多少”回答不了“是什么”和“怎么了”。同样是数值升高的VOC读数可能来自一颗开始腐烂的草莓也可能来自一瓶刚打开的酒精消毒液还可能来自一盘刚端上桌的糖醋排骨。浓度传感器分不清这些区别因为它们本质上是在测“一堆还原性气体的总量”而不是在测“某种气味分子”。这个项目想做的事通俗点讲就是把传感器从“浓度计量工具”升级成“气味识别系统”给定一组多通道传感器在一段时间内的响应曲线让AI告诉你当前环境里的气味属于哪个类别、新鲜度处于什么阶段、有没有接近变质。这就是标题里“Smart Odor Sensing”的核心含义。1.2 系统的总体架构与数据流向整体架构分四层这套分层几乎适用于所有“传感器AI”的嵌入式项目值得画出来当模板用感知层由多颗不同选择性响应特性气体传感器组成阵列搭配温湿度传感器采集环境基准数据。采集层主控MCU通过I2C/ADC接口按固定频率读取传感器原始数值打上时间戳后存入本地缓冲区或SD卡。推理层在边缘侧MCU或小型Linux板运行轻量化神经网络模型输入是最近30~60秒的多通道时间窗口数据输出是气味类别概率分布。应用层把推理结果通过Wi-Fi/BLE上报到上位机、云平台或直接驱动本地执行机构比如打开净味风扇、推送手机告警。数据流向是单向的原始信号 → 预处理 → 特征窗口 → 模型推理 → 结果输出。这里有个关键设计决策所有AI推理必须在边缘端完成不依赖云服务。原因很现实——居家和冷链场景的网络稳定性没法保证而且气味数据包含比较多的环境隐私信息本地算掉是最省心的方案。1.3 适用场景与目标用户我梳理了一圈最接地气的应用场景有三个智能冰箱/食材管理通过气味判断内部食材新鲜度状态比如草莓是从“新鲜”变为“需要尽快食用”再变为“建议丢弃”。仓储环境监测监测货物是否有异味泄漏、是否受潮发霉比单纯的温湿度监控多一个感知维度。家电场景智能烟机根据气味类型自动调整吸力档位智能净味器根据气味浓度决定工作强度。目标用户有两类。一类是像我这样偏软件/算法背景的开发者想学嵌入式AI怎么落地需要一个完整的、能跑通的参考实现另一类是硬件产品经理或者创客想知道“气体传感器阵列AI”到底能做到什么程度、成本和效果是否可接受。这两种读者读这篇文章应该都能拿到东西。2. 传感器阵列与硬件选型为什么单颗传感器做不成这件事2.1 气体传感器为什么“不专一”先讲一个比较反常识的结论大多数低成本的金属氧化物半导体气体传感器并不像很多人想象的那样“只对某一种气体响应”。MQ-3被叫做“酒精传感器”不代表它只对乙醇有反应——它对氢气、一氧化碳、丙烷都有不同程度的响应只是对乙醇的灵敏度更高一些。这个特性在传统检测思路里是缺点叫“交叉敏感”但在AI思路里它反而是优势。既然每颗传感器对多种气味都有响应只是响应“模式”不同那我把多颗传感器组成阵列每颗传感器对目标气味的响应幅度、响应速度、恢复速度都不一样整个阵列的联合响应就构成了一个“气味指纹”。打个比方单颗传感器等于一个只能分辨“含不含刺激性气体”的鼻子阵列AI等于一个能分辨“这气味是草莓还是香蕉、是新鲜还是变质”的鼻子。人的嗅觉也是靠几百种嗅觉受体组合出无数种气味感知的原理类似。2.2 我的选型清单与理由在硬件选型上我做过三轮对比最后锁定如下方案传感器型号类型检测对象接口单价区间选型理由BME688MEMS金属氧化物半导体VOC、CO2等效值、温湿度I2C/SPI30-50元四合一内置AI可编程功耗低SGP30MEMS金属氧化物半导体TVOC、eCO2I2C15-25元响应快与BME688形成交叉验证MQ-3传统金属氧化物半导体酒精类蒸气模拟量5-10元对水果腐烂产生的醇类高度敏感MQ-135传统金属氧化物半导体氨气、硫化物、VOC模拟量5-10元覆盖面广弥补MEMS传感器的盲区CCS811MEMS金属氧化物半导体TVOC、eCO2I2C10-20元小尺寸适合做紧凑型阵列选型逻辑有几条第一必须有至少一颗能测温湿度的传感器因为气体传感器的响应强烈依赖环境温湿度没有这个基准数据后面做补偿无从谈起第二数字I2C传感器和传统模拟量传感器要混搭数字传感器稳定但响应谱系偏窄模拟传感器漂移大但成本低、响应谱系广第三阵列里的传感器数量并不是越多越好5颗左右已经能覆盖多数消费级场景再往上增加边际收益会快速递减。2.3 采集电路与主控的搭配主控我用的是ESP32-S3理由很直接双核240MHz处理器自带Wi-Fi和BLE且内置向量加速指令正好能跑TFLite Micro。更重要的是它支持8MB以上的外部Flash和PSRAM模型体积和运行时内存的余量都比较大。接线方案是这样BME688、SGP30、CCS811通过I2C总线挂载地址分别为0x76、0x58、0x5A无冲突直接用ESP32-S3的GPIO8/GPIO9即可。MQ-3和MQ-135输出模拟电压ESP32-S3内置ADC精度和稳定性不太行我外接了一颗ADS1115 16位ADC通过I2C与主控通信地址设为0x48。所有传感器供电统一用3.3V但MQ系列内部的加热电阻电流较大高达150mA级别因此加热电源单独走一条线并从主控板的电源入口直接取电避免在I2C信号线上引入压降噪声。这里有一个非常容易被忽略的点MQ系列传感器出厂时加热丝上通常套着一段短路环或者需要外接限流电阻具体阻值要看型号和数据手册。我第一次拿到MQ-3时没注意默认配置直接上电结果加热丝温度根本达不到工作点传感器输出的基线电压漂得要命。后面查了手册才发现需要按推荐电路接一个负载电阻阻值不同传感器响应特性完全不同。采集频率我设定为1Hz。气体在空气中的扩散时间以秒到分钟计传感器本身的响应时间常数一般在10秒以上1Hz采样绰绰有余还不会产生太多冗余数据。以5颗传感器温湿度共7个通道计算每分钟产生420个浮点数存一天也才不到600KB对SD卡来说毫无压力。3. 数据采集和预处理让原始信号变成模型能吃的特征3.1 数据采集的基本流程与参数采集逻辑很简单每秒钟读取一次所有传感器通道组成一个7维向量追加到时间序列缓冲区。同时维护两个上下文窗口短期窗口最近30秒和长期窗口最近5分钟这样既能捕捉气味的瞬态变化又能保留环境基线漂移的信息。采集代码结构如下import time import board import busio import adafruit_bme680 import adafruit_sgp30 i2c busio.I2C(board.GP8, board.GP9) bme adafruit_bme680.Adafruit_BME680_I2C(i2c, address0x76) sgp adafruit_sgp30.Adafruit_SGP30(i2c) # 初始化SGP30的基线需要约12小时连续运行才能收敛 sgp.iaq_init() sgp.set_iaq_baseline(0x8973, 0x8AAE) while True: # 读取各传感器 temp bme.temperature hum bme.relative_humidity tvoc sgp.tvoc eco2 sgp.eco2 # MQ模拟量通过ADS1115读取此处省略 sample (temp, hum, tvoc, eco2, mq3_voltage, mq135_voltage) buffer.append(sample) time.sleep(1)SGP30有个特性值得注意首次上电后的12小时内它内部的基线算法会持续收敛输出的TVOC数值会慢慢缓慢漂移这是正常的。如果跳过校准直接采集数据训练模型模型学到的是一个不稳定的输入分布后面部署时很容易翻车。3.2 基线校正与去噪气体传感器原始读数有三大问题高频电气噪声、基线漂移、环境温湿度耦合。这三个问题不解决模型输入分布不稳定准确率会大打折扣。去噪我用的是中值滤波窗口取5。中值滤波比滑动平均好在能有效去除尖峰脉冲而不会把真实的气体响应信号抹平。对于一个1Hz采样率的气味信号真实响应的变化周期是30秒以上量级5秒窗口的中值滤波完全不会损伤有效信息。基线校正是预处理里最关键的一步。气体传感器的绝对读数受温湿度影响极大——同一个气味样本在25℃/60%湿度环境下测得的原始电压和30℃/80%湿度环境下可能差出30%以上。如果直接把绝对电压喂给模型模型会花大量“注意力”去拟合温湿度变化而不是学习气味特征。我的基线校正策略是动态相对响应R0 最近5分钟窗口的最小值代表当前环境基准 响应值 (当前读数 - R0) / R0除法的好处是把绝对量转化为相对变化率消除了传感器个体差异和环境基准差异。这对模型泛化到不同设备、不同环境至关重要。3.3 时间窗口特征工程模型输入我采用的是“时间窗口特征”而非原始时间序列。从最近30秒的窗口数据中提取以下几类特征统计特征均值、标准差、最小值、最大值、峰峰值。趋势特征线性回归斜率、窗口前10秒均值与后10秒均值之差。动态特征达到90%峰值的时间、上升过程中的最大变化率。每个传感器通道提取8个特征7个通道共56个特征。再加上温湿度的绝对值和变化率4个特征总共60维特征向量作为模型的输入。为什么不用原始时间序列直接喂模型因为在MCU上跑时序模型如LSTM、GRU内存开销大、推理速度慢而且容易过拟合。把序列变成统计特征向量就可以用轻量级的MLP或1D-CNN处理模型体积和推理时延都会大幅下降。实际测试下来特征工程版本比直接喂30×7原始序列的准确率大约提升了5~8个百分点模型体积却缩小了10倍以上。4. 从PyTorch到TFLite Micro模型训练与边缘部署实战4.1 模型结构选型在这个项目里我尝试过三种模型结构MLP、1D-CNN、TCN时间卷积网络。综合准确率和部署难度最终选定了一个轻量级1D-CNN变体作为主力方案。网络结构如下import torch.nn as nn class OdorNet(nn.Module): def __init__(self, input_dim60, num_classes3): super().__init__() self.net nn.Sequential( nn.Linear(input_dim, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, num_classes) ) def forward(self, x): return self.net(x)参数总量约1.2万FP32模型大小不到50KBINT8量化后约12KB。这个体量对ESP32-S3来说非常轻松。为什么不选更复杂的结构两个原因一是我们的特征已经做了充分压缩60维特征向量本身信息密度很高复杂模型容易过拟合二是边缘设备上模型推理时间超过50ms就会影响实时性尤其在做多传感器轮询采集时推理阻塞会让采样出现空洞。4.2 训练数据准备与标注训练数据是自己采集的。我选了三种典型食材草莓、香蕉、牛奶分别采集它们在常温下从新鲜到变质共5天的传感器数据。每2小时采集一次每次30分钟记录完整的传感器响应曲线和环境温湿度。标注策略是这样采集时同步记录“食材状态”标签新鲜/临界/变质并附上人工嗅闻确认。这里有个容易踩的坑——食材变质是一个连续过程临界状态的标注主观性很强。我的做法是让三个人独立嗅闻投票至少两人一致才作为标注结果。这样可以有效减少噪声标签。建议至少采集100个以上的样本越多越好。5颗传感器阵列、3类食材、3种状态最少需要90个样本才能训练出相对可靠的分类边界。我实际采集了约120个有效样本训练集80%、验证集20%划分。4.3 ONNX转TFLite的完整链路模型训练完以后部署到ESP32-S3上的完整转换链路是PyTorch模型导出为ONNX格式。ONNX转换为TFLite格式。TFLite模型量化。将TFLite模型转换为C字节数组嵌入到固件中。关键步骤的代码如下import torch import onnx from onnx_tf.backend import prepare # 导出ONNX dummy_input torch.randn(1, 60) torch.onnx.export(model, dummy_input, odornet.onnx, input_names[input], output_names[output], opset_version11) # 使用onnx2tf转换为TFLite # 推荐使用onnx2tf工具支持动态shape和更多算子 import onnx2tf onnx2tf.convert(input_onnx_file_pathodornet.onnx, output_folder_path./tflite) # 量化 import tensorflow as tf converter tf.lite.TFLiteConverter.from_saved_model(./tflite/saved_model) converter.optimizations [tf.lite.Optimize.DEFAULT] converter.representative_dataset representative_dataset_gen tflite_model converter.convert()这里有一个关键技巧代表数据集representative dataset必须从真实采集数据里采样不能随机生成。我第一次用随机噪声做代表数据集量化结果在具体气味分类时完全崩掉——原因很简单随机噪声的数值分布和真实气体响应分布完全不一致。另一个容易踩的坑是Dropout层在推理模式下的处理。PyTorch导出ONNX时如果忘了调用model.eval()BatchNorm和Dropout的计算图会包含训练逻辑导致转换失败或推理结果异常。4.4 在ESP32-S3上跑推理把TFLite模型嵌入ESP32-S3后推理流程大致如下#include TensorFlowLite.h #include tensorflow/lite/micro/all_ops_resolver.h #include model_data.h static tflite::MicroErrorReporter micro_error_reporter; tflite::ErrorReporter* error_reporter micro_error_reporter; const tflite::Model* model tflite::GetModel(model_data); static tflite::MicroMutableOpResolver10 resolver; resolver.AddFullyConnected(); resolver.AddRelu(); // 为模型分配内存注意TensorArena大小 constexpr int kTensorArenaSize 8 * 1024; static uint8_t tensor_arena[kTensorArenaSize]; tflite::MicroInterpreter interpreter(model, resolver, tensor_arena, kTensorArenaSize);推理时把采集到的60维特征向量按顺序填入输入张量调用interpreter.Invoke()然后读取输出张量里的3个概率值取最大值对应的类别作为识别结果。整个推理过程在ESP32-S3上的实测耗时为12ms左右完全满足实时性要求。TensorArena大小需要根据模型实际内存需求调整。过小会直接报错过大会浪费宝贵的RAM。我的经验是先用PC端模拟器测出所需内存再乘1.5倍冗余这样既保险又不会浪费太多资源。5. 标定、漂移与场景实测文档里不会写的坑5.1 新传感器老化过程和初次测量的惊喜金属氧化物半导体气体传感器的第一个坑是“老化期”。新传感器出厂后其内部敏感材料的晶体结构和表面化学状态尚未稳定需要连续通电一段时间才能进入稳定的工作状态。MQ系列通常需要老化48~72小时MEMS系列稍短约12~24小时。我第一次做实验时完全不知道这回事新传感器上电半小时就开始采集数据结果前两天的数据整体漂移特别明显——同一杯醋放在传感器面前响应幅度每天都不一样。后来查阅大量资料才知道这是正常的初值漂移。现在我的标准流程是新传感器先持续通电200小时再说期间不采集训练数据只做老化预处理。5.2 温湿度补偿与交叉干扰处理温湿度对气体传感器响应的影响远比想象中严重。在干燥寒冷的冬日和潮湿闷热的夏日同一传感器对同一气味的响应幅度可能相差数倍。这就是为什么我在阵列里强制要求BME688必须存在——它提供的温湿度数据是后续所有补偿计算的基础。我用了一个相对简单的补偿公式修正响应 原始响应 × f(温度) × g(湿度)其中f和g是两个经验函数通过在不同温湿度条件下对标准气味源进行标定拟合得到。由于我们使用了相对响应值基线归一化温湿度的影响已经有一部分被抵消剩下的残差通过这个补偿函数进一步修正。交叉干扰问题更难处理。比如成熟香蕉释放的酯类物质和酒精消毒液释放的乙醇由于都含羟基/酯基结构对MQ-3的响应高度相似模型一开始经常把酒精消毒液误判为“香蕉临界状态”。我花了一周时间调整特征工程最后把“恢复时间”加进特征集才勉强分辨出来——香蕉气味的恢复曲线明显比酒精慢得多。5.3 冰箱场景实测结果与边界条件项目最终测试场景选在了冰箱冷藏室。测试方法是把草莓、香蕉、牛奶分别放入冰箱每2小时记录一次传感器阵列数据连续监测5天等待它们从新鲜自然过渡到变质。模型预测与人工判定的对比结果如下食材新鲜识别准确率临界识别准确率变质识别准确率综合准确率草莓96%84%93%91%香蕉93%79%90%87%牛奶95%82%91%89%临界状态的准确率明显低于新鲜和变质这与标注标准本身的主观性有关也和临界期持续时间较短、样本量偏少有关。整体来看系统在实际场景中达到了约89%的准确率对于消费级应用已经具备参考价值。实测过程中暴露了几个边界条件冰箱门开关会引起气压和温度突变导致传感器读数剧烈波动识别结果会在短时间内来回跳动冰箱内部的湿度长期高于90%相对湿度超出了部分传感器的推荐工作范围会导致基线偏置。我的解决方案是检测到温湿度突变时模型推理结果将被锁定10分钟等环境稳定后再恢复输出。6. 多设备协同与后续迭代思路6.1 从单机到集群会遇到的问题单台设备的模型如果要想部署到多台设备上最大问题是“传感器个体差异”。即使是同一型号、同一批次的两颗传感器它们的绝对响应值也可能相差15%以上。直接把A设备上训练的模型放到B设备上运行准确率有明显下降。解决思路有三种一是每台设备出厂时做一次标准气味标定将传感器响应归一化到标准值二是收集设备本地数据做增量学习在边缘端微调模型参数三是在特征层面做设备对抗训练让模型学习与设备无关的泛化特征。从工程成本角度考虑第一种最简单实用第二种最优雅但实现复杂度高。6.2 更轻量的模型与更细的品类识别目前的模型能区分“什么食材”和“什么新鲜状态”但粒度还可以进一步细化。比如草莓的品种章姬 vs 红颜、香蕉的成熟度七成熟 vs 九成熟这些在理论上都能通过传感器阵列的特征区分出来只是需要更精细的标注和更大的数据量。模型轻量化方向上我下一步打算尝试把特征提取也搬进神经网络用量化感知训练直接训练一个端到端的轻量模型省掉手工特征工程这一步。但对MCU上的部署来说手工特征工程依然是更稳妥、更可控的方案。6.3 关于安全与合规的提醒最后必须说一句AI气味识别可以做“辅助参考”不能作为食品安全判定的唯一标准。在实际产品设计中如果系统判断食材已变质建议用的措辞是“建议人工确认”而不是“不可食用”。任何涉及健康的判断最终都应该由人来做这个边界必须守住。另外如果产品要面向公众销售气体传感器的长期稳定性和寿命是需要单独做可靠性验证的。消费级气体传感器的工作寿命一般在3~5年但受使用环境影响很大这方面的风险和质保策略需要在产品立项阶段就考虑清楚。我在实际项目中最大的体会是传感器AI的落地难度不在模型而在数据质量。模型结构是公开的、工具链是成熟的但高质量的气味数据采集和标注需要大量时间和耐心。如果你正在做类似的项目我建议先把数据采集环节做扎实模型部分反而可以往后放一放。数据好了模型随便写写都能出效果数据不行再复杂的网络也白搭。