AI 镜像项目:ESP32-S3 实现美颜滤镜
用一块 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 负责精准定位,传统算法负责精细修饰 。
具体流程如下:
- 拍一张原始图像(320×240)
- 缩放到 96×96 输入模型 → 得到 96×96 的皮肤概率图
- 上采样回 320×240 → 形成精确 mask
-
对原图分区域处理:
- 非皮肤区域:直通
- 皮肤区域:磨皮 + 提亮 + 保边
磨皮算法:为什么不用高斯模糊?
因为高斯模糊太“傻”了——它不分青红皂白地把整张图都抹平,结果就是眼睛变糊、唇纹消失、整个人看起来像蜡像 😵。
我们采用的是 简化版双边滤波(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 色彩空间再操作 。
步骤如下:
- 将 RGB565 解包 → 转为 R/G/B
-
转换到 YCbCr:
$$
Y = 0.299R + 0.587G + 0.114B \
Cb = -0.168R - 0.331G + 0.5B \
Cr = 0.5R - 0.418G - 0.082B
$$ - 提升 Y 分量 15%~20%
- 适度降低 Cb/Cr(防过饱和)
- 转回 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 最大的价值——
不是替代人类,也不是炫耀技术,而是
在保护隐私的前提下,赋予每个人一点点温柔的力量
。
就像一面懂你的镜子,不说太多话,只是悄悄把你最好的样子还给你 💫
更多推荐


所有评论(0)