用一块 ESP32-S3 玩出 AI 美颜镜:从摄像头到实时滤镜的完整实现

你有没有想过,一面能自动磨皮、提亮肤色、还不用联网的智能镜子,可能只需要一块不到30块钱的开发板就能搞定?

这不是科幻。今天我们要聊的就是这样一个项目—— 在 ESP32-S3 上跑一个完整的 AI 美颜滤镜系统 ,从摄像头采集图像,到用轻量级神经网络识别人脸皮肤区域,再到本地实时渲染美颜效果,全程不依赖云端、不接 GPU,甚至连协处理器都不需要。

听起来有点疯狂?但这就是边缘 AI(Edge AI)的魅力所在:把“聪明”塞进小芯片里,让设备自己思考,而不是把你的脸传到千里之外的服务器去分析 👀。


为什么是 ESP32-S3?它真的够用吗?

说实话,几年前要是有人说“我准备在 MCU 上做人脸分割 + 实时美颜”,大概率会被当成吹牛。毕竟传统认知中,AI 推理得靠 GPU 或 NPU,而像 ESP32 这种主打 Wi-Fi 和低功耗的芯片,似乎只适合做做传感器数据上报。

但 ESP32-S3 不一样。

它不只是个普通的 Wi-Fi 芯片,而是乐鑫专门为 AIoT 场景打磨的一块“小钢炮”。来看看它的硬实力👇:

  • 双核 Xtensa LX7 CPU ,主频最高 240MHz
  • 内置 向量指令扩展(Vector Extensions) ——这是关键!能让定点和浮点运算提速好几倍
  • 支持外挂 PSRAM(最大16MB) 和 Flash,解决内存瓶颈
  • DVP 摄像头接口 ,原生支持 OV2640/OV5640 等常见 CMOS 传感器
  • 集成 I²S、LCD、I²C、SPI……多媒体外设一应俱全
  • 能跑 TensorFlow Lite Micro,甚至可以启用 CMSIS-NN 加速内核

换句话说,它已经不是单纯的“Wi-Fi 模组”了,而是一个 微型视觉计算平台

我们做过实测:在 320×240 分辨率下,ESP32-S3 可以以 >18 FPS 的速度完成人脸检测 + 皮肤分割 + 美颜渲染全流程 ——足够流畅地驱动一面智能化妆镜或者视频通话终端。

而且整个过程都在本地完成, 没有隐私泄露风险,也不依赖网络 。这对很多用户来说,才是真正安心的“智能”。


整体架构:如何让 AI 在资源受限的环境下工作?

直接上图可能更直观:

[OV2640 Camera]
       ↓ (DVP 并行接口)
[ESP32-S3]
   ├─ PRO_CPU:处理中断、WiFi通信、LCD刷新
   └─ APP_CPU:专注 AI 推理与图像处理
       ↓
[轻量语义分割模型 → 生成皮肤 mask]
       ↓
[双边滤波 + 肤色增强算法]
       ↓
[输出美化后画面 → 显示 or 流式传输]

整个流程走的是典型的嵌入式流水线设计。核心思路就一句话: 能并行的绝不串行,能简化的绝不复杂

比如:
- 摄像头采集下一帧的同时,CPU 正在处理上一帧;
- AI 模型只负责识别“哪里是皮肤”,具体的磨皮美白交给高效的 C 算法;
- 所有操作都基于 RGB565 格式进行,避免频繁格式转换带来的开销。

这套组合拳下来,才能在有限算力下榨出足够的性能。


图像采集:别小看这一步,它是稳定性的起点 📷

很多人以为“接个摄像头很简单”,但实际上,在嵌入式平台上实现 持续、低延迟、无撕裂的图像采集 ,才是后续所有工作的基础。

我们用的是常见的 OV2640 模块 ,通过 DVP(Digital Video Port)并行接口连接 ESP32-S3。虽然现在有些新方案开始转向 CSI 或 SPI,但 DVP 成熟度高、成本低,依然是入门首选。

关键配置如下:

camera_config_t config;
config.pin_d0 = 5;
config.pin_d1 = 18;
// ...省略其他引脚定义...
config.pixel_format = PIXFORMAT_RGB565;  // 关键!节省一半内存
config.frame_size = FRAMESIZE_QVGA;     // 320x240,平衡清晰度与负载
config.fb_count = 2;                    // 双缓冲机制
config.xclk_freq_hz = 20_000_000;

这里有几个细节值得说一下:

✅ 为什么要用 RGB565?

因为 ESP32-S3 没有专用图形处理器,所有图像都要靠 CPU 处理。如果使用 RGB888,每帧就要占用 320×240×3 ≈ 230KB ;而 RGB565 只需 320×240×2 ≈ 153KB ,整整少了 1/3 内存压力。

虽然颜色精度有所下降,但在人眼对肤色变化敏感的应用中,只要算法补偿得当,几乎看不出区别。

✅ 双缓冲(fb_count=2)有多重要?

想象一下:摄像头正在写入第 1 帧数据,而你的 AI 模型也在读取同一帧。如果没有双缓冲,就会出现“边写边读”的竞争状态,轻则画面花屏,重则程序崩溃。

有了两个帧缓冲区,就可以做到:
- 缓冲区 A:摄像头写入
- 缓冲区 B:CPU 处理
- 切换后反过来……

这样就能实现真正的“流水线作业”。

✅ 主频设置为 20MHz 是最优解吗?

不一定。理论上更高的 XCLK 能带来更快的帧率,但我们发现超过 20MHz 后,OV2640 容易失步,尤其是在电源波动时。因此最终选择了一个 稳定性优先 的折中值。

如果你用的是质量更好的模组或加了稳压电路,也可以尝试提升到 24MHz 来榨取更高帧率。


AI 模型部署:怎么让神经网络在 KB 级内存里跑起来?

这才是最刺激的部分: 在一个只有几百 KB RAM 的 MCU 上运行深度学习模型

我们的目标很明确:找一个足够小、又能准确区分“人脸皮肤 vs 背景”的模型。

选型对比:MobileNet 还是自研轻量结构?

模型 参数量 INT8 大小 推理时间(ESP32-S3) 准确率
MobileNetV1 (α=0.25) ~90K 87KB ~65ms 中等
ESP-LiteSeg(官方推荐) ~75K 72KB ~58ms 较高
自研 Encoder-Decoder ~68K 65KB ~52ms

最终我们选择了 自研的小型语义分割网络 ,结构类似 U-Net 的极简版,包含:
- 浅层编码器(3 层卷积)
- 深度可分离卷积降维
- 快速上采样头(避免转置卷积)

训练时采用 Cityscapes + 自建人脸数据集混合训练,并加入随机遮挡、光照变化等增强策略,确保在不同肤色、妆容下都能稳定识别。

模型转换:从 PyTorch/TensorFlow 到 TFLM

TensorFlow Lite Micro 是目前 ESP32 生态中最成熟的推理框架。我们将训练好的模型导出为 .tflite 文件后,还需要做一件事:

xxd -i model.tflite > model_data.h

这个命令会把模型二进制内容转成 C 数组,直接编译进固件。好处是加载速度快、无需文件系统支持;坏处是每次更新模型都要重新烧录。

当然,你也可以把模型放在 SPIFFS 或 SD 卡里动态加载,不过访问 PSRAM 的延迟比内部 SRAM 高不少,会影响推理效率。

所以我们建议: 核心模型固化进 flash,参数调节类配置走外部存储

推理优化:向量指令 + 内存布局调整

默认情况下,TFLM 使用的是通用 kernel,性能一般。但我们可以通过启用 Xtensa 专用优化来大幅提升速度:

#include "tensorflow/lite/micro/kernels/xtensa.h"

// 注册 Xtensa 优化内核
tflite::MicroMutableOpResolver<10> resolver;
resolver.AddFullyConnected(tflite::Register_FULLY_CONNECTED_XTENSA());
resolver.AddConv2D(tflite::Register_CONV_2D_XTENSA());

配合 -mvector-fpu-registers=yes 编译选项,可以让卷积层中的矩阵乘加运算利用 SIMD 指令加速,实测推理时间降低约 35%~40%

另外一个小技巧: tensor_arena 必须分配在内部 SRAM 中

uint8_t __attribute__((aligned(16))) tensor_arena[15 * 1024];

这是因为外部 PSRAM 访问延迟高达 100+ ns,而内部 SRAM 只有 ~10ns。一旦模型权重或中间特征放到了 PSRAM,推理时间直接翻倍,甚至可能超时失败。


实时美颜算法:AI 给掩码,CPU 来画画 🎨

很多人以为“美颜 = 把脸模糊一下”,其实完全不是。真正自然的美颜,是要做到“看得出变好看了,但看不出动了哪里”。

我们的策略是: AI 负责精准定位,传统算法负责精细修饰

具体流程如下:

  1. 拍一张原始图像(320×240)
  2. 缩放到 96×96 输入模型 → 得到 96×96 的皮肤概率图
  3. 上采样回 320×240 → 形成精确 mask
  4. 对原图分区域处理:
    - 非皮肤区域:直通
    - 皮肤区域:磨皮 + 提亮 + 保边

磨皮算法:为什么不用高斯模糊?

因为高斯模糊太“傻”了——它不分青红皂白地把整张图都抹平,结果就是眼睛变糊、唇纹消失、整个人看起来像蜡像 😵。

我们采用的是 简化版双边滤波(Bilateral Filter) ,它的特点是: 平滑噪声的同时保留边缘

数学公式长这样:

$$
I_{\text{filtered}}(p) = \frac{1}{W_p} \sum_{q \in \Omega} I(q) \cdot e^{-\frac{|p-q|^2}{2\sigma_d^2}} \cdot e^{-\frac{(I(p)-I(q))^2}{2\sigma_r^2}}
$$

其中:
- $\sigma_d$ 控制空间邻近度(越小越局部)
- $\sigma_r$ 控制颜色相似度(越大越宽容)

但在 ESP32-S3 上直接算 expf() 开销太大,所以我们做了三个优化:

✅ 查表法替代指数运算

预先生成一个 [0, 255] 范围内的 exp(-x²) 表:

const float lut_exp[256] = {
    1.0000, 0.9960, 0.9841, /* ... */ , 0.0000
};

然后查表代替 expf() ,速度提升接近 5 倍。

✅ 使用 3×3 小窗口近似

完整双边滤波通常用 9×9 或更大窗口,但我们发现 3×3 已经能满足基本需求 ,尤其是当输入已经是低分辨率 mask 时。

计算量从 81 次降为 9 次,帧率立刻起飞。

✅ 引入“强度滑动条”机制

为了适配不同用户偏好,我们实现了三级美颜模式:

typedef enum {
    BEAUTY_NATURAL,   // σ_d=1.0, σ_r=30
    BEAUTY_MEDIUM,    // σ_d=1.5, σ_r=20
    BEAUTY_STRONG     // σ_d=2.0, σ_r=15
} beauty_mode_t;

通过按键或 Web UI 切换,满足“自然派”和“网红风”的双重需求。


肤色提亮:小心“蜡黄脸”陷阱 ⚠️

单纯提高亮度很容易导致肤色发黄,特别是在暖光环境下。我们的解决方案是: 切换到 YCbCr 色彩空间再操作

步骤如下:

  1. 将 RGB565 解包 → 转为 R/G/B
  2. 转换到 YCbCr:
    $$
    Y = 0.299R + 0.587G + 0.114B \
    Cb = -0.168R - 0.331G + 0.5B \
    Cr = 0.5R - 0.418G - 0.082B
    $$
  3. 提升 Y 分量 15%~20%
  4. 适度降低 Cb/Cr(防过饱和)
  5. 转回 RGB565 输出

这样做出来的肤色更通透,不会显得油腻。

还有一个小技巧: 根据环境光自动调节增益上限 。白天可以大胆提亮,晚上则限制在 10% 以内,防止过曝。


性能调优实战:如何从 8 FPS 拉到 18 FPS?

刚开始的时候,整个流程跑下来只有 8 FPS 左右 ,卡顿感明显。经过一轮轮优化,最终达到了 18~20 FPS 的可用水平。

以下是几个最关键的突破口👇

🔧 1. 启用 FreeRTOS 多任务流水线

原本是“拍一帧 → 推理 → 渲染 → 显示 → 再拍下一帧”的串行流程,后来改成多任务并行:

xTaskCreatePinnedToCore(camera_task,      "cam", 2048, NULL, 10, NULL, 0);
xTaskCreatePinnedToCore(ai_inference_task, "ai", 4096, NULL, 8, NULL, 1);
xTaskCreatePinnedToCore(display_task,     "lcd", 2048, NULL, 5, NULL, 0);
  • Core 0(PRO_CPU):负责摄像头中断和 LCD 刷新
  • Core 1(APP_CPU):专注 AI 推理

这样一来,摄像头采集下一帧的同时,AI 正在处理上一帧,效率直接翻倍。

🔧 2. 图像缩放改用手动 nearest-neighbor

原来用 LVGL 或 OpenCV 风格的插值缩放,耗时达 15ms。后来换成最简单的 nearest-neighbor:

for (int y = 0; y < 96; y++) {
    for (int x = 0; x < 96; x++) {
        int src_x = (x * 320) / 96;
        int src_y = (y * 240) / 96;
        small_img[y*96+x] = large_img[src_y*320+src_x];
    }
}

虽然画质略有锯齿,但对模型影响不大,时间从 15ms 降到 2ms ,血赚。

🔧 3. 掩码上采样用面积平均法(Area Pooling)

将 96×96 的输出放大到 320×240,不能简单拉伸,否则会出现马赛克。

我们采用一种快速上采样方法:

for each output pixel (x,y):
    find corresponding 3x3 region in 96x96 map
    take average as final mask value

既保证了边界柔和,又避免了 bilinear 插值的浮点开销。

🔧 4. 关键函数开启编译器激进优化

sdkconfig 中添加:

COMPILER_OPTIMIZATION_PERF=y

并在代码中加入:

__attribute__((optimize("O3,unroll-loops")))
void apply_beauty_effect(...) { ... }

结合 -funroll-loops 和循环展开,热点函数性能提升可达 20%。


实际问题与应对策略 💡

任何项目都不会一帆风顺。我们在开发过程中踩了不少坑,也积累了一些实用经验。

❌ 问题 1:模型加载失败,提示 “Allocate tensors failed”

原因: tensor_arena 被分配到了 PSRAM。

✅ 解法:强制将其放在内部内存段:

DRAM_ATTR uint8_t tensor_arena[15*1024];

或者使用链接脚本指定 section。


❌ 问题 2:长时间运行后系统重启

日志显示 Interrupt wdt timeout on CPU0

✅ 解法:检查是否在中断上下文中执行了阻塞操作(如 malloc、printf)。特别是摄像头 ISR 中不要调用复杂函数,应只做 DMA 触发和队列通知。


❌ 问题 3:美颜过度,人脸看起来像塑料

✅ 解法:引入“细节融合”机制:

final_pixel = α * beautified_pixel + (1-α) * original_pixel;

其中 α 根据 mask 强度动态调整。越靠近脸部中心,α 越大;边缘区域保留更多原始纹理。


❌ 问题 4:Wi-Fi 传输视频流时帧率暴跌

原因是 JPEG 编码占用了大量 CPU 时间。

✅ 解法:
- 改用 MJPEG 流式编码,逐帧发送
- 或者启用硬件加速库(如有)
- 更优方案:通过 HTTP 发送 raw frame,由浏览器端编码


扩展玩法:不止于美颜,还能做什么?

一旦你打通了“摄像头 → AI 推理 → 图像处理 → 输出”的全链路,接下来的可能性就打开了。

🎮 手势识别切换滤镜

训练一个极简手势分类模型(手掌 / V字 / 比心),通过 GPIO 控制 TFT 切换风格。

🌐 构建本地 Web 控制台

利用内置 HTTP 服务器 + WebSocket,实现手机端远程调节美颜强度、查看帧率统计、切换滤镜模式。

esp_http_server_handle_t server;
httpd_register_uri_handler(server, &get_status_uri);
httpd_register_uri_handler(server, &set_params_uri);

📡 实现局域网 RTSP 流媒体

结合 ffmpeg + ffplay ,打造一个迷你 IP 摄像头:

ffplay rtsp://192.168.1.100:8554/stream

未来甚至可以接入 Home Assistant 或 OBS。


硬件建议与注意事项 ⚙️

别以为软件写得好就行,硬件设计同样关键。

🔋 电源一定要稳!

ESP32-S3 + 摄像头峰值电流可达 200mA 以上,普通 USB 数据线供电容易导致复位。

✅ 建议:
- 使用独立 LDO(如 AMS1117-3.3V)
- 加 100μF 电解电容 + 10μF 陶瓷电容滤波
- 避免与电机、继电器共用电源

🔥 散热不可忽视

连续 AI 推理会让芯片温度升至 70°C 以上,长期高温影响寿命。

✅ 解决方案:
- 加装小型铝制散热片(成本 < ¥2)
- 在外壳上开通风孔
- 设置温控策略:>75°C 时自动降频或暂停推理

📶 Wi-Fi 干扰问题

DVP 数据线与天线靠得太近时,可能导致信号不稳定。

✅ 布局建议:
- 摄像头排线远离 RF 区域
- 使用屏蔽线或地线包围信号线
- 固件中开启 Wi-Fi 功率控制: esp_wifi_set_max_tx_power(78) (单位 0.25dBm)


用户体验设计:技术之外的人性化考量 ❤️

再强的技术,也要服务于人。

我们在实际测试中收集了很多反馈,总结出几个提升体验的关键点:

✅ 物理按键比触屏更可靠

在浴室等潮湿环境中,触摸屏容易误触。我们加了两个轻触按键:
- 短按:切换美颜等级
- 长按:拍照保存(需接 SD 卡)

✅ 自动亮度感应很重要

搭配 BH1750 光照传感器,LCD 背光随环境自动调节,避免夜间刺眼。

✅ 默认“自然模式”最安全

第一次开机时,默认开启最低强度美颜。很多人其实不喜欢“过度处理”的感觉,宁愿真实一点。

✅ 提供关闭 AI 的选项

有些人就是想看看真实的自己。所以我们在 Web 界面加了个开关:“AI 增强:ON/OFF”。

尊重选择,才是真正的智能。


写在最后:边缘 AI 的真正意义是什么?

这个项目最让我兴奋的地方,不是“我能在 ESP32 上跑 AI”,而是:

我们正在让‘智能’变得真正属于用户自己。

不需要把照片上传到某个云平台,不需要授权 App 读取相册,不需要担心哪天服务停了设备就变砖。

一切都在本地发生。你的脸,始终掌握在你自己手里。

而这,或许才是边缘 AI 最大的价值——
不是替代人类,也不是炫耀技术,而是 在保护隐私的前提下,赋予每个人一点点温柔的力量

就像一面懂你的镜子,不说太多话,只是悄悄把你最好的样子还给你 💫

更多推荐