基于 ESP-DL 的 7 类人脸情绪分类:emotion_cls 组件从模型部署到推理实践
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
导读
本文聚焦乐鑫 esp-iot-solution 仓库中examples/ai/esp_dl/emotion_recognition示例的核心组件emotion_cls,完整讲解其在 ESP32-P4(以及可复用的 ESP32-S31)上运行 7 类人脸情绪分类模型的部署方式、推理接口与实测性能。读完本文,你将掌握如何阅读并复用该组件的模型文件、Kconfig 配置、C++ 推理 API 与测试用例,理解其量化精度与端侧工程化缓解策略,并能在自己的 ESP-IDF 工程中一键接入情绪识别能力。
组件概览:一个开箱即用的情绪分类模型
emotion_cls是 esp-iot-solution 中一个人脸情绪分类模型组件,其核心信息浓缩在 组件 README 中:
| 项目 | 值 |
|---|---|
| 支持目标芯片 | ESP32-P4 |
| 模型文件 | model/p4/emotion_cls.espdl |
| 输入图像尺寸 | 100 x 100 |
| 类别数 | 7 类 |
7 个情绪类别在源码中按固定顺序定义,见 emotion_cls.cpp:
const char *kEmotionCategoryNames[] = { "surprise", // 惊讶 "fear", // 恐惧 "disgust", // 厌恶 "happiness", // 高兴 "sadness", // 悲伤 "anger", // 愤怒 "neutral", // 中性 };类别索引从 0 开始,与模型输出 logits 的 top-1 索引一一对应。组件的idf_component.yml声明依赖espressif/esp-dl: "3.3.3",目标平台仅允许esp32p4与esp32s31两种芯片,这与组件 README 中"Supported target: ESP32-P4"的描述一致——ESP32-S31 直接复用同一份 ESP32-P4 模型文件(见 idf_component.yml 与构建脚本中IDF_TARGET STREQUAL "esp32p4" OR IDF_TARGET STREQUAL "esp32s31"的判断)。
实测推理延迟
组件 README 给出了在 ESP32-P4 上单次推理的基准耗时:
| 模型名 | 图像尺寸 | 耗时(us) |
|---|---|---|
| emotion_cls | 100 x 100 | 136803 |
即约 136.80 ms。该数据由组件的 test app 使用esp_timer_get_time()实测得到,测试代码见 app_main.cpp:
int64_t start_us = esp_timer_get_time(); emotion_cls::result_t result = cls.predict(img); int64_t elapsed_us = esp_timer_get_time() - start_us;值得注意:136.80 ms 是纯 emotion_cls 模型推理的时间。当在完整的 emotion_recognition 示例中与 face detection 模型并发运行时,单次情绪分类的端到端平均耗时约为 870 ms,这是模型与检测流水线共享计算资源导致的现象(示例 README 中对此有明确说明)。
使用测试程序验证模型
组件内置了一个可独立运行的 test app,路径为components/emotion_cls/test_apps/。其主程序 app_main.cpp 的逻辑非常清晰:
- 通过
dl::image::sw_decode_jpeg()将捆绑在固件中的test.jpg(100x100 测试人脸图)软解码为 RGB888 图像; - 构造
emotion_cls::EmotionCls cls;实例; - 调用
cls.predict(img)得到推理结果; - 打印类别 ID、类别名、置信度分数与耗时。
组件 README 给出了打包test.jpg运行 test app 后的典型串口输出:
I (1785) emotion_cls: Inference result: class_id=6, class_name=neutral, score=3.880 I (1785) emotion_cls: Inference time: 136803 us (136.80 ms) I (1795) main_task: Returned from app_main()测试用例还通过 partitions.csv 定义了nvs、phy_init与factory(8 MB 应用分区)三个分区,以容纳模型与固件。
C++ 推理接口解析
emotion_cls对外暴露的核心类是EmotionCls,定义于 emotion_cls.hpp。它有两个核心成员:
result_t结构体:包含class_id(类别索引)、score(top-1 类别的 softmax 概率)、class_name(类别名字符串指针);predict()重载:predict(const dl::image::img_t &img)与带crop_area裁剪区域的版本,后者支持传入人脸检测框坐标,仅对 ROI 区域做分类。
构造与预处理
构造函数 emotion_cls.cpp 中完成两件关键工作:
- 模型加载:通过
dl::Model加载模型,模型名缺省为"emotion_cls.espdl",模型位置由 Kconfig 选项CONFIG_EMOTION_CLS_MODEL_LOCATION决定; - 预处理配置:创建
dl::image::ImagePreprocessor,传入ImageNet 归一化参数(mean/std 乘以 255 后映射到 [0, 255] 输入域):
m_preprocessor = new dl::image::ImagePreprocessor( m_model, {123.675f, 116.28f, 103.53f}, {58.395f, 57.12f, 57.375f}, false);这与训练侧 PyTorch 使用的 ImageNet 统计量mean=[0.485, 0.456, 0.406]、std=[0.229, 0.224, 0.225]完全对应,保证端侧推理与训练时数据分布一致。
推理与结果解析
predict()的执行链路为:preprocess()→m_model->run()→ 取输出张量get_output()→ 按输出 dtype 调用get_top1_result<int8_t>()或get_top1_result<int16_t>()。该模板函数(emotion_cls.cpp)实现了一个数值稳定的 softmax:
- 遍历输出找到最大 logits 值
best_quant及其索引best_index; - 用
ldexpf()将量化整数按张量exponent反量化为浮点 logits; - 以
expf(logit - max_logit)计算 softmax 分母,最终score = 1 / sum_exp即 top-1 类别概率。
输出同时支持 INT8 与 INT16 两种量化 dtype,这也印证了模型采用混合精度量化(基础层 INT8、敏感层 INT16)的设计。
模型存储位置:Kconfig 双模式
组件通过 Kconfig 提供模型部署位置的可配置能力,两个互斥选项:
EMOTION_CLS_MODEL_IN_FLASH_RODATA(默认):模型通过target_add_aligned_binary_data()以二进制方式链接进固件的 rodata 段,源码中用extern const uint8_t emotion_cls_espdl[] asm("_binary_emotion_cls_espdl_start")直接引用;EMOTION_CLS_MODEL_IN_FLASH_PARTITION:模型作为独立分区数据烧录(分区名为emotion_cls),运行期从分区读取,便于独立升级模型而无需重刷固件。
此外还有一个未出现在菜单中的CONFIG_EMOTION_CLS_MODEL_IN_SDCARD分支(见 CMakeLists.txt),当启用时可跳过构建期打包逻辑,暗示模型也可从 SD 卡加载。
构建期模型打包依赖 ESP-DL 的fbs_loader工具链:CMake 会调用pack_espdl_models.py将model/p4/emotion_cls.espdl打包为espdl_models/emotion_cls.espdl,再根据所选位置嵌入固件或烧录到分区。
模型训练与量化背景(示例级佐证)
虽然组件 README 只聚焦部署,但其所属示例的顶层 README 提供了模型的完整训练画像,可作为理解组件行为的背景依据:
- 数据集:RAF-DB(Real-world Affective Faces Database),训练集 12,271 张预对齐人脸图,验证集 3,068 张;
- 骨干网络:MobileNetV2(ImageNet 预训练后微调);
- 分类头:
Linear(1280→256) → ReLU6 → Dropout(0.3) → Linear(256→7); - 输入:112×112 RGB(端侧推理时缩放到 100×100);
- 量化:基于 ESP-PPQ 工具链的混合精度 INT8/INT16 量化。
类别分布与量化精度
RAF-DB 各类别训练样本数量差异悬殊,这在量化后体现为精度分化(FP32 验证集 vs INT8 混合精度验证集):
| 类别 | Train (FP32) | Valid (FP32) | Valid (INT8 混合) |
|---|---|---|---|
| happiness | 100.0% | 95.4% | 94.2% |
| neutral | 100.0% | 85.4% | 88.8% |
| surprise | 100.0% | 82.1% | 76.0% |
| sadness | 100.0% | 79.3% | 65.9% |
| anger | 100.0% | 71.0% | 65.4% |
| fear | 100.0% | 47.3% | 32.4% |
| disgust | 100.0% | 38.1% | 29.4% |
| Overall | 100.0% | 83.8% | 80.2% |
总体精度从 FP32 的 83.8% 略降至量化后的 80.2%,但少数类(fear/disgust)由于样本严重不足(fear 仅 281 张训练图,而 happiness 约 4,800 张)且与 surprise/anger 面部表情高度混淆,量化噪声被进一步放大。这些是训练与量化层面的客观事实,并非端侧代码问题。
端侧缓解策略
示例 README 同时给出了在设备端提升实用体验的三条工程手段:
- 分数门控(Score gating):将
score < 0.5的预测视为uncertain并跳过更新,牺牲弱类召回换取更少的误报; - 时间平滑(Temporal smoothing):对最近 5 帧的 top-1 结果做多数投票,抑制单帧抖动导致的情绪跳变;
- 标签折叠(Label collapsing):若产品只需"正向 / 中性 / 负向"三分类,将 7 类映射到 3 类,同等权重下总体准确率可提升至 90% 以上。
完整示例:摄像头 → 检测 → 分类 → 屏显流水线
emotion_cls组件被嵌入到完整的 emotion_recognition 示例中,实现"摄像头实时取流 → 人脸检测 → 情绪分类 → LCD 叠加显示"的端到端流程。入口程序 emotion_recognition.cpp 展示了关键组装方式:
auto *panel = app_lcd_init(); auto *camera = new Camera(VIDEO_PIX_FMT_RGB565, 4, V4L2_MEMORY_MMAP, false); auto *overlay = new EmotionOverlay(panel); EmotionPipeline::Config cfg = { .camera = camera, .face_detect_period_ms = 100, // 人脸检测周期 .capture_period_ms = 20, // 取帧周期 };运行期日志示例(来源:示例 README):
I (3657) emotion_pipeline: Face[0]: emotion=surprise score=0.357 I (4605) emotion_pipeline: Face[0]: emotion=neutral score=0.886 I (5508) emotion_pipeline: Face[0]: emotion=neutral score=0.944 I (6386) emotion_pipeline: Face[1]: emotion=neutral score=0.399硬件要求
运行完整示例需要以下任一开发板(示例 README 明确列出):
- ESP32-S31-Korvo:集成 DVP 摄像头、800x480 RGB LCD 与 GT1151 触摸屏;
- ESP32-P4-Function-EV-Board:搭配 MIPI-CSI 摄像头(SC2336)与 1024x600 MIPI-DSI LCD(EK79007)。
环境与构建
- ESP32-P4 目标要求ESP-IDF release/v5.5 及以后版本;
- ESP32-S31 属于预览目标,需使用ESP-IDF master 分支,并以
idf.py --preview set-target esp32s31配置; - 烧录与监控命令(将
PORT替换为串口号,退出串口监控按Ctrl-]):
idf.py -p PORT flash monitor小结
emotion_cls组件为 ESP32-P4 / ESP32-S31 提供了一套完整、可复用的 7 类人脸情绪分类部署方案:100x100输入、约136.8 ms纯推理耗时、INT8/INT16 混合量化模型、rodata/分区双模式存储,以及开箱即用的 test app 验证入口。配合示例中的分数门控、时间平滑与标签折叠策略,开发者可以将其快速接入实时摄像头情绪识别的产品原型。深入阅读 emotion_cls.cpp 与 emotion_cls.hpp 即可完全掌握其加载、预处理、推理与结果解析的完整调用链。
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考