别驼背!使用 ESP32 和 TensorFlow Lite 构建 TinyML 姿势守护者 🚀
Source: Dev.to
停止驼背:使用 ESP32 与 TensorFlow Lite 构建 TinyML 姿势守护者
在这个项目中,我们将利用 TinyML(在微控制器上运行的机器学习)来实时检测坐姿是否正确。通过 ESP32、加速度计以及 TensorFlow Lite,我们可以创建一个小巧、低功耗的姿势监测器,当检测到驼背时会发出提醒。
目录
项目概述
- 目标:实时检测用户是否出现驼背姿势,并通过 LED 或蜂鸣器发出警示。
- 核心技术:
- TinyML:在资源受限的 MCU 上运行轻量级神经网络。
- TensorFlow Lite for Microcontrollers(TFLM):将训练好的模型部署到 ESP32。
- 加速度计(MPU‑6050):捕获用户的姿势数据。
所需硬件
| 编号 | 零件 | 备注 |
|---|---|---|
| 1 | ESP32 开发板(如 ESP32‑DevKitC) | 主控,负责模型推理与提醒 |
| 2 | MPU‑6050 6轴加速度计/陀螺仪 | 采集姿势数据 |
| 3 | LED(可选) | 视觉提醒 |
| 4 | 有源蜂鸣器(可选) | 声音提醒 |
| 5 | 面包板 + 跳线 | 原型搭建 |
| 6 | USB‑C / Micro‑USB 线 | 为 ESP32 供电并烧录固件 |
提示:如果你想让装置更隐蔽,可以将 ESP32 与 MPU‑6050 集成到一个 3D 打印外壳中,佩戴在背部。
软件准备
- Arduino IDE(或 PlatformIO)
- ESP32 Board Support(在 Arduino Boards Manager 中添加
https://dl.espressif.com/dl/package_esp32_index.json) - Python 3.8+(用于模型训练)
- TensorFlow 2.x(
pip install tensorflow) - tflite‑micro‑tools(用于生成 C 头文件)
数据采集与模型训练
1. 采集姿势数据
使用下面的 Arduino 示例代码读取 MPU‑6050 的加速度值,并通过串口发送到电脑:
#include <Wire.h>
#include <MPU6050.h>
MPU6050 mpu;
void setup() {
Serial.begin(115200);
Wire.begin();
mpu.initialize();
if (!mpu.testConnection()) {
Serial.println("MPU6050 connection failed");
while (1);
}
}
void loop() {
int16_t ax, ay, az;
mpu.getAcceleration(&ax, &ay, &az);
Serial.print(ax); Serial.print(",");
Serial.print(ay); Serial.print(",");
Serial.println(az);
delay(100);
}- 坐姿:保持背部挺直,记录 2000 条样本。
- 驼背:故意向前弯曲,记录 2000 条样本。
将串口输出保存为 CSV 文件,列顺序为 ax, ay, az, label(label=0 为坐姿,label=1 为驼背)。
2. 预处理
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
df = pd.read_csv('posture_data.csv')
X = df[['ax','ay','az']].values
y = df['label'].values
# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42, stratify=y)3. 构建轻量级模型
import tensorflow as tf
from tensorflow.keras import layers, models
model = models.Sequential([
layers.Input(shape=(3,)),
layers.Dense(16, activation='relu'),
layers.Dense(8, activation='relu'),
layers.Dense(1, activation='sigmoid')
])
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
model.fit(X_train, y_train, epochs=30, batch_size=32,
validation_data=(X_test, y_test))结果:在测试集上可以轻松达到 > 95% 的准确率,足以满足实时姿势检测需求。
模型转换为 TFLite
# 将 Keras 模型转换为 TensorFlow Lite
converter = tf.lite.TFLiteConverter.from_keras_model(model)
# 为 TinyML 启用整数量化
def representative_dataset():
for data in tf.data.Dataset.from_tensor_slices(X_train).batch(1).take(100):
yield [data.astype(np.float32)]
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8
tflite_model = converter.convert()
# 保存模型
with open('posture_model.tflite', 'wb') as f:
f.write(tflite_model)生成 C 头文件
xxd -i posture_model.tflite > posture_model.hposture_model.h 将包含一个 unsigned char 数组,可直接在 Arduino 项目中引用。
在 ESP32 上部署模型
1. 引入 TensorFlow Lite Micro 库
在 Arduino IDE 中打开 库管理器,搜索并安装 TensorFlowLite_ESP32(或手动复制 tensorflow/lite/micro 目录到 libraries 文件夹)。
2. 主程序结构
#include <Arduino.h>
#include <Wire.h>
#include <MPU6050.h>
#include "posture_model.h"
#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/micro/micro_error_reporter.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
MPU6050 mpu;
// TensorFlow Lite 相关对象
static tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = µ_error_reporter;
const tflite::Model* model = nullptr;
tflite::MicroInterpreter* interpreter = nullptr;
TfLiteTensor* input = nullptr;
TfLiteTensor* output = nullptr;
// 为模型分配的张量缓冲区(根据模型大小自行调整)
constexpr int kTensorArenaSize = 2 * 1024;
uint8_t tensor_arena[kTensorArenaSize];
void setup() {
Serial.begin(115200);
Wire.begin();
// 初始化 MPU6050
mpu.initialize();
if (!mpu.testConnection()) {
Serial.println("MPU6050 connection failed!");
while (1);
}
// 加载模型
model = tflite::GetModel(posture_model_tflite);
if (model->version() != TFLITE_SCHEMA_VERSION) {
error_reporter->Report("Model schema version mismatch!");
while (1);
}
// 解析器(使用全部内置算子)
static tflite::AllOpsResolver resolver;
// 创建解释器
static tflite::MicroInterpreter static_interpreter(
model, resolver, tensor_arena, kTensorArenaSize, error_reporter);
interpreter = &static_interpreter;
// 分配张量内存
TfLiteStatus allocate_status = interpreter->AllocateTensors();
if (allocate_status != kTfLiteOk) {
error_reporter->Report("AllocateTensors() failed");
while (1);
}
// 获取指向输入/输出张量的指针
input = interpreter->input(0);
output = interpreter->output(0);
// 初始化提醒硬件(LED + 蜂鸣器)
pinMode(LED_BUILTIN, OUTPUT);
pinMode(27, OUTPUT); // 假设蜂鸣器连接在 GPIO27
}
void loop() {
// 读取加速度计原始值
int16_t ax, ay, az;
mpu.getAcceleration(&ax, &ay, &az);
// 将原始值标准化(与训练时使用的 scaler 相同)
// 这里使用简化的线性映射,仅作示例
float ax_f = (float)ax / 16384.0f; // 1g ≈ 16384 LSB
float ay_f = (float)ay / 16384.0f;
float az_f = (float)az / 16384.0f;
// 填充模型输入(int8 量化)
// 假设量化参数为 scale=0.1, zero_point=0
input->data.int8[0] = static_cast<int8_t>(ax_f / 0.1f);
input->data.int8[1] = static_cast<int8_t>(ay_f / 0.1f);
input->data.int8[2] = static_cast<int8_t>(az_f / 0.1f);
// 运行推理
TfLiteStatus invoke_status = interpreter->Invoke();
if (invoke_status != kTfLiteOk) {
error_reporter->Report("Invoke failed!");
return;
}
// 读取输出(int8 -> float)
int8_t raw_pred = output->data.int8[0];
float prob = (raw_pred - output->params.zero_point) * output->params.scale;
// 判定阈值(>0.5 视为驼背)
if (prob > 0.5f) {
// 驼背 → 触发提醒
digitalWrite(LED_BUILTIN, HIGH);
digitalWrite(27, HIGH);
} else {
digitalWrite(LED_BUILTIN, LOW);
digitalWrite(27, LOW);
}
// 为了降低功耗,延时 200ms
delay(200);
}关键点说明:
- 量化一致性:训练时使用的
StandardScaler必须在 MCU 端以相同方式进行归一化。这里示例使用了简化的线性映射,你可以把scaler.mean_与scaler.scale_的数值硬编码进代码,以获得更精确的结果。 - 张量缓冲区大小:
kTensorArenaSize需要根据模型大小进行调节。若出现 “tensor arena is full” 错误,请适当增大该值(如 4 KB)。 - 功耗优化:可以在
loop()结束后调用esp_sleep_enable_timer_wakeup()并进入深度睡眠,以实现数小时的电池续航。
代码说明
| 部分 | 作用 |
|---|---|
| MPU6050 初始化 |
介绍
作为开发者,我们每天要在键盘前弯腰工作 8 到 12 小时。本指南展示了如何使用 TinyML、ESP32 和 TensorFlow Lite for Microcontrollers 构建一款实时姿势纠正可穿戴设备。该设备在本地检测 Slouching 与 Good Posture,并提供即时的触觉反馈,无需任何云端依赖。
系统概述
graph TD
A[MPU6050 Accelerometer] -->|Raw X,Y,Z Data| B[ESP32 Buffer]
B -->|Normalization| C[Feature Vector]
C -->|TFLite Micro Inference| D{Model Prediction}
D -->|Slouching Detected| E[Vibration Motor PWM]
D -->|Good Posture| F[Stay Silent]
E -->|Feedback| G[User Fixes Posture]
G --> A硬件要求
- ESP32 (DevKit V1)
- MPU6050 3‑轴加速度计/陀螺仪
- 小型振动马达(用于触觉反馈)
- 可选:OLED 显示屏、BLE 模块、电池(例如 500 mAh)
软件栈
- Arduino IDE 或 PlatformIO
- Python(用于数据收集和模型训练)
- TensorFlow Lite for Microcontrollers
- C++(ESP‑IDF/Arduino 框架)
数据收集
在故意驼背和坐直时捕获加速度计数据。每种姿势至少记录 5 分钟,以获得稳健的数据集。
// Simple data logger snippet (Arduino)
void loop() {
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
Serial.print(a.acceleration.x); Serial.print(",");
Serial.print(a.acceleration.y); Serial.print(",");
Serial.println(a.acceleration.z);
delay(50); // 20 Hz sampling
}模型设计与训练
轻量级的全连接网络足以用于二分类。
import tensorflow as tf
model = tf.keras.Sequential([
tf.keras.layers.InputLayer(input_shape=(12,)), # 4 samples × (X,Y,Z)
tf.keras.layers.Dense(16, activation='relu'),
tf.keras.layers.Dense(8, activation='relu'),
tf.keras.layers.Dense(2, activation='softmax') # [Good, Slouch]
])
# Convert to TFLite with post‑training quantization
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()训练后量化
量化将模型从 32 位浮点数降低为 8 位整数,使其能够适配 ESP32 有限的 SRAM。
在 ESP32 上部署模型
在将模型转换为 C 数组(model_data.h)后,使用 TensorFlow Lite for Microcontrollers(TFLM)进行推理。
#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "model_data.h" // Exported model array
// Memory pool for TFLM
constexpr int kTensorArenaSize = 8 * 1024;
uint8_t tensor_arena[kTensorArenaSize];
tflite::MicroInterpreter* interpreter;
void setup_model() {
static tflite::MicroMutableOpResolver resolver;
resolver.AddFullyConnected();
resolver.AddSoftmax();
static tflite::MicroInterpreter static_interpreter(
model, resolver, tensor_arena, kTensorArenaSize);
interpreter = &static_interpreter;
interpreter->AllocateTensors();
}
void run_inference(float* input_data) {
// Copy input data to the model's input tensor
float* model_input = interpreter->input(0)->data.f;
for (int i = 0; i Invoke();
// Evaluate result
float slouch_prob = interpreter->output(0)->data.f[1];
if (slouch_prob > 0.8f) {
digitalWrite(VIBRATOR_PIN, HIGH); // Trigger vibration
} else {
digitalWrite(VIBRATOR_PIN, LOW);
}
}功耗优化
- 降低 CPU 频率:将 ESP32 运行在 80 MHz 而不是 240 MHz。
- 轻度睡眠:在采样间隔之间调用
esp_light_sleep_start()。 - 基于中断的采样:使用 MPU6050 的 FIFO 缓冲区,仅在数据足够时唤醒 ESP32。
如需深入了解超低功耗(ULP)协处理器的使用,请参阅 WellAlly Tech Blog.
下一步
- 添加 OLED 显示屏 以显示实时的“健康评分”。
- BLE 连接 用于在移动应用上记录姿势历史。
- 尝试 RNN(例如 LSTM)以实现更细致的手势识别。
参考文献
- 详细的传感器噪声处理教程: https://wellally.tech/blog
- 超低功耗 ESP32 指南: https://wellally.tech/blog/ulp
祝你玩得开心,坐直点! 🦴✨