预训练 ONNX 模型转换为 .eim
在开发视觉识别应用时,我们可以使用已经训练好的模型,完成图像分类、目标检测等任务。本篇文章将介绍如何通过 Edge Impulse,把兼容的预训练 ONNX 模型构建为 .eim 部署程序
1. 概念讲解
什么是预训练模型?
预训练模型是已经使用数据完成训练的模型,保存了学习到的参数。例如,一个经过训练的目标检测模型,可以识别人、杯子、手机等物体,并给出它们在图片中的位置
使用预训练模型,可以省去自己收集、标注数据和训练的过程。但模型能够识别哪些类别,取决于它原来的训练内容,不能只修改类别名称就让它获得新的识别能力
什么是 ONNX?
ONNX 的全称是 Open Neural Network Exchange,是一种开放的机器学习模型表示标准,常见文件扩展名为 .onnx
ONNX 文件保存模型的计算结构、参数以及输入输出信息,方便模型在不同工具之间交换和部署。它本身不是可执行程序,需要由兼容的推理引擎加载和执行
例如,ONNX Runtime 就是一种能够执行 ONNX 模型的推理引擎。程序把处理好的图片数据交给它,它执行模型计算,再返回输出结果
Edge Impulse 和 BYOM
Edge Impulse 是面向边缘设备的机器学习开发平台,提供数据采集、模型训练、优化和部署等功能
本教程主要使用其中的 BYOM(Bring Your Own Model,导入已有模型) 功能:上传现成模型,配置与模型匹配的输入输出,再为目标设备构建部署文件,无需重新训练。BYOM 支持导入 ONNX、TensorFlow SavedModel 和 TFLite 等格式
什么是 .eim?
.eim 是 Edge Impulse 的一种可执行模型部署产物。在本教程涉及的 Linux 环境中,它包含模型推理、相关数据处理代码以及供其他程序调用的通信接口。Python、C++ 等应用可以通过相应 SDK 与它交互,获取推理结果
与 ONNX 文件相比,.eim 更接近"已经为目标平台构建好的模型运行程序"。因此,构建时需要选择正确的系统、处理器架构和计算后端
2. 准备模型与转换环境
本教程选择已经训练好的 YOLOX-Nano 目标检测模型。它可以识别 COCO 数据集中的 80 类常见物体,我们直接使用上游提供的 ONNX 文件,无需重新训练,YOLOX部署链接

我们选择YOLOX-Nano,下载后得到yolox_nano.onnx文件
适配模型输入
模型输入不仅包含图片宽高,还包括颜色顺序、数据排列方式和像素数值范围
为了方便使用 Edge Impulse 的标准图像输入,本教程给模型增加一层输入适配
| 项目 | 原始模型入喉 | 适配后的入口 |
|---|---|---|
| 输入形状 | 1, 3, 416, 416 | 1, 416, 416, 3 |
| 数据布局 | NCHW | NHWC |
| 颜色顺序 | BGR | RGB |
| 输入类型 | float32 | float32 |
| 像素范围 | 0~255 | 0~255 |
适配层在模型内部完成颜色通道交换和数据维度调整,原有预训练权重保持不变
这些设置仅针对本文指定的模型文件,不能直接套用到其他 ONNX 模型
生成适合导入的 ONNX 模型
接下来我们创建一个新工程

把前面下载的yolox_nano.onnx文件放入该文件夹中,并创建prepare_yolox.py文件
prepare_yolox.py内容如下
python
from pathlib import Path
import cv2
import numpy as np
import onnx
import onnxruntime as ort
from onnx import TensorProto, helper, numpy_helper
SOURCE = Path("yolox_nano.onnx")
TARGET = Path("yolox_nano_rgb.onnx")
LABELS = [
"person", "bicycle", "car", "motorcycle", "airplane",
"bus", "train", "truck", "boat", "traffic light",
"fire hydrant", "stop sign", "parking meter", "bench",
"bird", "cat", "dog", "horse", "sheep", "cow",
"elephant", "bear", "zebra", "giraffe", "backpack",
"umbrella", "handbag", "tie", "suitcase", "frisbee",
"skis", "snowboard", "sports ball", "kite",
"baseball bat", "baseball glove", "skateboard",
"surfboard", "tennis racket", "bottle", "wine glass",
"cup", "fork", "knife", "spoon", "bowl", "banana",
"apple", "sandwich", "orange", "broccoli", "carrot",
"hot dog", "pizza", "donut", "cake", "chair", "couch",
"potted plant", "bed", "dining table", "toilet", "tv",
"laptop", "mouse", "remote", "keyboard", "cell phone",
"microwave", "oven", "toaster", "sink", "refrigerator",
"book", "clock", "vase", "scissors", "teddy bear",
"hair drier", "toothbrush",
]
def main():
"""
@description : 为指定 YOLOX-Nano 模型添加 RGB 输入适配,并核对输出一致性
@return : 无返回值
"""
model = onnx.load(str(SOURCE))
onnx.checker.check_model(model)
original = ort.InferenceSession(
str(SOURCE),
providers=["CPUExecutionProvider"],
)
inputs = original.get_inputs()
if len(inputs) != 1 or inputs[0].shape != [1, 3, 416, 416]:
raise ValueError(
"本脚本只适用于输入为 [1,3,416,416] 的指定模型"
)
if inputs[0].type != "tensor(float)":
raise ValueError("原始模型输入必须为 float32")
if len(original.get_outputs()) != 1:
raise ValueError("本脚本要求单输出模型")
old_name = inputs[0].name
new_input = helper.make_tensor_value_info(
"ei_rgb_images",
TensorProto.FLOAT,
[1, 416, 416, 3],
)
reverse_channels = numpy_helper.from_array(
np.array([2, 1, 0], dtype=np.int64),
name="ei_reverse_channels",
)
adapters = [
helper.make_node(
"Transpose",
["ei_rgb_images"],
["ei_rgb_nchw"],
name="ei_to_nchw",
perm=[0, 3, 1, 2],
),
helper.make_node(
"Gather",
["ei_rgb_nchw", "ei_reverse_channels"],
[old_name],
name="ei_to_bgr",
axis=1,
),
]
original_nodes = list(model.graph.node)
del model.graph.node[:]
model.graph.node.extend(adapters + original_nodes)
del model.graph.input[:]
model.graph.input.extend([new_input])
model.graph.initializer.extend([reverse_channels])
onnx.checker.check_model(model)
onnx.save(model, str(TARGET))
image = cv2.imread("test.jpg")
if image is None:
raise FileNotFoundError(
"请在当前目录放入测试图片 test.jpg"
)
height, width = image.shape[:2]
ratio = min(416 / height, 416 / width)
resized = cv2.resize(
image,
(int(width * ratio), int(height * ratio)),
interpolation=cv2.INTER_LINEAR,
)
square = np.full(
(416, 416, 3), 114, dtype=np.uint8
)
square[:resized.shape[0], :resized.shape[1]] = resized
cv2.imwrite("test_416.png", square)
rgb = cv2.cvtColor(
square, cv2.COLOR_BGR2RGB
).astype(np.float32)[None]
bgr_nchw = np.ascontiguousarray(
rgb[..., ::-1].transpose(0, 3, 1, 2)
)
reference = original.run(
None, {old_name: bgr_nchw}
)[0]
adapted = ort.InferenceSession(
str(TARGET),
providers=["CPUExecutionProvider"],
)
result = adapted.run(
None, {"ei_rgb_images": rgb}
)[0]
if reference.shape != (1, 3549, 85):
raise ValueError(
"输出形状与本教程指定模型不一致"
)
np.testing.assert_allclose(
result,
reference,
rtol=1e-4,
atol=1e-4,
)
Path("labels.txt").write_text(
"\n".join(LABELS) + "\n",
encoding="utf-8",
)
print("模型适配与本次测试图片的输出一致性检查通过")
print("新输入:", adapted.get_inputs()[0].shape)
print("输出:", result.shape)
if __name__ == "__main__":
main()
接下来,我们还需要准备一张包含人、杯子、手机等常见物体的图片,命名为test.jpg
当然我们也可以使用Ultralytics 官方 YOLO 目标检测示例图片 bus.jpg,使用以下命令即可
bash
curl -L "https://ultralytics.com/images/bus.jpg" -o test.jpg
我们进入终端,windows的powershell即可,python推荐3.11版本
创建虚拟环境
bash
python -m venv .venv
source .venv/Scripts/activate
接下来安装依赖
bash
python -m pip install --upgrade pip
python -m pip install numpy opencv-python onnx onnxruntime
然后验证
bash
python -c "import cv2, numpy, onnx, onnxruntime; print('依赖安装成功')"
验证成功之后,运行该程序即可
bash
python prepare_yolox.py
最后我们看到整个文件结构如下图所示

yolox_nano_rgb.onnx文件是导入 Edge Impulse 的适配后模型
labels.txt是按模型类别编号排列的 80 个标签
test_416.png是经过等比缩放、补边的测试图片
在 Edge Impulse 中导入模型
打开Edge Impulse,登录账号并创建项目

创建完成项目后,点击下面的Upload your model,上传我们刚刚转换好的模型即可

这里我们选择自己使用的平台,也可以不选择,我选择使用arduino 的ventuno q

我们第一次操作,代表性校准数据可以暂时不上传
BYOM的具体操作我们可以查看官方文档


这里部署目标我选择ventuno q,选择自己使用的平台即可
build完成之后可以参考对应平台的部署文档
构建完成后模型会自动下载,本教程下载为yolox-nano-onnx-to-eim-linux-aarch64-qnn-v1-impulse-#1.eim的文件

配置开发板 (可选)
接下来我们要对ventuno q进行配置
首先我们更换镜像源(针对arm架构开发板)
打开配置文件
bash
sudo nano /etc/apt/sources.list.d/ubuntu.sources

把原来的内容替换为图片中的内容即可(只修改了URIs部分)
内容如下
bash
Types: deb
URIs: https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/
Suites: noble noble-updates noble-backports
Components: main universe restricted multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
## Ubuntu security updates. Aside from URIs and Suites,
## this should mirror your choices in the previous section.
Types: deb
URIs: https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/
Suites: noble-security
Components: main universe restricted multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
接下来安装 Edge Impulse 依赖项
bash
sudo apt update
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y gcc g++ make build-essential nodejs sox gstreamer1.0-tools gstreamer1.0-plugins-good gstreamer1.0-plugins-base gstreamer1.0-plugins-base-apps
安装Edge Impulse Linux CLI
bash
sudo npm install edge-impulse-linux -g --unsafe-perm
Edge Impulse Linux CLI 是 Edge Impulse 提供的一套 Linux 命令行工具,主要用于将 Linux 设备连接到 Edge Impulse 平台,实现数据采集、模型下载、EIM 部署及模型推理测试
APP lab中使用转换的模型

点击左下角登录账号,要求与 Edge Impulse 账号时同一个邮箱,这样可以直接在app lab中使用 Edge Impulse平台的项目,无需额外下载移植

我们直接copy Detect Objects on Camera工程

我们打开Video Object Detection这个brick,然后点击AI models页面,可以看到yolox-nano-onnx-to-eim项目,这个就是我们在Edge Impulse 中转换的模型,我们选中这个模型直接运行该app即可(注意该app需要连接摄像头)

我们看到python日志最上方
bash
- Local URL: http://localhost:7000
- Network URL: http://10.166.51.72:7000
我们在与开发板同一网络的主机电脑下,在浏览器打开http://10.166.51.72:7000链接即可(实际网络地址会有所不同,注意查看)
