Qualcomm Linux · AI Hub · QNN · NPU
在 Dragonwing IQ-8275 上实现实时单目深度估计的完整原型路径:Ultralytics YOLO 深度模型 → AI Hub W8A16 量化 → QNN DLC → 生成的 QNN 上下文二进制文件 → 持久化原生 C++ QNN 运行器 → Python/OpenCV 实时摄像头 UI。
本教程展示如何在 Dragonwing IQ-8275 EVK 的 Hexagon NPU 上运行实时 USB 摄像头单目深度演示,使用由 Qualcomm AI Hub 编译的模型,并通过原生 C++ QNN 应用执行。 最终的演示有意拆分为两个部分:
这种拆分使 NPU 路径保持真实且持久,同时让实时演示易于修改。
本原型使用的目标设备: 运行 Ubuntu 24.04 的 Dragonwing IQ-8275 EVK(
aarch64),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 得到公开的浮点输出。在假设输出类型或布局之前,请检查您自己的图。步骤 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 提供程序。 - 使用
QnnContext_createFromBinary()加载生成的上下文二进制文件。 - 复用图和张量,以便反复调用
QnnGraph_execute()。
qnn_app/Makefile:
qnn_dlc_runner.cpp 源码。以下是关键的实现要求:
- 从
/usr/include/QNN包含 QNN 头文件。 - 使用
dlopen()加载libQnnHtp.so。 - 加载
QnnInterface_getProviders并选择一个暴露QNN_API_VERSION_MAJOR的提供程序。 - 按照 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 毫秒的主要原因。

