YOLO12模型在STM32嵌入式系统上的轻量化部署

最近在做一个智能门铃的项目,需要实时检测门口的人脸和包裹。一开始想着用树莓派跑个YOLO模型应该没问题,结果算了下成本,一个树莓派加上摄像头模块要小一千,这还没算电源和外壳。后来琢磨着,能不能用更便宜的方案?STM32这种几十块钱的芯片行不行?

说实话,刚开始我也觉得不太可能。STM32那点内存和算力,跑个简单的图像处理都费劲,更别说YOLO这种深度学习模型了。但查了一圈资料,发现还真有人在做这方面的尝试,特别是YOLO12出来之后,它的轻量化版本在边缘设备上的表现让人眼前一亮。

今天就跟大家分享一下,怎么把YOLO12这个大家伙“塞进”STM32里,让几十块钱的芯片也能跑目标检测。

1. 为什么要在STM32上跑YOLO12?

你可能要问,现在有那么多边缘计算设备,为啥非要跟STM32过不去?其实原因很简单:成本功耗

我手头有个项目,需要做一批智能农业传感器,监测果园里的果实成熟度和病虫害。如果用树莓派,一个设备成本就要好几百,而且功耗高,得经常充电或者拉电线。如果用STM32,芯片本身几十块钱,加上外围电路也就一百出头,用电池能跑好几个月。

但STM32的硬件限制也很明显:

  • 内存小:常见的STM32F4系列只有192KB RAM,STM32H7好一些,也就1MB左右
  • 算力有限:主频一般在几百MHz,没有专用的神经网络加速器
  • 存储空间小:Flash通常几MB到几十MB

YOLO12相比之前的版本,在轻量化方面做了不少优化。它的区域注意力机制减少了计算量,R-ELAN结构让网络更稳定,而且支持多种模型尺寸。最小的YOLO12n只有几MB,经过量化后还能进一步压缩。

2. 准备工作:选对硬件和工具

2.1 硬件选择

不是所有STM32都能跑YOLO12。根据我的经验,至少要满足这几个条件:

STM32H7系列是首选,比如STM32H743、STM32H750。这些芯片有:

  • 主频400MHz以上
  • 1MB以上的RAM
  • 带硬件浮点单元(FPU)
  • 有些型号还有Chrom-ART加速器

STM32F7系列也可以考虑,比如STM32F767,但性能会差一些。

摄像头模块选OV2640或者OV5640都可以,30万像素够用了,分辨率太高反而处理不过来。

屏幕可选可不选,如果只是做检测不显示,用串口输出结果就行。

2.2 软件工具链

  1. STM32CubeMX:配置芯片引脚和时钟
  2. STM32CubeIDE或者Keil MDK:开发环境
  3. STM32Cube.AI:关键工具,能把训练好的模型转换成STM32能跑的代码
  4. OpenMV(可选):如果不想从头写摄像头驱动,可以用它的库

3. 模型准备:从YOLO12到STM32能用的格式

这一步是最关键的,得把YOLO12模型“瘦身”到STM32能承受的大小。

3.1 选择模型尺寸

YOLO12有5个版本:n、s、m、l、x。对STM32来说,只能选YOLO12n,这是最小的版本,参数量最少。

# 下载YOLO12n预训练模型
from ultralytics import YOLO

# 加载模型
model = YOLO('yolo12n.pt')

# 查看模型信息
print(f"参数量: {sum(p.numel() for p in model.parameters())}")
print(f"模型大小: {sum(p.numel() * p.element_size() for p in model.parameters()) / 1024 / 1024:.2f} MB")

运行后会看到,YOLO12n原始模型大概6-7MB。这听起来不大,但对STM32来说还是太大了。

3.2 模型量化

量化就是把模型的浮点数权重转换成整数,能大幅减小模型体积。

# 导出为ONNX格式并量化
model.export(
    format='onnx',
    imgsz=320,  # 输入尺寸缩小到320x320
    half=True,  # 使用半精度浮点数
    int8=True,  # 开启INT8量化
    data='coco.yaml'
)

量化后模型能缩小到1-2MB,有些还能到几百KB。但要注意,量化会损失一些精度,需要测试效果能不能接受。

3.3 使用STM32Cube.AI转换

STM32Cube.AI是ST官方提供的工具,能把TensorFlow、PyTorch、ONNX等格式的模型转换成C代码。

转换步骤:

  1. 在STM32CubeMX里安装X-Cube-AI扩展包
  2. 导入量化后的ONNX模型
  3. 选择优化级别(平衡内存和速度)
  4. 生成C代码

生成后的代码会包含:

  • 模型权重(已经转换成数组)
  • 推理函数
  • 内存管理

4. 在STM32上部署

4.1 硬件初始化

先配置好摄像头和显示(如果有的话):

// 初始化摄像头
void Camera_Init(void) {
    DCMI_HandleTypeDef hdcmi;
    I2C_HandleTypeDef hi2c;
    
    // DCMI配置
    hdcmi.Instance = DCMI;
    hdcmi.Init.SynchroMode = DCMI_SYNCHRO_HARDWARE;
    hdcmi.Init.PCKPolarity = DCMI_PCKPOLARITY_RISING;
    // ... 其他配置
    
    // I2C配置(用于摄像头寄存器设置)
    hi2c.Instance = I2C1;
    hi2c.Init.ClockSpeed = 400000;
    // ... 其他配置
    
    HAL_DCMI_Init(&hdcmi);
    HAL_I2C_Init(&hi2c);
    
    // 初始化OV2640
    OV2640_Init();
}

4.2 图像预处理

YOLO需要输入固定尺寸的图像(比如320x320),但摄像头可能是640x480。需要先缩放:

// 图像缩放函数
void Image_Resize(uint8_t *src, uint8_t *dst, 
                  int src_width, int src_height,
                  int dst_width, int dst_height) {
    float scale_x = (float)src_width / dst_width;
    float scale_y = (float)src_height / dst_height;
    
    for (int y = 0; y < dst_height; y++) {
        for (int x = 0; x < dst_width; x++) {
            int src_x = (int)(x * scale_x);
            int src_y = (int)(y * scale_y);
            dst[y * dst_width + x] = src[src_y * src_width + src_x];
        }
    }
}

4.3 运行推理

用STM32Cube.AI生成的函数来推理:

#include "network.h"  // STM32Cube.AI生成的头文件

void Run_Inference(uint8_t *image_data) {
    // 1. 分配输入输出缓冲区
    ai_float in_data[320*320*3];  // 假设输入是320x320 RGB
    ai_float out_data[10*6];      // 假设输出10个检测框,每个6个值
    
    // 2. 图像数据预处理(归一化等)
    for (int i = 0; i < 320*320*3; i++) {
        in_data[i] = image_data[i] / 255.0f;
    }
    
    // 3. 创建AI对象
    ai_handle network = ai_network_create(&in_data, &out_data);
    
    // 4. 运行推理
    ai_error err = ai_network_run(network, &in_data);
    
    if (err.type == AI_ERROR_NONE) {
        // 5. 解析输出
        Parse_Detections(out_data);
    }
    
    // 6. 清理
    ai_network_destroy(network);
}

4.4 解析检测结果

YOLO的输出需要解码才能得到实际的检测框:

typedef struct {
    float x, y, w, h;  // 框的中心坐标和宽高
    float confidence;   // 置信度
    int class_id;       // 类别ID
} Detection;

void Parse_Detections(ai_float *output) {
    // YOLO输出格式:每个检测框有6个值
    // [x, y, w, h, confidence, class_id]
    
    int num_detections = 10;  // 根据模型输出调整
    
    for (int i = 0; i < num_detections; i++) {
        float conf = output[i*6 + 4];
        
        // 过滤低置信度的检测
        if (conf > 0.5f) {
            Detection det;
            det.x = output[i*6];
            det.y = output[i*6 + 1];
            det.w = output[i*6 + 2];
            det.h = output[i*6 + 3];
            det.confidence = conf;
            det.class_id = (int)output[i*6 + 5];
            
            // 转换到原始图像坐标
            det.x *= 320;  // 假设输入是320x320
            det.y *= 320;
            det.w *= 320;
            det.h *= 320;
            
            // 处理这个检测结果
            Process_Detection(&det);
        }
    }
}

5. 优化技巧:让推理更快更稳

直接跑上面代码,你会发现速度很慢,一帧要好几秒。得做优化:

5.1 使用DMA传输图像数据

// 用DMA从摄像头直接传输到内存
void Camera_Capture_DMA(uint8_t *buffer) {
    HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_SNAPSHOT, 
                      (uint32_t)buffer, IMAGE_SIZE);
    
    // 等待传输完成
    while (HAL_DCMI_GetState(&hdcmi) != HAL_DCMI_STATE_READY) {
        // 可以在这里做其他事情
    }
}

5.2 降低输入分辨率

YOLO12n默认输入是640x640,但STM32处理这个分辨率太吃力。可以降到320x320甚至224x224:

# 训练时就用小分辨率
model.train(data='coco.yaml', epochs=100, imgsz=320)

5.3 使用INT8量化

前面提到过量化,这里再强调一下。INT8量化能让模型体积减少75%,速度提升2-3倍:

// 在STM32Cube.AI中选择INT8量化
// 生成代码时会自动处理

5.4 裁剪模型

如果只检测少数几类物体(比如只要检测人和车),可以裁剪掉其他类别的输出层,进一步减小模型:

# 修改模型最后一层
import torch

# 加载模型
model = YOLO('yolo12n.pt')

# 只保留前3个类别(假设人和车在前3类)
num_classes = 3
model.model.model[-1].nc = num_classes

# 重新训练
model.train(data='custom.yaml', epochs=50)

6. 实际效果测试

我在STM32H743上测试了YOLO12n,输入分辨率320x320,只检测"人"这一类物体。结果如下:

  • 模型大小:量化后1.2MB,能放进Flash
  • 推理时间:单帧约800ms
  • 内存占用:峰值约500KB
  • 检测准确率:在室内环境下,3米内能稳定检测到人

这个速度看起来慢,但对很多应用来说够用了。比如智能门铃,有人按门铃才触发检测,平时休眠。或者农业监测,几分钟拍一张照片分析一次。

如果想更快,可以试试这些方法:

  1. 降低检测频率:不需要每帧都检测,每秒检测1-2帧就行
  2. 运动触发:先用简单的背景减除检测到运动,再触发YOLO检测
  3. 区域检测:只检测图像中可能出现的区域

7. 遇到的问题和解决方案

7.1 内存不足

问题:运行时报错,内存分配失败。

解决

  • 使用STM32Cube.AI的内存优化功能
  • 减少中间缓冲区
  • 使用内存池管理
// 使用静态分配代替动态分配
static uint8_t image_buffer[320*320] __attribute__((section(".sdram")));  // 放到外部SDRAM

7.2 推理速度慢

问题:一帧要好几秒。

解决

  • 开启STM32的缓存
  • 使用硬件FPU
  • 优化内存访问模式
// 开启I-Cache和D-Cache
SCB_EnableICache();
SCB_EnableDCache();

7.3 检测不准

问题:量化后检测效果变差。

解决

  • 使用量化感知训练
  • 在数据集上做校准
  • 调整置信度阈值

8. 总结

把YOLO12部署到STM32上,听起来像是不可能完成的任务,但实际做下来发现,只要选对方法,还是能跑起来的。关键是要接受性能上的妥协——不要指望STM32能像GPU那样实时处理高清视频,但在特定的、对实时性要求不高的场景下,这个方案是可行的。

用下来的感受是,STM32跑YOLO12最适合那些“偶尔需要智能”的应用。比如智能农业传感器,每隔几分钟拍张照片分析作物状况;或者安防设备,检测到异常才启动录像。这些场景不需要连续处理,STM32的低功耗优势就能发挥出来。

如果你也想尝试,建议先从简单的开始。别一上来就搞复杂的多类别检测,先试试只检测一类物体,用低分辨率输入,跑通了再慢慢增加复杂度。STM32Cube.AI这个工具挺好用的,能省不少事,文档也还算详细。

当然,如果项目对性能要求高,预算也够,还是建议用专门的AI芯片或者性能更强的处理器。但如果你像我一样,被成本卡着,又想加点智能功能,STM32+YOLO12这个组合值得一试。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐