AI 边缘部署:MCU 上的轻量级目标检测,从 YOLO 到 TFLite Micro 的全链路优化

cover

一、MCU 上跑目标检测:为什么"不可能"正在变成"勉强可以"

在 STM32H7 这类高端 MCU 上,RAM 通常只有 1MB,Flash 只有 2MB,算力约 480 DMIPS。而 YOLOv5s 模型即使量化到 INT8,参数量仍有 7.2M——光模型就放不进 Flash。更不用说 YOLO 推理过程中间激活值的内存需求,动辄数 MB。

然而,边缘 AI 的需求是真实的:工业质检需要在摄像头端实时检测缺陷;智能家居需要在本地识别入侵行为;可穿戴设备需要在端侧监测健康异常。这些场景无法容忍云端推理的延迟和隐私风险。

解决方案不是把 YOLO 硬塞进 MCU,而是从模型架构、量化策略、推理引擎三个维度同时压缩。MobileNet + SSDLite、FOMO(Faster Objects More Objects)等专为 MCU 设计的检测架构,配合 INT8 量化和 TFLite Micro 的算子融合,可以在 512KB RAM 的 MCU 上实现 10 FPS 的低分辨率目标检测。

二、MCU 目标检测的技术链路

2.1 模型选型:从 YOLO 到 FOMO

模型 输入分辨率 参数量 INT8 模型大小 RAM 峰值 适用 MCU
YOLOv5n 640×640 1.9M 1.9MB ~30MB 不适用
MobileNetV2-SSDLite 320×320 4.3M 1.1MB ~5MB Cortex-A
MobileNetV1-SSDLite 192×192 1.4M 400KB ~1.5MB Cortex-M7 (H7)
FOMO (Edge Impulse) 96×96 80K 60KB ~200KB Cortex-M4
flowchart TD
    A[训练数据集] --> B[模型架构选择]
    B --> C{目标 MCU 级别?}
    C -- Cortex-A / 有 NPU --> D[MobileNetV2-SSDLite<br/>320×320, INT8]
    C -- Cortex-M7 / 1MB RAM --> E[MobileNetV1-SSDLite<br/>192×192, INT8]
    C -- Cortex-M4 / 512KB RAM --> F[FOMO 架构<br/>96×96, INT8]
    D --> G[TFLite / NCNN 推理]
    E --> H[TFLite Micro 推理]
    F --> I[TFLite Micro 推理]
    G --> J[10-30 FPS]
    H --> K[5-15 FPS]
    I --> L[10-20 FPS]

2.2 量化策略:PTQ 与 QAT 的精度权衡

训练后量化(PTQ)实现简单但精度损失较大,量化感知训练(QAT)精度更高但需要完整训练流水线:

flowchart LR
    A[FP32 模型] --> B{量化方式?}
    B -- PTQ --> C[校准数据集<br/>统计激活值分布]
    C --> D[INT8 量化<br/>精度损失 2-5%]
    B -- QAT --> E[插入伪量化节点<br/>模拟 INT8 截断]
    E --> F[微调训练<br/>恢复精度]
    F --> G[INT8 量化<br/>精度损失 < 1%]
    D --> H[部署到 MCU]
    G --> H

三、TFLite Micro 推理引擎的工程实现

3.1 模型转换与量化

import tensorflow as tf
import numpy as np
from typing import Optional


def convert_to_tflite_micro(
    keras_model: tf.keras.Model,
    representative_dataset: Optional[tf.data.Dataset] = None,
    quantize: bool = True,
    input_shape: tuple = (96, 96, 3),
) -> bytes:
    """将 Keras 模型转换为 TFLite Micro 兼容的 INT8 模型"""

    converter = tf.lite.TFLiteConverter.from_keras_model(keras_model)

    if quantize:
        converter.optimizations = [tf.lite.Optimize.DEFAULT]

        # 提供代表性数据集用于校准量化参数
        if representative_dataset is not None:
            def rep_dataset():
                for data in representative_dataset.batch(1).take(100):
                    yield [tf.cast(data, tf.float32)]

            converter.representative_dataset = rep_dataset

            # 强制全 INT8 量化(包括输入输出)
            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()

    # 验证模型是否兼容 TFLite Micro 的算子集
    interpreter = tf.lite.Interpreter(model_content=tflite_model)
    interpreter.allocate_tensors()

    input_details = interpreter.get_input_details()
    output_details = interpreter.get_output_details()

    print(f"模型大小: {len(tflite_model) / 1024:.1f} KB")
    print(f"输入: shape={input_details[0]['shape']}, dtype={input_details[0]['dtype']}")
    print(f"输出: shape={output_details[0]['shape']}, dtype={output_details[0]['dtype']}")

    return tflite_model


def benchmark_tflite_model(
    tflite_model: bytes,
    test_input: np.ndarray,
    num_runs: int = 100,
) -> dict:
    """基准测试 TFLite 模型的推理延迟和内存占用"""
    interpreter = tf.lite.Interpreter(model_content=tflite_model)
    interpreter.allocate_tensors()

    input_details = interpreter.get_input_details()
    output_details = interpreter.get_output_details()

    # 量化输入数据
    input_scale, input_zero_point = input_details[0]['quantization']
    if input_scale > 0:
        quantized_input = np.round(
            test_input / input_scale + input_zero_point
        ).astype(np.int8)
    else:
        quantized_input = test_input.astype(np.float32)

    # 预热
    interpreter.set_tensor(input_details[0]['index'], quantized_input)
    interpreter.invoke()

    # 计时推理
    latencies = []
    for _ in range(num_runs):
        start = tf.timestamp()
        interpreter.set_tensor(input_details[0]['index'], quantized_input)
        interpreter.invoke()
        latencies.append((tf.timestamp() - start) * 1000)

    # 估算内存占用(张量内存)
    tensor_memory = sum(
        t['bytes'] for t in interpreter.get_tensor_details()
    )

    return {
        'avg_latency_ms': np.mean(latencies),
        'p99_latency_ms': np.percentile(latencies, 99),
        'tensor_memory_kb': tensor_memory / 1024,
        'model_size_kb': len(tflite_model) / 1024,
    }

3.2 MCU 端 C++ 推理代码

// tflite_micro_detector.h — MCU 端目标检测推理接口
#ifndef TFLITE_MICRO_DETECTOR_H_
#define TFLITE_MICRO_DETECTOR_H_

#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/schema/schema_generated.h"

// 检测结果结构体
typedef struct {
    float x;          // 中心点 x 坐标(归一化 0-1)
    float y;          // 中心点 y 坐标(归一化 0-1)
    float width;      // 宽度(归一化 0-1)
    float height;     // 高度(归一化 0-1)
    float confidence; // 置信度
    int8_t class_id;  // 类别 ID
} Detection;

class TFLiteMicroDetector {
public:
    // 初始化推理引擎,arena_size 为张量内存池大小
    static TFLiteMicroDetector* Create(
        const uint8_t* model_data,
        size_t model_size,
        uint8_t* tensor_arena,
        size_t arena_size,
        float confidence_threshold = 0.5f
    );

    // 执行推理:输入 RGB 图像数据,输出检测结果
    int Detect(
        const int8_t* input_data,
        int input_width,
        int input_height,
        Detection* detections,
        int max_detections
    );

    // 获取推理延迟(微秒)
    uint32_t GetLastInferenceTimeUs() const { return last_inference_time_us_; }

private:
    TFLiteMicroDetector() = default;
    tflite::MicroInterpreter* interpreter_ = nullptr;
    float confidence_threshold_ = 0.5f;
    float input_scale_ = 1.0f;
    int input_zero_point_ = 0;
    float output_scale_ = 1.0f;
    int output_zero_point_ = 0;
    uint32_t last_inference_time_us_ = 0;
};

#endif  // TFLITE_MICRO_DETECTOR_H_
sequenceDiagram
    participant CAM as 摄像头
    participant PRE as 预处理<br/>(缩放+量化)
    participant TFL as TFLite Micro<br/>推理引擎
    participant POST as 后处理<br/>(NMS+反量化)
    participant APP as 应用层

    CAM->>PRE: 原始帧 (320×240 RGB)
    PRE->>PRE: 缩放到 96×96
    PRE->>PRE: 量化为 INT8
    PRE->>TFL: 输入张量 (1×96×96×3)
    TFL->>TFL: INT8 推理
    TFL->>POST: 输出张量 (检测框+类别)
    POST->>POST: 反量化为 FP32
    POST->>POST: NMS 去重
    POST->>APP: Detection[] 结果数组
    Note over TFL: 推理延迟: 50-100ms<br/>RAM 峰值: ~200KB

四、MCU 目标检测的边界与权衡

4.1 精度与资源的天平

96×96 的输入分辨率意味着模型无法识别小目标。在工业质检场景中,如果缺陷像素占比低于 5%,FOMO 架构几乎无法检出。提高输入分辨率可以改善小目标检测,但 RAM 需求呈平方增长——从 96×96 提升到 192×192,RAM 峰值增加约 4 倍。

4.2 算子兼容性限制

TFLite Micro 支持的算子集合远小于 TFLite 标准版。某些检测头中的自定义算子(如 Deformable Convolution)无法在 MCU 上运行。模型设计阶段必须确认所有算子都在 TFLite Micro 的支持列表中,否则需要手动实现自定义算子——这在 MCU 上是极其昂贵的工程投入。

4.3 推理延迟与功耗的矛盾

提高推理帧率意味着更频繁地唤醒 MCU,增加功耗。电池供电的 IoT 设备通常需要在检测性能和续航之间做取舍。一种务实策略是使用低功耗传感器触发(如 PIR 人体感应),只在有事件时才启动推理,待机功耗可降至微安级。

4.4 模型更新困难

MCU 的模型存储在 Flash 中,OTA 更新需要完整的固件刷写流程。频繁更新模型不现实,模型必须在部署前经过充分验证。这与云端模型"快速迭代"的理念相悖,要求 MCU 模型在精度和鲁棒性上有更高的基线要求。

五、总结

MCU 上的目标检测不是把云端模型缩小那么简单,而是从模型架构、量化策略到推理引擎的全链路优化。FOMO 和 MobileNet-SSDLite 是当前 MCU 检测的主流选择,配合 INT8 全量化可将模型压缩到 60-400KB,RAM 峰值控制在 200KB-1.5MB。

工程落地的关键决策:模型选型先确定 MCU 的 RAM/Flash 预算,再倒推输入分辨率和架构;量化优先尝试 PTQ,精度不足时再投入 QAT 的训练成本;TFLite Micro 的算子兼容性必须在模型设计阶段验证,不要等到部署时才发现不支持;功耗敏感场景采用传感器触发推理,避免持续运行。

MCU 目标检测的精度天花板远低于云端,但在延迟、隐私和成本上具有不可替代的优势。选择 MCU 部署的前提是:应用场景对精度的容忍度高于对实时性和隐私性的要求。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐