别驼背!使用 ESP32 和 TensorFlow Lite 构建 TinyML 姿势守护者 🚀

发布: (2026年3月15日 GMT+8 09:30)
11 分钟阅读
原文: Dev.to

Source: Dev.to

停止驼背:使用 ESP32 与 TensorFlow Lite 构建 TinyML 姿势守护者

在这个项目中,我们将利用 TinyML(在微控制器上运行的机器学习)来实时检测坐姿是否正确。通过 ESP32、加速度计以及 TensorFlow Lite,我们可以创建一个小巧、低功耗的姿势监测器,当检测到驼背时会发出提醒。


目录

  1. 项目概述
  2. 所需硬件
  3. 软件准备
  4. 数据采集与模型训练
  5. 模型转换为 TFLite
  6. 在 ESP32 上部署模型
  7. 代码说明
  8. 测试与调试
  9. 结论与后续改进

项目概述

  • 目标:实时检测用户是否出现驼背姿势,并通过 LED 或蜂鸣器发出警示。
  • 核心技术
    • TinyML:在资源受限的 MCU 上运行轻量级神经网络。
    • TensorFlow Lite for Microcontrollers(TFLM):将训练好的模型部署到 ESP32。
    • 加速度计(MPU‑6050):捕获用户的姿势数据。

所需硬件

编号零件备注
1ESP32 开发板(如 ESP32‑DevKitC)主控,负责模型推理与提醒
2MPU‑6050 6轴加速度计/陀螺仪采集姿势数据
3LED(可选)视觉提醒
4有源蜂鸣器(可选)声音提醒
5面包板 + 跳线原型搭建
6USB‑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.xpip 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);
}
  1. 坐姿:保持背部挺直,记录 2000 条样本。
  2. 驼背:故意向前弯曲,记录 2000 条样本。

将串口输出保存为 CSV 文件,列顺序为 ax, ay, az, labellabel=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.h

posture_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 = &micro_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 构建一款实时姿势纠正可穿戴设备。该设备在本地检测 SlouchingGood 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)以实现更细致的手势识别。

参考文献

祝你玩得开心,坐直点! 🦴✨

0 浏览
Back to Blog

相关文章

阅读更多 »