预训练 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内容如下
frompathlibimportPathimportcv2importnumpyasnpimportonnximportonnxruntimeasortfromonnximportTensorProto,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",]defmain():""" @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()iflen(inputs)!=1orinputs[0].shape!=[1,3,416,416]:raiseValueError("本脚本只适用于输入为 [1,3,416,416] 的指定模型")ifinputs[0].type!="tensor(float)":raiseValueError("原始模型输入必须为 float32")iflen(original.get_outputs())!=1:raiseValueError("本脚本要求单输出模型")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)delmodel.graph.node[:]model.graph.node.extend(adapters+original_nodes)delmodel.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")ifimageisNone:raiseFileNotFoundError("请在当前目录放入测试图片 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]ifreference.shape!=(1,3549,85):raiseValueError("输出形状与本教程指定模型不一致")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,使用以下命令即可
curl-L"https://ultralytics.com/images/bus.jpg"-otest.jpg我们进入终端,windows的powershell即可,python推荐3.11版本
创建虚拟环境
python-mvenv .venvsource.venv/Scripts/activate接下来安装依赖
python-mpipinstall--upgradepip python-mpipinstallnumpy opencv-python onnx onnxruntime然后验证
python-c"import cv2, numpy, onnx, onnxruntime; print('依赖安装成功')"验证成功之后,运行该程序即可
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架构开发板)
打开配置文件
sudonano/etc/apt/sources.list.d/ubuntu.sources
把原来的内容替换为图片中的内容即可(只修改了URIs部分)
内容如下
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 依赖项
sudoaptupdatecurl-fsSLhttps://deb.nodesource.com/setup_20.x|sudo-Ebash-sudoaptinstall-ygcc g++makebuild-essential nodejs sox gstreamer1.0-tools gstreamer1.0-plugins-good gstreamer1.0-plugins-base gstreamer1.0-plugins-base-apps安装Edge Impulse Linux CLI
sudonpminstalledge-impulse-linux-g--unsafe-permEdge 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日志最上方
- Local URL: http://localhost:7000 - Network URL: http://10.166.51.72:7000我们在与开发板同一网络的主机电脑下,在浏览器打开http://10.166.51.72:7000链接即可(实际网络地址会有所不同,注意查看)