跳到主要内容
YOLOv8 模型部署至高通 RB5 边缘推理平台实战 | 极客日志
Python AI 算法
YOLOv8 模型部署至高通 RB5 边缘推理平台实战 本文详解将 YOLOv8 目标检测模型从 PyTorch 迁移至高通 RB5 平台的完整流程。涵盖环境搭建、ONNX 导出、QNN 转换与量化、模型库生成及 C++ 应用部署。重点解决算子兼容性、INT8 量化精度保持及 HTP 运行时配置问题,提供性能优化建议与常见故障排查方案,助力开发者在边缘端实现高效推理。
YOLOv8 模型部署至高通 RB5 边缘推理平台实战
1. 概述
1.1 背景
随着边缘端 AI 推理需求的增长,将深度学习模型部署到嵌入式平台成为许多开发者的关注焦点。本文详细介绍如何将 Ultralytics YOLOv8 训练后的目标检测模型移植到高通机器人 RB5 平台设备上运行,涵盖从 PyTorch 模型到最终部署的完整流程。
RB5 平台搭载 Qualcomm QRB5165 处理器,支持 AI 加速和 5G 连接,非常适合边缘端 AI 推理任务。
1.2 模型移植流程
整个流程可以概括为以下几个关键步骤:
PyTorch 转 ONNX :将 .pt 模型导出为通用格式 .onnx。
ONNX 转 QNN :使用 QNN SDK 工具链转换并可选量化。
生成模型库 :编译为设备可加载的 .so 文件。
生成上下文缓存 :生成 .bin 缓存以减少启动时间。
部署与推理 :推送到设备并运行 C++ 应用。
1.3 硬件加速器选择
高通机器人 RB5 平台支持多种硬件加速器,根据场景选择合适的后端:
加速器 说明 适用场景 CPU Kryo 585 八核处理器 通用计算,调试验证 GPU Adreno 650 GPU FP16/FP32 推理,图形处理 HTP/DSP Hexagon Tensor Processor INT8 量化推理,最佳性能功耗比
2. 环境准备
2.1 主机开发环境要求
开发主机需满足以下要求:
操作系统:Ubuntu 22.04 LTS(推荐)或 Ubuntu 20.04 LTS
Python 版本:Python 3.8 或 3.10
内存:建议 16GB 或以上
存储空间:至少 50GB 可用空间
2.2 安装 QNN SDK
从高通开发者网站下载高通® 神经处理 SDK (QNN SDK)。
解压 SDK 并初始化环境:
unzip qairt_sdk_v2.x.x.zip -d ~/qnn
export QNN_SDK_ROOT=~/qnn/qairt/v2.x.x
source $QNN_SDK_ROOT /bin/envsetup.sh
2.3 配置 QNN 环境变量
在 ~/.bashrc 文件中添加以下配置,确保路径正确:
export QNN_SDK_ROOT=/path/to/qnn/qairt/v2.x.x
export PATH=$QNN_SDK_ROOT /bin/x86_64-linux-clang:$PATH
export LD_LIBRARY_PATH= /lib/x86_64-linux-clang:
PYTHONPATH= /lib/python:
$QNN_SDK_ROOT
$LD_LIBRARY_PATH
export
$QNN_SDK_ROOT
$PYTHONPATH
2.4 安装 Python 依赖 python3 -m venv qnn_env
source qnn_env/bin/activate
pip install ultralytics
pip install onnx==1.17.0
pip install onnxruntime==1.22.0
pip install numpy opencv-python
3. YOLOv8 模型导出为 ONNX
3.1 导出命令 使用 Ultralytics 提供的导出功能将 PyTorch 模型转换为 ONNX 格式。注意,QNN 不支持动态输入,必须设为 False。
from ultralytics import YOLO
model = YOLO("path/to/your/best.pt" )
model.export(
format ="onnx" ,
imgsz=[640 , 640 ],
opset=12 ,
simplify=True ,
dynamic=False
)
3.2 关键导出参数说明
format : 导出格式,设为 'onnx'。
imgsz : 输入图像尺寸,如 [640, 640],需与训练时保持一致。
opset : ONNX opset 版本,推荐 11 或 12 以获得最佳 QNN 兼容性。
simplify : 是否简化模型图,建议设为 True。
dynamic : 动态输入大小,QNN 不支持,必须设为 False。
3.3 验证 ONNX 模型 import onnx
import onnxruntime as ort
import numpy as np
model = onnx.load("best.onnx" )
onnx.checker.check_model(model)
print ("ONNX 模型验证通过" )
print (f"输入:{[i.name for i in model.graph.input ]} " )
print (f"输出:{[o.name for o in model.graph.output]} " )
session = ort.InferenceSession("best.onnx" )
input_name = session.get_inputs()[0 ].name
test_input = np.random.rand(1 , 3 , 640 , 640 ).astype(np.float32)
outputs = session.run(None , {input_name: test_input})
print (f"输出形状:{outputs[0 ].shape} " )
4. ONNX 模型转换为 QNN 模型
4.1 使用 qnn-onnx-converter 转换 QNN SDK 提供了 qnn-onnx-converter 工具将 ONNX 模型转换为 QNN C++ 模型格式。
qnn-onnx-converter \
--input_network best.onnx \
--output_path best.cpp \
--input_dim "images" 1,3,640,640
qnn-onnx-converter \
--input_network best.onnx \
--output_path best.cpp \
--input_dim "images" 1,3,640,640 \
--out_name output0
4.2 转换参数详解
--input_network: 输入 ONNX 模型文件路径。
--output_path: 输出 QNN C++ 模型文件路径。
--input_dim: 输入层名称和维度,格式:"name" N,C,H,W。
--out_name: 指定输出节点名称(可选)。
--input_list: 校准数据列表文件,提供后将在转换时同时完成量化。
4.3 验证转换结果 转换完成后会生成 .cpp 和 .bin 文件,可通过编译生成模型库来验证:
qnn-model-lib-generator \
-c best.cpp \
-b best.bin \
-o model_libs/
5. 模型量化
5.1 量化概述 要在 Hexagon Tensor Processor (HTP) 上运行模型,必须将模型量化为 INT8 或 INT16 格式。在 QNN SDK 中,量化可以在模型转换阶段通过 qnn-onnx-converter 的 --input_list 参数一步完成,也可以单独进行。量化过程包括两个步骤:权重和偏置量化(静态)以及激活层量化(需要校准数据)。
5.2 准备校准数据 量化需要一组代表性的输入数据作为校准集。创建 Python 脚本生成 .raw 格式的校准数据:
import numpy as np
import cv2
import os
def preprocess_image (image_path, input_size=(640 , 640 ) ):
"""预处理图像为 QNN 所需格式"""
img = cv2.imread(image_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, input_size)
img = img.astype(np.float32) / 255.0
return img
calib_images = ["image1.jpg" , "image2.jpg" , ...]
for i, img_path in enumerate (calib_images):
data = preprocess_image(img_path)
raw_path = f"calib_data/input_{i} .raw"
data.tofile(raw_path)
with open ("input_list.txt" , "w" ) as f:
for i in range (len (calib_images)):
f.write(f"calib_data/input_{i} .raw\n" )
5.3 执行量化 在 QNN SDK 中,量化集成在 qnn-onnx-converter 转换步骤中。通过提供 --input_list 参数指定校准数据,即可在转换时完成 INT8 量化:
qnn-onnx-converter \
--input_network best.onnx \
--output_path best_quantized.cpp \
--input_dim "images" 1,3,640,640 \
--input_list input_list.txt \
--act_bw8 \
--weight_bw8
5.4 量化参数说明
--input_list: 校准数据列表文件路径,提供后启用量化。
--act_bw: 激活值位宽,默认 8,可选 16。
--weight_bw: 权重位宽,默认 8,可选 16。
--float_bw: 浮点位宽,可选 16 或 32。
6. 模型库生成与上下文二进制缓存
6.1 概述 QNN SDK 采用两步流程将转换后的模型编译为设备可执行格式:先通过 qnn-model-lib-generator 生成目标平台的模型库(.so),再通过 qnn-context-binary-generator 生成上下文二进制缓存(.bin),这可以显著减少运行时的初始化时间。
6.2 生成模型库 将量化后的 QNN 模型编译为目标平台(aarch64)的模型库:
qnn-model-lib-generator \
-c best_quantized.cpp \
-b best_quantized.bin \
-o model_libs/ \
-t aarch64-ubuntu-gcc7.5
6.3 生成上下文二进制缓存 针对 RB5 平台(QRB5165 SoC)生成 HTP 上下文二进制缓存:
qnn-context-binary-generator \
--model model_libs/aarch64-ubuntu-gcc7.5/libmodel.so \
--backend libQnnHtp.so \
--output_dir context_binary/
qnn-context-binary-generator \
--model model_libs/aarch64-ubuntu-gcc7.5/libmodel.so \
--backend libQnnHtp.so \
--output_dir context_binary/ \
--config_file htp_config.json
6.4 验证生成结果
7. 模型部署到 RB5 设备
7.1 连接设备
7.2 部署 QNN 运行时库 adb push $QNN_SDK_ROOT /lib/aarch64-ubuntu-gcc7.5/* /data/qnn/
adb push $QNN_SDK_ROOT /lib/hexagon-v68/unsigned/* /data/qnn/
在设备上配置环境变量(添加到 ~/.bashrc):
export PATH=$PATH :/data/qnn/
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH :/data/qnn/
export ADSP_LIBRARY_PATH="/data/qnn:/system/lib/rfsa/adsp:/system/vendor/lib/rfsa/adsp:/dsp"
7.3 部署模型文件 adb shell mkdir -p /data/models/yolov8
adb push context_binary/model.bin /data/models/yolov8/
adb push test_images/ /data/models/yolov8/test_images/
7.4 使用 qnn-net-run 测试 echo "/data/models/yolov8/test_images/test.raw" > /data/models/yolov8/test_list.txt
qnn-net-run \
--backend libQnnHtp.so \
--retrieve_context /data/models/yolov8/model.bin \
--input_list /data/models/yolov8/test_list.txt
8. 推理应用开发
8.1 C++ 应用开发 以下是使用 QNN C API 进行推理的示例代码框架:
#include "QNN/QNNInterface.h"
#include "QNN/QNNCommon.h"
#include "QNN/QNNTypes.h"
#include "QNN/QNNContext.h"
#include "QNN/QNNGraph.h"
#include "QNN/QNNTensor.h"
#include "QNN/HTP/QNNHtpDevice.h"
#include <dlfcn.h>
#include <vector>
#include <string>
#include <cstring>
typedef QNN_ErrorHandle_t (*QNNInterfaceGetProvidersFn_t) (const QNNInterface_t*** providerList, uint32_t * numProviders) ;
struct QNNRuntime {
void * backendHandle = nullptr ;
QNNInterface_t qnnInterface;
QNN_BackendHandle_t backendH = nullptr ;
QNN_ContextHandle_t contextH = nullptr ;
QNN_GraphHandle_t graphH = nullptr ;
};
bool initQNN (QNNRuntime& rt, const std::string& contextBinPath) {
rt.backendHandle = dlopen ("libQnnHtp.so" , RTLD_NOW | RTLD_LOCAL);
if (!rt.backendHandle) return false ;
auto getProviders = (QNNInterfaceGetProvidersFn_t)dlsym (rt.backendHandle, "QNNInterface_getProviders" );
const QNNInterface_t** providerList = nullptr ;
uint32_t numProviders = 0 ;
getProviders (&providerList, &numProviders);
rt.qnnInterface = *providerList[0 ];
rt.qnnInterface.QNN_INTERFACE_VER_NAME.backendCreate (nullptr , nullptr , &rt.backendH);
...
return true ;
}
void runInference (QNNRuntime& rt, float * inputData, size_t inputSize) {
QNN_Tensor_t inputTensor = {};
QNN_Tensor_t outputTensor = {};
...
rt.qnnInterface.QNN_INTERFACE_VER_NAME.graphExecute (
rt.graphH, &inputTensor, 1 , &outputTensor, 1 , nullptr , nullptr );
}
8.2 编译命令 g++ -std=c++17 -o yolov8_inference main.cpp \
-I/data/qnn/include/ \
-L/data/qnn/ -lQnnHtp-dl \
`pkg-config --cflags --libs opencv`
8.3 后处理逻辑 YOLOv8 输出格式为 [1, 84, 8400](以 80 类 COCO 数据集为例),需要进行后处理:
struct Detection {
float x, y, w, h;
float confidence;
int class_id;
};
std::vector<Detection> postprocess (float * output, float conf_thresh, float nms_thresh) {
std::vector<Detection> detections;
for (int i = 0 ; i < 8400 ; i++) {
float x = output[0 * 8400 + i];
float y = output[1 * 8400 + i];
float w = output[2 * 8400 + i];
float h = output[3 * 8400 + i];
float max_conf = 0 ;
int max_class = 0 ;
for (int c = 0 ; c < 80 ; c++) {
float conf = output[(4 + c) * 8400 + i];
if (conf > max_conf) {
max_conf = conf;
max_class = c;
}
}
if (max_conf > conf_thresh) {
detections.push_back ({x, y, w, h, max_conf, max_class});
}
}
return applyNMS (detections, nms_thresh);
}
9. 常见问题与解决方案
9.1 ONNX 转换失败 问题 :qnn-onnx-converter 报告不支持的算子。
解决方案 :
使用较低的 opset 版本(如 opset=11)重新导出 ONNX。
使用 onnx-simplifier 简化模型图。
对于 5D 张量操作(如某些 Reshape),可能需要修改模型结构。
考虑使用 QNN 自定义算子包(Custom Op Package)实现不支持的算子。
9.2 量化后精度下降 问题 :INT8 量化后检测精度明显下降。
解决方案 :
增加校准数据集大小(建议 50-100 张代表性图像)。
使用 FP16 激活值 + INT8 权重的混合量化。
尝试 INT16 量化以获得更好精度。
在 qnn-onnx-converter 中使用 --act_bw 16 提高激活值精度。
9.3 HTP 运行时错误 问题 :在 DSP/HTP 上运行时报错。
解决方案 :
确保模型已正确量化(非量化模型无法在 HTP 上运行)。
检查 ADSP_LIBRARY_PATH 环境变量配置。
确认 DSP 签名库已正确部署。
使用 qnn-context-binary-generator 为目标 SoC 生成上下文二进制缓存。
10. 性能优化建议
10.1 模型优化
使用 YOLOv8n(nano)或 YOLOv8s(small)变体以获得更快推理速度。
降低输入分辨率(如 320x320 或 416x416)在可接受精度损失范围内。
减少检测类别数量以降低输出处理开销。
10.2 运行时优化
优先使用 HTP 加速器以获得最佳性能功耗比。
使用离线上下文二进制缓存(qnn-context-binary-generator)减少初始化时间。
使用 QNN 共享内存缓冲区减少数据拷贝。
实现多线程流水线:图像采集、预处理、推理、后处理并行。
10.3 预期性能参考 模型 输入尺寸 量化 预期 FPS (HTP) YOLOv8n 640x640 INT8 ~15-25 YOLOv8n 320x320 INT8 ~40-60 YOLOv8s 640x640 INT8 ~8-12
注意 :实际性能受多种因素影响,包括模型复杂度、输入尺寸、量化精度和系统负载等。建议在目标平台上进行实际测试以获得准确的性能数据。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online