Showing Posts From
Onnx
- 29 Apr, 2026
如何把PT格式的模型转换为TFLite/LiteRT模型?
之前在尝试把Ultralytics的YOLOv8模型运行在Android手机上的过程(参考总结Android Flutter项目中YOLO模型的执行流程)中,直接使用了YOLO模型的export接口即可实现把该模型导出为ONNX、TFLite等格式。但是如何我们在开发过程中遇到的模型没有官方所提供的export接口,如何能够把pt格式的模型文件转换为TFLite,从而方便的部署在Android手机上呢? 因此,本文尝试解决这样的问题:如何把一个通用的 PyTorch 模型权重(.pt)文件,通过模型转换处理,变成在 Android 端侧能跑的 TFLite 模型文件(.tflite)? Google的AI Edge Torch/Litert 针对以上所提出的问题,一个比较流行的解决方案是: - PyTorch (.pt) → ONNX: ONNX 是模型交换的通用格式,所以这一步转换相当于是把模型文件脱离了 PyTorch 的框架限制。 - ONNX → TensorFlow (SavedModel): 这是一个转译过程,从ONNX的生态语言转换为TensorFlow生态,确保目标平台(即Google 手机APP)能正确的识别模型文件中的算子。 - TensorFlow → TFLite: 这有点类似于 Linux 下的 strip 和 objcopy,这一步操作会进行常量折叠、算子精简以及最重要的量化操作(Quantization),把默认的 FP32 的权重参数变成 INT8,以适应移动端 CPU/NPU 的算力和存储方面的限制。 但是,以上这个传统的 PT -> ONNX -> TF -> TFLite 路径并非是一个完美的解决方案。这是由于, PyTorch 坚持使用 NCHW (即所谓的Channel First) 而 TensorFlow 则默认使用 NHWC (即所谓的Channel Last),那么转换工具在以上传统的转换路径中为了保住数学上的等价性,会疯狂插入 Transpose 算子。这在手机 CPU/GPU 上运行过程中,会导致频繁的内存重排,严重拖慢推理速度。 因此,Google 在2024年4月推出了一个AI Edge Torch的工具,用于实现从 PT 模型文件向TFLite格式模型文件的直接转换。这个工具利用了 torch.export(PyTorch 2.x 的核心特性)直接捕获计算图,并将其映射为 TFLite 算子,更重要的是,它可以直接在模型转换过程中对齐推理过程中的数据格式,这样就消除了大量不必要的 Transpose 算子,避免后续推理过程要执行的大量内存重排。 到了2025年,Google 对移动端 AI 生态进行了一次品牌重塑:之前的TensorFlow Lite被重新命名为LiteRT。这主要是因为,Google 认为 TensorFlow Lite 这个名字太受限于 TensorFlow 框架了。现在整个AI模型生态的趋势是 PyTorch 统治科研与训练,而 TFLite 统治移动端部署。因此为了吸引 PyTorch 用户,Google 强调 TFLite 这个 Runtime 是通用的端侧AI部署工具,不只是给 TensorFlow 用的。所以现在新的品牌叫 LiteRT (Lite Runtime)。 !image.png 环境安装 要在PC端把一个Pytorch的模型转换为 TFLite/LiteRT 格式,主要是需要依赖于litert-torch和ai-edge-litert 这两个包。 - litert-torch:对应之前的 ai-edge-torch,负责将 PyTorch 算子映射到 LiteRT。 - ai-edge-litert: 对应之前的 tflite-runtime,是推理引擎,负责在 Android 或 PC 上执行生成的 .tflite (LiteRT) 文件。 截至目前(2026年3月份),litert-torch和ai-edge-litert 这两个包的接口变动仍然非常剧烈,导致其对环境的要求非常苛刻。所以一定要创建一个独立的Python环境,并且提前安装好其指定的torch版本,否则后面安装这两个包的过程中很容易出现包冲突的情况。经过仔细检查,目前比较稳定的环境安装步骤为(在Windows的WSL2环境中安装): 注意:这个模型转换和PC端推理的环境必须安装在Linux环境中,我自己在Windows中安装环境总是会以下错误。从报错信息上看,litert-torch依赖于ai-edge-tensorflow,而ai-edge-tensorflow 目前主要发布在 Google 的 Nightly/Experimental 仓库中,对 Windows 平台的原生支持非常滞后。所以这个转换工具最好还是在Linux或者Windows的WSL2环境中来安装。 模型转换 环境安装好以后,就可以开始把Pytor下的预训练pt格式的模型文件转换为TFLite格式了。我这里直接使用Ultralytics/YOLO项目中的yolov8s.pt这个文件做转换测试。 以上模型导出脚本能够成功的导出 FP32 的模型文件,FP32 虽然精度最高,但由于其对内存带宽和存储空间的占用比较大(对于YOLOv8s这个模型而言,转换后的模型文件大小为44MB),在端侧 CPU/NPU 上运行肯定不是性能最优选,因此接下来还需要进行量化(Quantization)处理。 量化 对于YOLO模型自带export接口而言,量化是一个很简单的事情,只需要设置导出时的fp16、int8等导出格式就可以了。但是使用litert_torch 完成模型导出之后,对TensorFlow Lite模型文件进行量化操作的接口发生的变化非常剧烈,导致现在在网上看到的几乎所有量化参考代码在最新的litert-torch 都无法正确、成功的完成量化操作。 通过学习https://github.com/google-ai-edge/litert的ReadME文件: > > > I have a PyTorch model... > > - Goal: Convert a model from PyTorch to run on LiteRT. > - Path1 (classic models): Use the LiteRT Torch Converter to transform your PyTorch model into the .tflite format, and use AI Edge Quantizer to optimize the model for optimal performance under resource constraints. From there, you can deploy it using the standard LiteRT runtime. > - Path2 (LLMs): Use LiteRT Generative Torch API to reauthor and convert your PyTorch LLMs into Apache format, and deploy it using LiteRT LM. > 可以看到对于目前比较新的LiteRT实现而言,量化需要用一个新的工具ai-edge-quantizer(https://github.com/google-ai-edge/ai-edge-quantizer)。这个工具是 Google 专门为了对LiteRT模型进行 INT8/INT4 权重压缩打造的新一代利器。在最新的 Edge AI 理念中,单纯的文件体积 FP16 截断已经不再流行,权重 INT8 + 激活值浮点 (即所谓的Dynamic Quantization) 才是能在手机等端侧设备上上平衡速度、体积和精度的终极王者。 以上的量化流程中选择了wi8-afp32的模式(即权重参数使用INT8量化,激活值仍然使用FP32量化),最终得到的量化后的模型文件只有11MB的样子,是量化前的FP32模型文件的1/4。 除了以上的wi8-afp32量化模式,ai-edge-quantizer还支持其他的量化模式,如下图所示,Google 的工程师用了一种非常直白的命名约定来定义这些量化模式: - w = Weight(权重参数,即模型训练出来的固定参数) - a = Activation(激活值,即模型在推理时流动的中间特征图) - i8 / i4 = 8位 / 4位整数(Integer) - fp32 = 32位浮点数(Float) !8a772873-2af8-4892-8f32-b1998f10d39e.png 推理测试 基于 LiteRT模型文件已经转换并量化完成,下一步就是在PC端的LiteRT runtime中对其进行推理测试。 如上所述,在 LiteRT生态中,ai-edge-litert包对应于之前的 tflite-runtime,也就是负责在 Android 或 PC 上基于LiteRT模型文件执行前向推理的运行时框架。因此,在PC端进行前向推理的流程大致也就是:加载模型以及推理的图片文件→数据预处理→执行推理→对推理结果执行NMS后处理→把NMS过滤的结果叠加在输入图像上。 参考资料 - AI Edge Torch: High Performance Inference of PyTorch Models on Mobile Devices - Google Developers Blog - Convert Models From Pytorch to TFLite With AI Edge Torch | by David Cochard | ailia Tech BLOG (EN) | Medium - https://github.com/google-ai-edge/litert-torch/blob/main/docs/pytorch_converter/README.md - https://github.com/google-ai-edge/ai-edge-quantizer
- 28 Apr, 2026
TFLite如何处理算子版本和兼容性的问题?
在瑞芯微NPU平台上进行模型板端适配的时候,在进行模型板端转换的时候,ONNX 导出时设置的 Opset 版本,与板端模型转换工具(RKNN-Toolkit2)能够支持的 Opset 版本的一致性非常重要,如果板端模型转换工具不支持onnx文件中的某些算子类型的话,这个转换过程就会报错。那么,TFLite是否也存在 Opset 版本的问题,也就是模型导出为 TFLite 格式的时候也应该有个 Opset Version,这个 Opset 的版本需要与 interpreter 能够支持的 Opset 版本相对应,否则也会出现以上瑞芯微平台模型转换的问题?以及在TFLite的工作流程中,究竟是处理Opset版本一致性问题的?本文尝试总结以上问题的答案。 相关的背景信息可参考: - ONNX的Opset版本: 如何理解ONNX模型导出的Opset版本? - TFLite框架的核心概念和工作流程: 如何理解TFLite/LiteRT的核心概念和框架流程? TFLite的Opset版本信息 针对TFLite框架下,是否也有与ONNX相同的OpSet版本的概念和处理这个问题,简单的答案是:TFLite同样有算子版本的概念,但是与算子版本的处理流程与ONNX有较大的差异。 .tflite 文件本质上是一个序列化后的 FlatBuffer 二进制文件。在这个文件头部,有一个专门的 OperatorCode 数组(也就是所谓的算子注册表)。当模型被导出时,TFLite 转换器会把这个模型文件中用到的所有算子登记在这个表里,并且每个算子独立记录其算子版本号,而不是像ONNX那样有一个统一的算子集版本号。 如下图所示的两者的显著差异:TFLite采用的是细粒度的基于各个算子的独立版本号,模型文件本身没有算子集版本的概念;而ONNX采用的这是全局的算子集版本号,通过模型文件来规范和管理整个算子集的版本。 !%E6%97%A0%E6%A0%87%E9%A2%98-2026-03-26-1350.png 为什么TFLite要采用这种单个算子集别的版本管理模式,而不是采用ONNX这样的全局算子集Opset版本的方式? 最主要的原因是,算子级别的版本管理方式可以更好的向前兼容性,这一点对于软硬件碎片化非常严重的Android生态系统非常重要。假设我们的模型中仅包含有最基础的 Conv2D 和 Relu 算子,使用最新版本的模型转换器导出的时候: - 如果是 ONNX的全局Opset模式:模型转换器会将这个ONNX文件标记为比较新的 Opset 18。这样的话,即使 Conv2D 这个算子在 Opset 1 到 18 里没有发生任何变化,那么一个老版本的 ONNX Runtime 在看到 Opset 18 版本的情况下,可能就会直接报错并拒绝加载。 - 而在 TFLite 这种单算子版本的管理模式中:模型转换器在转换过程中,发现该模型没用到 Conv2D 的新特性,就会在TFlite模型文件里将 CONV_2D 这个算子标记为 Version 1。这种情况下,即使用户的Android手机里包含的是很早版本的 TFLite Interpreter,也能完美加载并运行这个最新导出的模型。 算子版本的匹配 与其他嵌入式平台的模型导出、适配以及板端的加载运行相同,在Android系统上运行TFLite格式的模型,在算子的版本匹配上,需要关注三个环节:模型导出和转换工具的算子版本(Converter),Interpreter支持的算子版本,以及delegate支持的算子版本。只有以上三者的算子版本信息相互匹配,才能确保这个TFlite模型在Android系统上成功的跑起来。 - TFLite 模型转换工具、Interpreter以及Delegate等相关的概念及其在Android系统中的结构,可以参考如何理解TFLite/LiteRT的核心概念和框架流程? 这一篇笔记。 Converter TFLite 模型的 Converter 工具内部有一套算子版本试探的算法,来自动检测模型文件中各个算子的版本信息。例如对于 FullyConnected 这个算子,在没有开启 Keep_num_dims 特性的情况下,Converter 在进行模型转换的时候,就会给这个算子打上 Version 1 的标签;而如果模型的推理计算过程需要开启这个新特性,Converter 才会给该算子打上 Version 2。这就相当于是一个“能用低版本绝不用高版本”的算子版本决定策略,这个策略是保证模型和 App 拥有最广设备兼容性的核心所在。 总结起来,对于Converter而言,无论这个Converter工具有多新,它的工作逻辑都是:在进行模型TFLite格式转换的过程中,它会扫描和试探出来每个算子在模型计算图中所要求的最低版本,然后把这个最低版本作为该算子的版本,以此来尽量保证后续这个算子以及模型能够支持到范围最广的设备。 Interpreter Interpreter 也有自己的版本支持情况,那么它在加载TFLite模型文件和构建模型的计算图的过程中,同样会去扫描各个算子的版本信息,并检查该版本与自己支持版本的适配情况。 Interpreter 在加载 TFLite 模型文件的 FlatBuffer 结构的过程中,提取每个算子的版本信息,与自己内部的 OpResolver(算子注册表)进行比对。对于一个算子而言,如果在TFLite文件中该算子需要的版本是 Version 5,而 Interpreter 的代码库里算子注册表只实现了 Version 4,它会立即抛出 Unresolved builtin op 异常,并拒绝执行。 因此,一个模型究竟能否在Android系统中跑起来,Interpreter 对于算子版本的支持情况非常关键,这就是为什么在使用 Flutter 框架开发Android App 时,应该尽量使用最新版本的 tflite_flutter 插件。 > 💡 Interpreter本身就是一个普通的 C++ 动态链接库(比如 libtensorflowlite_c.so),它是被硬打包进 APK 里的,而不是由 Android 系统底层提供的。这样的好处是,Interpreter与APP里面的模型强绑定,只要APP完整测试通过,就可以确保运行在不同硬件平台和Android系统版本上。因此,如果系统中包含有多个跑不同模型的APP,也就有多个各自独立的Interpreter副本。当然,TFLite 的核心解释器被设计得极其精简,去除了所有训练相关的代码。一个基础的 C++ Interpreter 库通常只有 1MB 到 2MB,对现代 App 的体积影响微乎其微。 在 Interpreter 中实际上包含了其所支持的所有算子的CPU实现,这样的话,如果 Delegate 不支持某个算子的话,Interpreter 就会自动调用其内部实现的CPU算子。 Delegate 而在 Delegate方面,同样有自己所支持的各个算子的版本信息,在Delegate与Interpreter握手的过程中,Interpreter会拿到了Delegate所支持的各个算子的版本信息。在这种情况下,Interpreter 在模型前向推理的算子调度过程中,就能够清楚的知道哪些算子 Delegate 能够支持,哪些算子Delegate不支持必须要放在CPU中执行。 当然,不同于 Interpreter 是内置在Android APP层面中,Delegate 对于算子的支持是固定在Android系统中的。这也很好理解,因为 Delegate 之下的 NPU 驱动的实现需要依赖于SOC厂家的实现,是不可能放在APP层面来解决这个问题的。因此,除非升级整个系统,Delegate层面对算子版本的支持情况就是固定的,Delegate没法支持的算子就只能被Interpreter调度到CPU上去执行。 TFLite前向推理与算子版本相关的完整流程 至此,对于TFLite的算子版本管理,TFLite模型文件导出时的算子版本推定,在Android系统中Interpreter和Delegate进行算子版本匹配和调度的完整流程就呼之欲出了: !TFLite_Architecture_Flowchart.png
- 26 Apr, 2026
如何理解TFLite/LiteRT的核心概念和框架流程?
本文整理了TFLite的核心概念,与ONNX框架的对比,及其内部各个组件的关键工作流程,尝试建立对这个Android系统下的标准AI推理框架的整体概念和工作流程建立完整的理解。 TFLite是什么?以及与ONNX的比较 Google推出的 TensorFlow Lite 是一个专为移动、嵌入式和边缘设备设计的开源深度学习框架。它能够支持开发者在手机(Android/iOS)、微控制器(MCU)及各类物联网设备上,以极低的延迟和较小的二进制体积运行高性能的推理任务。 !image.png 众所周知,在边缘侧的板端设备上运行各种 AI 模型,已经有一个非常流行的 ONNX 框架,那为什么不在 Android 上直接使用 ONNX?原因主要还是 Facebook的 Pytorch/ONNX 与Google的 TensorFlow 之间生态竞争的问题。 虽然 ONNX Runtime 现在也能够支持在 Android上运行,但在 Android APP 中跑AI Model还是应该首选使用 TFLite,原因如下: - 兼容性的问题:在 Android 系统的实现中,Google 提供了一层 NNAPI (Neural Networks API) 的封装。TFLite 是 Google 主推的端侧机器学习框架,它对 NNAPI 的支持是第一优先级的。如果用 ONNX 的话,在调用不同 SOC 厂家的 NPU 时,可能会遇到各种驱动不兼容或性能折损的情况,兼容性的处理会非常头疼。 - 内存与包体积的问题:要在 Linux 或者 Android 系统中使用ONNX来运行模型,就需要把 ONNX Runtime 内置进去,相关的库文件通常很大(几十 MB),这样就会直接增加 App 的安装包大小和内存占用情况。而如果使用TFLite的话就没有这个问题,TFLite 的核心解释器非常小(只有几百 KB),而且 Android 系统中已经内置了相关的运行时环境。 TFLite模型部署的整体工作流程 将一个复杂的深度学习模型部署到手机上,通常需要经历从训练、精简/格式转换/量化再到板端执行的完整的过程。这个流程对于所有的设备端 AI 框架的工作流程而言都是大致相同的。 例如对于瑞芯微平台上的模型适配而言,其整体的适配流程如下: - 首先在PC/AI服务器端完成模型训练,导出为ONNX格式的模型; - 把以上导出的ONNX模型再利用瑞芯微的RKNN-Toolkit工具转换为RKNN格式的板端模型; - 在瑞芯微的处理器中,板端模型与其运行时库librknnrt.so、应用程序相互配合完成板端的推理任务。 以上的这个流程,对于在手机的的 Android 系统中使用 TFLite 框架来运行 AI 推理的流程是完全相同的,只不过整个过程稍微简化了一些而已: - 模型转换部分:首先把原始的训练好的 PyTorch 模型(通常是 .pt 或 .pth)使用 TFLite Converter 之类的工具转换为扁平缓冲区格式的 .tflite 文件。与其他板端模型转换工具的工作流程类似,在这个转换过程中,它会进行算子融合(将多个连续的操作合并为一个,以减少内存访问次数和量化(将原始模型文件中的 32 位浮点数权重转换为 16 位浮点数 FP16 或者 8 位整数 INT8,在微弱损失精度的前提下可换取数倍的速度提升同时降低对模型大小和显存的需求)等操作。 - 板端推理执行部分:在 Android App 运行时,内置在Android 系统中的 TFLite 解释器(Interpreter)会负责加载模型、管理内存缓存并调度计算算子。 > 💡 注:在模型转换部分,Pytorch 框架下训练出来的模型,一般需要先转换为 ONNX 模型,再利用类似 onnx2tensorflow 之类的工具转换为 tflite 格式。不过 Goggle 在2024年也出了一个 AI edge torch的工具可以实现直接的转换。 !image.png TFLite模型在Android框架下是如何运行的? 从以上的框架图中可以看到,模型转换为 TFLite 格式后再Android APP中的运行,需要依赖于两个关键的组件:Interpreter 和 Android Neural Networks API(NNPI)。 其中的Interpreter (解释器)是 TFLite 模型在系统中运行的核心控制中枢。它负责加载模型,解析模型的计算图(Graph),并管理内存缓冲区。在整个模型的工作流程中,Interpreter 并不直接做复杂的浮点和算子运算,而是负责将模型中定义的计算任务分发出去。 - 其实这个Interpreter有些类似于瑞芯微的librknnrt.so,负责辅助应用程序加载模型,解析模型的计算图和管理内存,然后在接收到输入数据的时候,对模型每一层的计算任务进行调度管理,分配到合适的处理器(CPU/GPU/NPU)上,并根据具体的计算调度情况不同的处理器上在处理器之间管理好内存的拷贝。所以其任务就是:加载并解析模型,分发计算任务,管理不同计算任务和处理器上的内存。 而 NNAPI 则是 Android 系统提供的一个 C API。它的存在是为了屏蔽底层 NPU、GPU、DSP 硬件的碎片化。Google 在 Android 系统中所提供的 NNAPI 只是一个与 Interpreter 相互配合的API调用接口的框架,而对于瑞芯微、高通、联发科等 SoC 厂商,要让 Android 手机能流畅地使用 TFLite 执行模型的推理任务,他们的核心工作就在其SOC的设计中进行 NNAPI 接口的具体实现。 具体来讲,这些 SOC 厂商需要根据 Android NNAPI 模块所提供的 NeuralNetworks.h 标准,编写对应的 NPU 驱动指令。这些驱动指令的关键任务就是实现模型的算子映射(将 Android 定义的 ANEURALNETWORKS_CONV_2D 等标准算子,翻译成自己的 NPU 能理解和执行的具体指令)和内存管理(实现不同类型和区域的内存共享控制,尽量避免模型在执行推理过程中数据在 CPU 和 NPU 之间来回拷贝)。 总结起来,就形成了这样的调用关系和流程: !5a379fcc-72eb-471f-9001-bf892b8a6120.png - 首先是APP的应用程序逻辑代码调用 Interpreter 加载和解析模型文件; - 在 Interpreter 对输入数据执行推理流程的过程中,会通过 Delegate 机制调用 Android系统中的 NNAPI 框架中所定义的API。NNAPI中定义了不同的标准算子,Interpreter 在推理中针对具体不同的算子调用 NNAPI 中与之对应的API接口。 - NNAPI之下则是不同 SOC 针对算子的具体实现,这部分由各个 SOC 厂家根据自己SOC中 CPU/GPU/NPU 的能力来进行实现。相当于 NNAPI 定义了一层标准的算子接口,屏蔽了不同 SOC 厂商在其硬件上实现这些算子的差异性,这样的话,对于应用程序和Interpreter而言,看到的就只是 NNAPI 所定义的标准接口。 Interpreter 调用 NNAPI框架的Delegate机制 以上所描述的 Interpreter 和 NNAPI 框架的调用流程只是一个简化的解释,实际上Interpreter并不是直接调用NNAPI所定义的API接口。如果 Interpreter 直接以硬编码的方式去调用 NNAPI的话,会产生两个问题: - 某些算子可能不支持 (Fallback):NNAPI 并不一定能够支持 TFLite 模型文件中的所有算子。如果直接调用的话,遇到不支持的算子程序就崩溃了。 - 跨平台的兼容性问题:TFLite 所谋甚大,不仅要运行在 Android 上,还能够支持在 Windows、Linux、iOS 上跑,而这些系统中根本就没有NNAPI(例如上面架构图中的iOS APP部分根本就没有 NNAPI)。 所以 Interpreter 在 Android 中对于 NNAPI 框架的调用,采用了一种 delegate 的机制。 本质上,Delegate 就是 Interpreter 为了实现跨平台兼容性而设计的一个抽象插件接口。 - 对上(与Interpreter之间),它通过TfLiteDelegate 定义了一个标准的 C API,通过这个C API屏蔽了不同硬件平台、SOC、NPU的差异性。Interpreter始终只需要调用这个C API接口来实现对下层不同算力的调用,实现完整的推理过程。 - 而对下(各种平台不同的NPU实现),它作为客户端,负责调用各个不同系统和硬件平台上特有的深度学习算子运算的加速库(如 Android 的 libneuralnetworks.so 或 iOS 的 Metal.framework)。 至此,TFLite 框架得到完整结构及其调用流程就比较清楚了: !%E6%97%A0%E6%A0%87%E9%A2%98-2026-03-26-1350.png 参考资料 - Google Play 服务 C 和 C++ API 中的 LiteRT | Google AI Edge | Google AI for Developers - TFLite基础知识 - VitoYeah - 博客
- 20 Apr, 2026
基于Insightface的人脸识别功能的完整实现流程总结
本文详细解释了计算机视觉领域中的人脸识别功能的工作流程,并通过InsightFace这个完整的人脸检测+识别的解决方案对以上流程的具体实现进行代码级别的演示和解释。 人脸检测+识别功能的完整工作流程 在人脸识别功能在实际产品的工业落地实践中,并非是通过一个独立的模型来解决完整的问题,而是一套级联的流水线:人脸检测(Detection)+对检测到的人脸图像进行几何修正(Alignment)+人脸识别(Recognition)。 而在目前的工业界,尤其是在移动端(手机)和边缘计算设备(如瑞芯微 RK3588)上,人脸识别的标准化解决方案是 InsightFace 开源生态体系。该方案能够支持在算力受限的 NPU 上实现实时且高精度的人脸识别,其通常采用的 Pipeline 是:SCRFD(轻量级人脸检测与关键点定位) + 仿射变换对齐 + MobileFaceNet-ArcFace(轻量级特征提取)。因此整个人脸识别的流程可以分为三个阶段: - Phase 1 (检测器 - SCRFD):输入整张大图,通过 SCRFD 模型检测到人脸区域以及定位其中的 5 个关键点(双眼、鼻尖、两嘴角),该阶段输出人脸区域所在的边界框(Bounding Box)和以上的 5 个面部关键点(Landmarks)。 - Phase 2 (对齐器 - 几何变换):这部分就是进行单纯的图像几何变换,不是深度学习模块(可以直接使用Opencv的warpAffine接口执行这个变换),计算检测关键点与标准模板关键点之间的变换矩阵,主要的目标是将前一阶段检测出来的人脸图像摆正。 - Phase 3 (识别器 - MobileFaceNet):输入摆正后的人脸区域小图,输出该小图对应的 512 维特征,并且与数据库中保存的人脸特征数据进行余弦距离计算,进行人脸特征比对,找到对应的人脸ID。 !Face_Recognition_Pipeline.png 从数据流图的角度上讲: - 检测阶段:输入一张高清分辨率的大图(如1920 x 1080),经过 SCRFD 模型检测,找到 个人脸,输出与之对应的个人脸的坐标框位置()以及每个脸 5 个关键点的 (x, y) 坐标()。 - 对齐阶段:该阶段会将任意大小、角度的人脸,通过矩阵乘法裁剪缩放并旋转至绝对正脸,输出的每张人脸图像的尺寸强制锁定为 112x112,因此输入数据的维度为 。 - 识别阶段;基于对齐阶段输出的人脸图像,使用 MobileFaceNet 对其提取特征向量,每张人脸输出 512 维特征向量,因此输出数据维度为 。接下来就是基于这 512 维特征向量从向量库中通过余弦距离进行比对在,找到对应的人脸ID。 人脸识别和匹配的流程总结 以上从输入图像中检测到人脸区域,对人脸区域图像对齐以及从对齐后的人脸图像提取特征向量的过程,是比较好理解的。那么提取到人脸图像的特征向量后,进一步是如何进行人脸识别的呢? 其实这个人脸识别的过程就是一个从向量数据库中进行图像搜索的过程,这一步与OpenAI的CLIP方案(实例讲解OpenAI的轻量级多模态模型CLIP)基于自然语言进行图像搜索的实现流程如出一辙。 - 首先需要有一个人脸特征向量的注册库,其中预先预先存储好了已知人员的特征向量。这些已知人员的特征向量同样采用以上的检测-对齐-特征向量生成的步骤进行,得到注册人员的512维特征向量后保存在这个注册库中。需要注意,特征向量生成需要使用相同的 MobileFaceNet 模型来生成,也就是说,注册人员的人脸登记流程和后续进行人脸识别流程在人脸图像的特征向量生成应使用同一个 MobileFaceNet 模型,否则特征向量会不匹配。 - 后续的人脸识别的流程实际上就是一个查询向量注册库的流程。从当前摄像头抓拍并经过 MobileFaceNet 提取出人脸的实时 512 维特征,从注册库中使用余弦距离比较和判断来找到人脸在注册库中的ID。 !Vector_DB_Matching_LR.png 如果注册库中的已知人员数量不多的话,可以直接以线性的方式保存所有已知人员的特征信息。这种传统的线性遍历算法复杂度为 ,如果已知人员的数量很多的话,可以采用现代的向量数据库(如 Faiss, Milvus),这些数据库通过引入近似最近邻(ANN)算法,将搜索复杂度降至 。 在进行人脸特征向量入库以及人脸特征向量通过余弦距离来进行匹配时,需要先将向量的模长统一缩放为 1(这一步也就所谓的L2归一化),使其投射到超球面上,这是余弦相似度等价于向量内积的先决条件。这样余弦相似度的计算就简化成了: 基于InsightFace方案实现人脸识别的完整流程记录 以上详细描述了人脸检测、几何校正和缩放、人脸信息入口、人脸识别等环节的执行流程,以下通过 InsightFace 给出的完整方案来实现人脸识别的功能。 首先需要安装 InsightFace 包及其项目演示所需要的各种依赖包: 人脸检测 基于 InsightFace 的人脸检测的流程代码如下所示: FaceAnalysis API首次执行会自动下载buffalo_l模型包,其中包含了多个人脸检测+识别流程的onnx模型文件,其中: - det_10g.onnx:SCRFD 人脸检测模型。 - w600k_r50.onnx:ResNet50人脸识别模型。 - w600k_mbf.onnx:MobileFaceNet识别模型。 - genderage.onnx: 基于人脸图预测性别和年龄。 - 2d106det.onnx: 稠密关键点模块,用于预测面部的 106 个精细轮廓点(常用于美颜、贴纸)。 - 1k3d68.onnx: 3D 姿态模块,用于 3D 人脸重建和深度俯仰角计算。 进行人脸检测时,只需要调用app.get就可以得到这一个图像帧中所包含的所有人脸的坐标及其关键点信息。 几何变换 上一步已经通过SCRFD模型得到了图像中所包含的人脸区域的坐标位置以及关键点信息,下一步就是把其中包含的任意角度(俯仰、偏航、旋转)的人脸强制拉平到统一的正视面部拓扑坐标系中,以确保后续的人脸特性信息提取环节面对的人脸图像都是规范化的前向正视人脸。 这一步大致的数学原理是:在一个112x112分辨率的画布上,基于 InsightFace 提供的标准正脸的 5 个基准点常量,与前一步检测到的人脸的5个关键点的坐标位置,进行两组坐标之间的矩阵变换,只允许旋转、平移和等比例缩放(不允许拉伸变形),变换的目标是使得两组关键点之间的误差最小。 具体的工程实现中,可以直接调用OpenCV的cv2.estimateAffinePartial2D来实现这个几何变换。 !Face_Affine_Transform.png 这一步对应的代码如下: 人脸识别 经过上一步的几何变换处理以后,从原图中检测出来的每一个人脸,就变换成了112x112分辨率的正向面部人脸图像。接下来就是从这个标准化处理过后的人脸图像中提取其特征向量,并查询向量数据库得到其对应的ID。 特征向量提取的任务,在InsightFace中提供了两套模型,分别针对算力需求不同的硬件: - w600k_r50 (ResNet-50 backbone):使用经典的ResNet-50深度残差网络,特征提取能力强,但计算量大,适合服务器/高性能设备。 - w600k_mbf (MobileFaceNet backbone):专为移动端设计的轻量级网络,使用深度可分离卷积(Depthwise Separable Conv),参数量极少,推理速度快,适合嵌入式/移动端设备。 无论始终哪种模型,最终都是从前一阶段的112x112的人脸图像提取出来 512 维的特征向量。 现在有了人脸对应的 512 维特征向量,下一步就是通过计算检测人脸的特征向量与人脸向量库中特征向量的余弦距离来判断两者是否一致。两张人脸的特征向量 和 (且已经过 L2 归一化,即 ),判断它们是否为同一个人的数学依据是: 如果 (通常设为 0.45 或 0.5),则可以判定为同一人。因此计算两个特征向量之间的相似度就非常简单了,直接可以使用 numpy 的点积计算公式并与阈值进行比较就可以了:
- 08 Apr, 2026
如何理解ONNX模型导出的Opset版本?
在把PC或服务器上基于Pytorch训练好的模型导出为ONNX格式的时候,需要在导出的脚本中设置opset的版本,不同的opset版本设置会对后续的NPU适配过程造成很大的影响,那么这个opset的版本就是是个什么概念?不同的opset版本究竟有哪些差异,以及这些差异对后续的NPU适配过程如何造成影响?本文尝试回答以上问题。 从总的概念上来讲,在嵌入式AI模型的部署流程中,Opset(即Operator Set,算子集) 是 ONNX(Open Neural Network Exchange)所定义的算子集合的版本标准。在将PC上训练好的 PyTorch(.pt/.pth)模型导出为 ONNX 格式时,设置正确的 opset 版本至关重要,因为它直接决定了导出的模型能否在后续的NPU板端模型转换工具成功转换,并高效运行在 NPU 上 。 !image.png 要理解不同的Opset对后续的NPU模型适配和部署流程会造成什么样的影响,首先要深入的理解究竟什么是算子和什么是算子集。 什么是算子(Operator/Op)和算子集(Operator Set)? 在 AI 模型的语境之中,算子(Operator)是模型里的最小执行单元。一个典型的深度学习模型(例如 YOLO 模型),其本质就是一个巨大的有向无环计算图(DAG)。这个图由无数个节点组成,而每个节点就是一个算子。 这就有点类似于我们在写软件程序的时候,如果要实现某项功能,通常会把这个功能封装为一个函数来进行调用。那么_算子就类似于一个可被应用程序调用的标准库函数,对这个算子(函数)的调用,就相当于是在NPU上执行一次最小粒度的标准化操作。_ 常见的算子包括: - Conv (卷积):提取特征,相当于嵌入式里的滤波算法。 - ReLU (激活函数):实际上就是一个简单的 if(x
- 01 Apr, 2026
基于ECS50数据集对PANNs语音分类模型进行的训练流程
本文使用ECS50数据集对语音检测和分类模型PANNs进行自定义训练的完整流程进行了记录。 ECS50数据集简介 在PANNs音频分类模型预研信息总结一文中,详细介绍了在语音分类应用中非常流行的模型PANNs,该模型使用Google的AudioSet数据集上进行了充分的训练,从而在不同种类的语音检测、辨识和分类应用上取得了非常好的效果。本文基于该模型(CNN14结构)的预训练文件Cnn14_mAP=0.431.pth,使用ECS-50语音数据集对预训练模型进行微调,使之适应ECS-50数据集的检测和分类需求。 ESC-50数据集包含有2000个环境录音文件,该数据集适用于环境声音分类的基准方法。数据集中的每个音频文件由5秒长的记录组成,可分为50个语义类,每个类型有40个示例,松散地分为5个主要类别: !image.png ECS-50数据集的下载地址:karolpiczak/ESC-50: ESC-50: Dataset for Environmental Sound Classification > 需要注意的是,ESC-50 数据集中的音频文件原始采样率是 44.1kHz,而 PANNs Cnn14 的预训练权重是基于 32kHz 的。 因此在读取这些音频文件送入模型训练时,必须强制重采样,从44.1Khz转换为32KHz。 ECS-50数据集的训练流程记录 客观的讲,PANNs模型对于自定义数据集的训练支持并不是很友好,所以需要自行设计和处理整个训练过程(数据集准备,模型检测头修改,训练流程)的各个阶段的处理流程。 训练数据集的准备 首先从以上ECS-50数据集的下载地址下载到这个数据集的压缩包,整个数据集有600多MB的样子。 然后基于Pytorch的Dataset类派生专门的ECS50数据集的Dataset类,在其中实现len和getitem接口。此处值得一提的是,因为PANNs模型是基于32KHz的音频文件进行训练的,所以此处在加载训练数据之前,需要把ECS50数据集的44.1KHz的音频文件转换为32KHz。 此外为了进一步规范化训练数据集中的音频数据长度,统一把训练数据集中的音频长度固定为5s,过长的音频文件直接截断,过短的因为文件则在后面补零。 CNN14检测头的修改 PANNs模型是在Google的AudioSet数据集的基础上进行设计和训练的,预训练文件Cnn14_mAP=0.431.pth对应的模型检测头支持527种音频分类。而ECS50数据集支持50种音频分类,因此要在训练之前对检测头部分进行修改,以适应ECS50数据集的要求。 整个过程分为以下步骤: - 首先基于PANNs模型源代码实例化一个Cnn14结构的模型,并加载预训练模型文件。 - 然后修改以上Cnn14模型的检测头部分,最后一层使用一个50个神经元的线性层替代之前默认的527神经元的线性层。 - 最后对新修改的检测头的线性层的权重参数进行初始化。 至此,预训练数据集和待训练的模型结构就准备好了。下一步就开始进行训练。 模型的训练 模型的训练流程的执行与普通的卷积神经网络模型的训练流程没有太大区别,需要注意的是:因为是分类模型,所以训练过程使用标准的交叉熵损失函数,优化器使用Adam。 训练过程中的log信息如下: 可以看到,经过30个epoch的训练以后,在验证集上的识别准确率达到了93.5%。训练完成后会自动把训练好的模型参数保存为cnn14_esc50_best.pth文件。 推理 最后基于以下代码对训练出来的模型进行推理测试: 参考资料 - karolpiczak/ESC-50: ESC-50: Dataset for Environmental Sound Classification
- 07 Aug, 2025
Pytorch模型的ONNX导出和部署入门
本文详细总结了基于Pytorch框架开发和训练好的模型导出为通用的onnx文件,并且通过onnxruntime进行部署和Python/C++访问的流程和步骤。 本文要解决的问题是:在Pytorch中开发和训练好的网络框架及其参数,如何导出为可以部署的onnx文件?如何在其他的环境中部署这个onnx文件? 本文以[[基于Pytorch实现手写数字识别的卷积神经网络]]一文中所训练的卷积神经网络为例,来说明这个完整的过程。 Pytorch模型导出为pt/pth文件 在Pytorch框架中,已经训练完成或者训练中的模型及其参数可以保存pt或者pth文件,这样后续就可以直接通过加载这些文件快速恢复出来模型及其训练后的参数状态,继续进行训练或者直接用于新数据的推理。 一般而言,使用pt后缀名的文件用于仅保存模型的参数,而pth后缀名文件则同时保存了模型的结构和参数。因此前者保存的文件更小,但在下次加载前需要提前构建模型的框架结构,因为pt文件里面并没有包含模型结构;而后者在使用上更加方便,内部同时包含了模型结构和参数,使用时直接加载即可,但是文件体积更大。 > 当然,在 PyTorch 中, pt和pth这两种文件后缀没有官方严格的区分和定义,它们更多是开发者社区约定俗成的用法。也就是说,你完全可以用 .pt 保存完整模型,或用 .pth 仅保存参数。 以下是基于Pytorch保存训练模型及其参数,并且重新加载模型参数到新的模型对象的代码示例: 模型的ONNX导出 相比于Pytorch的模型参数导出的pt/pth文件格式,ONNX文件格式是模型部署以及在不同模型开发框架之间共享和转换支持上更为普遍的文件格式。因此也可以把训练的模型及其参数导出为ONNX文件,用于进行模型部署以及转换到其他的框架下。 Pytorch模型导出onnx文件并通过ONNXRuntime类似进行新样本推理的前提条件,是需要安装几个依赖库: 把已经训练好的模型及其参数导出为Onnx文件的代码流程如下: 如以上代码中input_sample变量所表明的,在进行ONNX文件导出时需要明确的指定后续进行推理的数据输入格式。以上代码执行后,在当前目录下就会生成一个model.onnx文件,后续就可以基于这个文件进行Onnx部署以及在不同框架下转换和使用。 ONNX文件检查 以上所导出的ONNX文件,可以使用以下代码检测该文件的合法性: onnx.checker.check_model接口用于验证ONNX模型文件的有效性,确保它符合ONNX规范,可以被ONNX兼容的推理引擎正确加载和执行。这是在使用ONNX模型进行推理前的一个常见检查步骤,有助于提高代码的健壮性。 此外,也可以直接使用netron.app工具来对以上onnx文件进行检查,该工具可以以图形的方式输出非常详细的模型及其参数信息,可以非常直观的看到该文件中所描述的模型结构及其当前各层的训练参数信息: !image.png 通过ONNXRuntime部署模型并测试 Onnx文件中所保存的模型结构和参数是模型的静态表示,不能够直接调用PT格式导致后的eval()和predict()方法。当基于Onnx文件加载模型的结构和参数以后,需要通过ONNXRuntime引擎来执行新样本数据的推理。因此要首先安装ONNXRuntime包才能支持对ONNX文件的部署和推理。 > 如前所述,在Pytorch框架下使用ONNXRuntime导入Onnx文件并进行新样本的推理,需要通过pip安装onnxruntime和protobuf两个包。 以前文所导出的模型Onnx文件为例,从MNIST数据集中的某个样本进行OnnxRuntime的推理测试代码如下: 可以看到,除了使用torchvision.datasets和torchvision.transforms来实现对数据集的访问以外(其实完全可以采用其他方式来准备输入该模型进行推理的数据样本,只要数据格式符合ONNX input sample的要求即可),以上代码对于新样本数据的推理仅仅依赖于numpy和onnxruntime,完全不依赖于开发该模型的pytorch框架。 参考资料 - 第一章:模型部署简介 — mmdeploy 1.3.1 文档 - Netron - ONNX Runtime | onnxruntime