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 实时摄像头 UI。

Heath Blandford·Jul 29, 2026·← 所有文章

本教程展示如何在 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 版本和生成的上下文二进制文件必须与您的硬件/运行时匹配。

您将构建的内容

完成后,实时路径如下所示:
在原型运行中,稳态原生 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 令牌。如果令牌曾被粘贴到聊天、问题跟踪器、终端录制或共享日志中,请从您的 AI Hub 账户中撤销或轮换它。
配置 AI Hub(只需一次):

步骤 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 上下文二进制文件并直接加载。
根据工具版本,生成的文件可能带有双重后缀:
请将 QNN 上下文二进制文件视为目标特定的文件。它与硬件目标和 QAIRT/QNN 版本绑定。更改开发板镜像、QAIRT 版本、目标设备或模型时,请重新生成它。

步骤 6:构建持久化的原生 QNN 运行器

原生运行器做三件事:
  1. 从 /usr/lib 动态加载 QNN 提供程序。
  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 的提供程序。
  • 按照 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 应用应当:
  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 负责摄像头采集、预处理、可视化和实时 UI。这让您在应用层拥有 Python 的灵活性,同时获得原生 QNN 模型执行的性能特性。