Skip to main content
Qualcomm Linux · AI Hub · QNN · NPU

在 Dragonwing IQ-8275 上实现实时单目深度估计的完整原型路径:Ultralytics YOLO 深度模型 → AI Hub W8A16 量化 → QNN DLC → 生成 QNN 上下文二进制文件 → 常驻的原生 C++ QNN 运行器 → Python/OpenCV 实时摄像头界面。

Heath Blandford·2026年7月29日·← 所有文章

本教程展示如何使用由 Qualcomm AI Hub 编译的模型,通过原生 C++ QNN 应用,在 Dragonwing IQ-8275 EVKHexagon NPU 上运行实时 USB 摄像头单目深度估计演示 最终演示被有意拆分为两个部分: 这种拆分方式使 NPU 路径保持真实且常驻,同时又让实时演示易于修改。
本原型使用的目标设备: Dragonwing IQ-8275 EVK,运行 aarch64 上的 Ubuntu 24.04,QCS8275/QCS8300 级别平台,Hexagon V75,USB 摄像头,并连接了显示器。相同的模式同样适用于其他 Dragonwing 目标设备,但 AI Hub 目标、QAIRT 版本和生成的上下文二进制文件必须与你的硬件/运行时相匹配。

你将构建什么

完成后,实时数据路径如下所示:
在原型运行中,稳定状态下原生 QNN 推理约为 16 毫秒/次推理,即模型执行本身约为 62 FPS。实时显示的 FPS 较低,因为其中还包括摄像头捕获、Python 预处理、Python 与原生进程之间的文件 I/O、着色以及 OpenCV 显示。

原型的参考基准测试

以下所有测量均使用 imgsz=320
基准测试数值取决于开发板镜像、QAIRT 版本、模型版本、摄像头分辨率、显示分辨率、散热状态和功耗模式。请将这些数值视为参考,而非产品规格。

前提条件

硬件

  • 支持 HTP/NPU 的 Dragonwing IQ-8275 EVK。如果你的开发板尚未设置,请按照在 Ubuntu 上设置 IQ-8275 EVK操作。
  • USB 摄像头
  • 连接到开发板的显示器,用于实时 OpenCV 窗口
  • 用于软件包/模型下载和 AI Hub 任务提交的网络连接

开发板上的软件

安装运行时、开发头文件和 Python 包:
确认 HTP 后端可用:
正常的环境会报告 Hexagon 架构且后端 DSP 测试通过,例如:
设置 Python 环境:
请妥善保管你的 AI Hub token。如果 token 被粘贴到聊天、问题跟踪器、终端录像或共享日志中,请在你的 AI Hub 账户中撤销或轮换它。
配置 AI Hub(只需一次):

第 1 步:将 YOLO 深度模型导出为 ONNX

下载/加载 Ultralytics YOLO 深度模型,并导出固定 320×320 的 ONNX 模型。
预期输出为:

如有必要,清理重复的 ONNX 元数据

某些导出器可能会将图输出同时放在 graph.outputgraph.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 上下文二进制文件,然后直接加载它。
根据工具版本的不同,生成的文件可能带有双重后缀:
请将 QNN 上下文二进制文件视为目标专用文件。它与硬件目标以及 QAIRT/QNN 版本绑定。当你更换开发板镜像、QAIRT 版本、目标设备或模型时,请重新生成它。

第 6 步:构建常驻的原生 QNN 运行器

原生运行器做三件事:
  1. /usr/lib 动态加载 QNN provider。
  2. 使用 QnnContext_createFromBinary() 加载生成的上下文二进制文件。
  3. 复用计算图和张量,以便重复调用 QnnGraph_execute()
它还提供一个简单的基于行的服务器模式,使 Python 可以在不重新加载模型的情况下流式发送帧:
创建目录:
创建 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_diff0.0

第 7 步:添加实时 Python 摄像头应用

实时 Python 进程负责摄像头和显示。原生 C++ 进程负责 QNN 上下文和计算图。 Python 应用应当:
  1. 启动 qnn_dlc_runner --server
  2. 等待 READY
  3. 使用 OpenCV 打开 USB 摄像头。
  4. 对于每一帧:
    • 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
配套文件页面复制完整的实时 Python 应用。一个最小化的循环如下所示:
从开发板的桌面会话中运行实时应用:
如果摄像头索引 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 上下文常驻:
这就是在原型中常驻原生应用测得约 16 毫秒,而 qnn-net-run 路径摊销后约为 48 毫秒的主要原因。

总结

本演示通过常驻的原生 C++ QNN 应用,在 Dragonwing IQ-8275 的 Hexagon NPU 上运行经 Qualcomm AI Hub W8A16 量化的 YOLO 深度模型,而 Python 负责摄像头捕获、预处理、可视化和实时界面。这让你在应用层拥有 Python 的灵活性,同时获得原生 QNN 模型执行的性能特性。