Qualcomm Linux · AI Hub · QNN · NPU
在 Dragonwing IQ-8275 上实现实时单目深度估计的完整原型路径:Ultralytics YOLO 深度模型 → AI Hub W8A16 量化 → QNN DLC → 生成 QNN 上下文二进制文件 → 常驻的原生 C++ QNN 运行器 → Python/OpenCV 实时摄像头界面。
本教程展示如何使用由 Qualcomm AI Hub 编译的模型,通过原生 C++ QNN 应用,在 Dragonwing IQ-8275 EVK 的 Hexagon NPU 上运行实时 USB 摄像头单目深度估计演示。 最终演示被有意拆分为两个部分:
这种拆分方式使 NPU 路径保持真实且常驻,同时又让实时演示易于修改。
本原型使用的目标设备: Dragonwing IQ-8275 EVK,运行
aarch64 上的 Ubuntu 24.04,QCS8275/QCS8300 级别平台,Hexagon V75,USB 摄像头,并连接了显示器。相同的模式同样适用于其他 Dragonwing 目标设备,但 AI Hub 目标、QAIRT 版本和生成的上下文二进制文件必须与你的硬件/运行时相匹配。你将构建什么
完成后,实时数据路径如下所示:原型的参考基准测试
以下所有测量均使用imgsz=320。
前提条件
硬件
- 支持 HTP/NPU 的 Dragonwing IQ-8275 EVK。如果你的开发板尚未设置,请按照在 Ubuntu 上设置 IQ-8275 EVK操作。
- USB 摄像头
- 连接到开发板的显示器,用于实时 OpenCV 窗口
- 用于软件包/模型下载和 AI Hub 任务提交的网络连接
开发板上的软件
安装运行时、开发头文件和 Python 包:第 1 步:将 YOLO 深度模型导出为 ONNX
下载/加载 Ultralytics YOLO 深度模型,并导出固定320×320 的 ONNX 模型。
如有必要,清理重复的 ONNX 元数据
某些导出器可能会将图输出同时放在graph.output 和 graph.value_info 中。AI Hub 可能会因重复名称错误而拒绝该模型。下面这个小型清理脚本会删除所有重复的 value_info 条目。
第 2 步:采集校准图像
模型输入为 NHWC float32 RGB,归一化到[0, 1],并采用 Ultralytics 风格的方形 letterbox 处理为 320×320。
创建 make_aihub_calib.py:
第 3 步:使用 AI Hub 进行量化
本示例使用 W8A16 量化:8 位权重和 16 位激活。创建submit_aihub_quant.py:
量化后的 ONNX 计算图内部通常包含整数张量。在本原型中,对外的公开输出仍为
FLOAT [1,1,320,320]。反量化之前的内部张量为 uint16,随后通过 DequantizeLinear 得到公开的 float 输出。在假定输出类型或布局之前,请先检查你自己的计算图。第 4 步:将量化模型编译为 QNN DLC
在 AI Hub 中,为与你的开发板 SoC/NPU 代次相匹配的目标编译量化模型,并选择 Qualcomm AI Runtime / QNN DLC 目标运行时。 将编译好的 DLC 下载到开发板:qnn-net-run 进行健全性检查:
第 5 步:生成 QNN 上下文二进制文件
下载的 DLC 可能包含拓扑、参数和权重,而不包含预构建的 HTP 上下文缓存。原生应用可以自行组建,但标准的快速路径是先生成一次 QNN 上下文二进制文件,然后直接加载它。第 6 步:构建常驻的原生 QNN 运行器
原生运行器做三件事:- 从
/usr/lib动态加载 QNN provider。 - 使用
QnnContext_createFromBinary()加载生成的上下文二进制文件。 - 复用计算图和张量,以便重复调用
QnnGraph_execute()。
qnn_app/Makefile:
qnn_dlc_runner.cpp 源码。以下是关键实现要求:
- 从
/usr/include/QNN包含 QNN 头文件。 - 使用
dlopen()加载libQnnHtp.so。 - 加载
QnnInterface_getProviders,并选择一个暴露QNN_API_VERSION_MAJOR的 provider。 - 按照 QNN 示例应用使用的相同顺序调用 QNN backend/device/context API。
- 使用
QnnContext_createFromBinary()加载生成的上下文二进制文件,而不是原始 DLC。 - 使用来自上下文的计算图/张量元数据或已知的模型约定:
- 计算图:
graph_ymndtmzg - 输入:
images,形状[1,320,320,3],float32,1,228,800字节 - 输出:
output_0,形状[1,1,320,320],float32,409,600字节
- 计算图:
- 在服务器模式下,将每个新输入缓冲区复制到已注册的输入张量中,调用
QnnGraph_execute(),并将输出张量写入磁盘。
qnn-net-run 的输出进行对比以验证正确性:
qnn-net-run 完全一致,或在正常的浮点容差范围内。在原型中,max_abs_diff 为 0.0。
第 7 步:添加实时 Python 摄像头应用
实时 Python 进程负责摄像头和显示。原生 C++ 进程负责 QNN 上下文和计算图。 Python 应用应当:- 启动
qnn_dlc_runner --server。 - 等待
READY。 - 使用 OpenCV 打开 USB 摄像头。
- 对于每一帧:
- letterbox 处理为
320×320、BGR → RGB、float32[0,1]、NHWC 批次 - 写入
input.raw - 向原生进程发送
RUN input.raw output.raw - 将
output.raw读取为 float32[1,1,320,320] - 裁剪掉 letterbox 填充,并将深度图调整回摄像头分辨率
- 给深度着色并显示
RGB | DEPTH | OVERLAY
- letterbox 处理为
0 不正确:
输出约定与数据类型检查
对于本原型:
尽管 W8A16 量化在内部使用整数张量,但编译后的 QNN 运行时输出为 float32。对于每个模型都要检查这一点。常见的模式是:
output0_q 可能是 16 位,但公开的模型/运行时输出是 float32。
故障排查
qnn-platform-validator 失败
NPU 后端尚未就绪。在调试模型之前,请先检查是否已安装正确的开发板镜像、固件、FastRPC 设备和 QNN 软件包。
qnn-context-binary-generator 成功但应用无法加载上下文
请在将要运行应用的同一开发板/运行时上重新生成上下文。上下文二进制文件在任意 QAIRT 版本和目标之间不可移植。
原生应用在打印 READY 之前退出
在服务器模式下,请确保初始 --input 路径存在且字节大小正确。即使后续帧通过 RUN 命令提供,原型运行器在启动期间也会验证该输入路径。
在启动服务器之前创建一个虚拟输入:
摄像头能打开但显示失败
请从连接到开发板图形会话的终端运行,而不是无显示器的 SSH shell。如果使用 SSH 查看日志,请将 OpenCV 显示保留在开发板的显示器上。深度颜色看起来不稳定
使用更多具有代表性的校准帧,并在实际的光照/摄像头环境中重新采集。对于实时演示,100–300 帧比 32 帧是更好的起点。为什么不每帧运行一次 qnn-net-run?
qnn-net-run 非常适合验证,但它是一个命令行测试工具。如果每帧都启动它,大部分时间会花在进程启动、上下文设置和销毁上。
对于实时应用,应保持 QNN 上下文常驻:
qnn-net-run 路径摊销后约为 48 毫秒的主要原因。

