智能门禁要根据人脸判断是否放行,交互设备要知道当前是谁在使用。摄像头找到一张脸之后,还需要把这张脸与已经录入的人员对应起来。
本期博主使用 STM32N6570-DK 的 ISP 工程,部署两个模型:自己训练的 YOLO 人脸及关键点检测模型负责找到人脸,MobileFaceNet 特征模型负责提取身份特征。程序将特征与注册模板比较,再把人脸框、五个关键点和匹配到的人员号码显示在 800×480 屏幕上。
不同于依赖云端、WiFi 的人脸识别方案,整套人脸检测与身份比对完全在 STM32 本地离线完成,不依赖网络、不上传人脸图像。一方面可以保障人脸隐私数据不出设备,非常适合门禁、本地交互这类对数据敏感的场景;另一方面脱离网络延迟,从抓拍、检测、对齐、特征提取到识别出结果全部毫秒级闭环,断网环境也能稳定工作。同时相比搭载 Linux 的高端 SOC 方案,STM32N6 的功耗、启动开销更低,借助内置 Neural‑ART NPU,用一颗微控制器就同时跑检测 + 特征双模型,为小型化、低功耗的本地智能设备提供轻量化落地路线。
1、人脸识别的原理
人脸识别不是 “拿新照片去和人脸库里的人脸照片比像素”。光照变一点、头偏一点、离镜头远近不同,同一张脸的像素都不一样。比像素,或者说直接比裁剪下来的小图,很容易把同一个人判成不同人。
所以工程上把 “认人” 拆成一件件小事,本质是把认脸这件事,变成算一串数字。
这里可以借用信号处理中的一个思路来理解。
比如分析两段信号是否具有相似特性,我们不一定只比较它们的时域波形,还可以通过频谱分析,提取其中的频率成分进行比较。即使两段信号在时域上存在一定差异,它们仍然可能具有相似的频谱特征。
人脸识别也是类似的思路。我们不直接比较两张照片的像素,而是利用神经网络,将一张人脸转换成一组能够描述其特征的数字。
我们使用 MobileFaceNet 会将一张 112×112 的人脸图像转换成 128 维特征向量。也就是说,一张人脸最终可以用 128 个浮点数来表示。
当需要识别身份时,程序只需要提取当前人脸的特征,再与已经注册的特征进行比较。如果两组特征足够相似,就可以认为它们可能来自同一个人。
当然,这并不意味着特征完全不受光照和姿态影响,只是相比直接比较像素,经过训练的特征模型通常能够更好地保留与身份相关的信息。
2、两个模型分别做什么
第一个模型,输入是 256×256 的 uint8 RGB 图像,输出是 [1,1344,20] 的 float32 数据。
每组 20 个数包含人脸框的四个坐标、一个人脸置信度,以及左眼、右眼、鼻尖、左嘴角、右嘴角的坐标和置信度。程序做置信度筛选和 NMS 后,就知道画面中有哪些人脸、每张脸的位置在哪里。也就是我们上一期所完成的内容,详情可以查看上一期文章:
第二个模型,输入是 112×112 的 uint8 RGB 人脸,最终输出 128 个 float32 数值。它们组成一组用于比较人脸的特征,也叫 embedding。
注册人员,就是保存这个人的特征模板。再次出现时,程序提取新的特征,再与模板比较。
因此,增加一个人员号码不需要重新训练模型,可以直接在开发板上完成录入,实现一个本地离线运行可以随意注册和删除人脸的效果。
3、摄像头工作流和 CubeMX 双模型配置
开发板使用 IMX335 摄像头。Pipe1 输出 800×480 RGB888,用于屏幕预览;Pipe2 输出 256×192 RGB,提供给两个模型使用。
这两个模型要求正方形输入,因此程序在相机图像上下各补 32 行 RGB 114,组成 256×256 输入。检测结果中的纵坐标包含这部分补边,裁剪人脸时先减去 32,回到原来的相机图像坐标。
接着根据五个关键点,把人脸对齐成 112×112 图像。
大伙可以把对齐理解为:用眼睛、鼻子和嘴角确定位置,再调整人脸的旋转、大小和位置,让不同帧中的脸落在比较接近的位置上。工程使用五点相似变换和双线性插值完成这一步;关键点不足时,回退到按人脸框裁剪缩放。
两个模型读取同一张已完成采集的 Pipe2 图像。这样检测框对应的脸,与送入特征模型的脸能够保持一致。
在 CubeMX 的 X-CUBE-AI 配置里,两个模型分别使用独立的 Neural-ART profile 和 memory pool:
两个模型总权重为 5.73MB,总 RAM 为 1.34MB,分别存放在外部Flash的 0x71000000 和 0x71800000
在CubeAI中点击齿轮就可以更换权重的烧录位置。模型生成代码会按配置中的地址读取权重。如果下载时随意换地址,应用就会读到错误的数据。
两个模型运行时还复用部分激活内存。激活内存可以理解为网络计算过程中的中间结果缓冲区:完成检测、把框和关键点复制到应用 RAM 后,特征模型才能使用这块空间继续计算。
3、编写推理、特征匹配和注册代码
程序先等待 Pipe2 采集完成,停止 DMA,再准备检测输入。检测结束后,将输出转换为候选框,执行 NMS,然后依次提取各张人脸的特征。下面节选工程中的调度代码:
uint32_t inference_ms = 0U;if (!AI_RunModel(&NN_Instance_facefind, &inference_ms)) return;SCB_InvalidateDCache_by_Addr((uint32_t *)buffer_out, output_len);float max_score;uint32_t candidates = Face_Decode((constfloat *)buffer_out, output_len / sizeof(float),boxes, FACE_PREDICTIONS, &max_score);face_box_count = candidates;uint32_t faces = Face_Nms(boxes, candidates);Face_ExtractDetectedEmbeddings(candidates);
这里传给特征提取函数的是候选数组长度。函数内部会跳过被 NMS 删除的框,对保留的人脸逐张运行 face_pipe,目前每帧最多处理 9 张人脸特征。
本工程的特征模型前段由 NPU 执行,末端的深度卷积、投影和反量化由 MCU 完成。配套的 MCU 末端源码也要参与应用构建,最终仍然得到 128 维 float32 特征。
两个模型接收的都是原始 uint8 RGB 像素,但量化与预处理接口要分别核对。当前 face_pipe 的输入预处理已包含在模型图中,应用按 0—255 像素填入即可。
输出特征做 L2 归一化后,再计算余弦相似度。可以理解为先统一向量长度,再比较两组特征的方向是否接近。这些数值用于模板匹配,不能直接当成识别概率。
程序会取每个号码中最高的模板匹配分数。最佳号码达到 0.60,而且比第二个不同号码至少高 0.08,才接受该号码。分差要求用于避免两个人员号码得分接近时直接给出结果。
模板通过 FileX 保存到 FAT32 SD 卡,保存的是归一化后的特征。每个号码使用 A/B 两份记录交替更新,写入、刷新和读回比较成功后,界面才显示“保存成功”。启动时加载模板到 RAM,后续匹配在 RAM 中完成。
总结
通过这次实践,我们在 STM32N6 上成功部署了两个人工智能模型,将人脸检测、关键点定位、特征提取和身份匹配串联起来,实现了一套完整的端侧人脸识别流程。
这也说明,随着 STM32N6 引入 Neural-ART NPU,MCU 已经不再局限于简单的控制逻辑和传感器数据处理,而是能够在有限的资源下运行多个神经网络模型,并配合传统嵌入式程序完成更复杂的 AI 应用。
从“运行一个 AI 模型”到“让多个 AI 模型协同完成实际任务”,STM32N6 让 MCU 端侧 AI 有了更多可能。 无论是智能门禁、人机交互,还是工业视觉检测,都可以沿着这样的思路,将 AI 推理与嵌入式控制结合起来,让设备在本地完成感知、分析和决策,而不必依赖云端计算。
32