在售价 2 美元的微控制器上实现 SIMD 加速的计算机视觉
摘要
作者为 2 美元的 ESP32-S3 微控制器编写了利用 128 位 SIMD 指令加速的 FAST 特征检测器,性能约为参考实现的两倍,处理 QVGA 帧约需 6ms,并顺带开发了自用的寄存器分配器 basm。
暂无内容
查看缓存全文
缓存时间: 2026/10/27 02:07
# 在 2 美元的微控制器上实现 SIMD 加速的计算机视觉 — shraiwi
来源:https://shraiwi.com/blog/simd-fast-esp32s3/
> 我为 ESP32\-S3 编写了一个加速版 FAST 特征检测器,利用了它的 128\-bit SIMD 指令。它的性能大约是参考实现的两倍,处理一帧 QVGA \(320x240\) 图像只需约 6ms。源代码在这里 (https://github.com/shraiwi/simd-fast-esp32s3)。
以这个价格来说,ESP32\-S3 是一款性能强劲的微控制器。它不起眼的塑料封装内,藏着一颗最高运行频率为 240MHz 的双核 CPU,以及一系列外设,包括 WiFi 和低功耗蓝牙无线模块。在翻阅它的技术参考手册 (https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf)时,我发现这颗芯片支持一组有限的 SIMD 指令。对于一颗比普通咖啡还便宜的芯片来说,这相当酷。让我们看看能用它做些什么有趣的事情吧!
在编写 SIMD 加速的 FAST 角点检测器的过程中,我学会了 ESP32\-S3 汇编的基础知识,绕过了它那些古怪的限制,最后还自己写了一个基础的寄存器分配器,`basm`。
---
又在网上查了一番之后,我意识到这些指令其实并不是什么秘密 (https://bitbanksoftware.blogspot.com/2024/01/surprise-esp32-s3-has-few-simd.html),只是鲜为人知。我想亲自试用它们,写出比网上能找到的示例更高级的代码。由于最近我对计算机视觉很感兴趣,所以决定为 ESP32\-S3 编写一个 SIMD 加速的 FAST 特征检测器 (https://en.wikipedia.org/wiki/Features_from_accelerated_segment_test)实现。
我先从寻找使用 SIMD 指令的示例代码入手。截至撰写本文时,我只能找到三个来源:
1. Larry Bank 的博客 (https://bitbanksoftware.blogspot.com/),他在其中探索了这一功能,并提供了一个最小示例 (https://bitbanksoftware.blogspot.com/2024/01/esp32-s3-simd-minimal-example.html)
2. Espressif 的 `esp\-dsp` (https://github.com/espressif/esp-dsp) 库,用于实现类似 DSP 的功能 \(卷积、FFT 等\)
3. Espressif 的 `esp\-dl` (https://github.com/espressif/esp-dl) 库,用于片上 AI 加速。
仔细看过这些代码之后,我松了口气:ESP32\-S3 的汇编并没有那么难。我最终采用了以下总体结构:
``
.section .rodata
# 只读数据放在这里
.section .text
.align 4
.global add
.type add,@function
# 一个将两个数相加的简单函数
# int32_t add(int32_t a, int32_t b)
add:
entry sp, 48 # sp(栈指针)是 a1 的别名
#define r_a a2
#define r_b a3
# 代码放在这里
add a2, r_a, r_b # a2 = a + b
retw.n # 返回,a2 中存放返回值。
``
接着,我开始编写 SIMD 加速的 FAST 角点预检测。它的基本原理是在上、下、左、右四个方向各采样一个像素,并检查其中是否至少有三个“极端”像素。这里的极端像素指的是与中心像素的差值的绝对值超过某个阈值的像素。
我决定编写一个每次计算十六个像素的极端像素数量的函数,因为每个向量寄存器可以存放十六个八位值。具体方法是在四个方向分别采样一个像素“块”,再将它们与各自对应的中心像素进行比较。
然而,我在这里遇到了第一个限制\- 指令集架构不允许直接进行非对齐读取。假设中心像素是对齐的,那么东侧和西侧的像素块就会是非对齐的:
中心东侧西侧北侧南侧幸运的是,这颗芯片有一条指令,可以从两个拼接起来的寄存器中提取一个片段,放入另一个寄存器。借助这个功能,我读取了与中心块相邻的两个块,再从中提取片段,得到了东侧和西侧的像素块。
好了,现在像素数据到手了。接下来就可以比较了,对吧?错了。原来,ESP32\-S3 只实现了有符号八位数的比较指令。这让处理变得有些麻烦,因为我的所有像素数据都存储为无符号八位数。如果直接把无符号数据重新解释为有符号数据,就会得到一些奇怪的结果:
``
>>> np.int8(np.uint8(range(0, 256, 8)))
array([ 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80,
88, 96, 104, 112, 120, -128, -120, -112, -104, -96, -88,
-80, -72, -64, -56, -48, -40, -32, -24, -16, -8],
dtype=int8)
``
范围 `\[128 255\]` 被非线性地映射到了 `\[\-128 \-1\]`,导致所有比较操作都出了问题。要让它正常工作,我需要把数值范围从 `\[0, 255\]` 平移到 `\[\-128 127\]`。这本应只是一次简单的减法,但指令集又来添乱了:在 ESP32\-S3 上,所有 SIMD 算术运算都是饱和运算,这意味着所有大于 127 的像素值最终都会被钳制为 \-128,而不是通过下溢得到相应的值。要克服这个问题,我必须设法在不使用减法指令的情况下,并行地将每个数减去 128。起初,这在我看来是一项艰巨的任务,但最终我还是找到了办法。
假设 `x` 是被减数。它各位的位权如下:
``
-128 64 32 16 8 4 2 1
``
发现什么有趣的地方了吗?减去 128 就等于加上 \-128,因此,要计算 `x \- 128`,只需给 `x` 的最高位加一,就能让这个数“增加”\-128,因为最高位的位权正是 \-128。我们只需翻转 `x` 的最高位,就能完成这个操作。由于任意一位与 1 异或都等同于对该位取反,因此可以将 `x` 与位掩码 `0b10000000`(即 `0x80`)进行异或,选择性地翻转它的最高位。所以,`x \- 128 == x ^ 0x80`。
现在,每次载入无符号八位值时,都会先将它们与 `0x80` 异或,把它们转换到一个合适的 \(线性\) 范围内,让算术运算和比较重新变得有意义。
借助这两个技巧,我绕过了 ESP32\-S3 的限制,写出了 SIMD 加速的 FAST 角点预检测。我还使用类似的一组操作编写了 SIMD 加速的评分函数,不过这里就不展开介绍了。
最终,在我的测试中,FAST 特征检测器的吞吐量提升了约 220%,从 5\.1MP/s 提高到了 11\.2MP/s。这一性能完全能够满足实时计算机视觉任务的要求,让 ESP32\-S3 可以轻松处理 30fps 的 VGA 视频流。对于 2 美元的芯片来说,表现不错!
相似文章
@grapeot: 一个完整的机器视觉项目,从头搜集图像数据,做标注,做神经网络训练,做量化,做单片机(ESP32)上的系统构建,固件烧录和部署,部署完了发现效果不好,从头训练,然后做性能和耗能调优,最终部署。如果是一个senior scientist来做要…
作者利用GPT-5.6在10小时内完成了一个ESP32-CAM上的车库门识别项目,涵盖数据收集、标注、训练、量化与部署,展示了AI在硬件开发中的自主能力,并分享了人机协作的经验。
Home made GPU escalated quickly [video]
作者使用65536个低成本RISC-V微控制器(CH570)自制了一个GPU类并行计算集群,实现了QVGA分辨率渲染,并解决了电源、散热和编程自动化等工程挑战。
ESP32 as counter-surveillance platform
本文介绍如何使用低成本ESP32微控制器构建反监控平台,通过开源工具检测和对抗车牌识别、警用摄像机等监控技术,实现大众化的隐私保护。
SAGE:推动 2 比特向量量化大语言模型走向实际应用
SAGE 提出一种面向 2 比特权重向量量化的方法,通过能量分层分组(ESG)与自适应比特分配(ABS),在 Qwen3-30B-A3B 上保留 BF16 平均性能的 95.3%,并借助自定义 CUDA 内核实现最高 2.55 倍的端到端加速。
GameBoy on E-Paper ESP32 at 30FPS [video]
作者为带专用显示控制器的 ESP32 墨水屏开发板自写显示驱动,通过缩短波形、逐像素驱动预算和灰度校准,把 Game Boy 模拟器从不到 2FPS 提升到约 30FPS,并实现了不依赖抖动的真正四灰度级输出。