> ## Documentation Index
> Fetch the complete documentation index at: https://dragonwingdocs.qualcomm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 使用 AI Hub 和原生 QNN 在 NPU 上实现实时 YOLO 深度估计

> 使用 Qualcomm AI Hub 对 YOLO 深度模型进行量化，编译为 QNN DLC/上下文二进制文件，通过常驻的原生 C++ QNN 应用运行，并使用 Python/OpenCV 在 Dragonwing IQ-8275 上实现 USB 摄像头的实时深度显示。

<div style={{ marginBottom: "2rem" }}>
  <div
    style={{
fontSize: "0.72rem",
fontWeight: 700,
color: "#31017D",
letterSpacing: "1.5px",
textTransform: "uppercase",
marginBottom: "0.5rem"
}}
  >
    Qualcomm Linux · AI Hub · QNN · NPU
  </div>

  <p style={{ fontSize: "0.95rem", color: "#555", lineHeight: 1.7, margin: "0 0 0.75rem" }}>
    在 Dragonwing IQ-8275 上实现实时单目深度估计的完整原型路径：Ultralytics YOLO 深度模型 → AI Hub W8A16 量化 → QNN DLC → 生成 QNN 上下文二进制文件 → 常驻的原生 C++ QNN 运行器 → Python/OpenCV 实时摄像头界面。
  </p>

  <div style={{ fontSize: "0.85rem", color: "#888", display: "flex", gap: "0.5rem", flexWrap: "wrap", alignItems: "center" }}>
    <span>Heath Blandford</span>
    <span>·</span>
    <span>2026年7月29日</span>
    <span>·</span>
    <a href="/zh/tutorial" style={{ color: "#31017D", fontWeight: 600, textDecoration: "none" }}>← 所有文章</a>
  </div>
</div>

<hr style={{ border: "none", borderTop: "1px solid #eee", margin: "0 0 2rem" }} />

本教程展示如何使用由 **Qualcomm AI Hub** 编译的模型，通过**原生 C++ QNN 应用**，在 Dragonwing **IQ-8275 EVK** 的 **Hexagon NPU** 上运行**实时 USB 摄像头单目深度估计演示**。

最终演示被有意拆分为两个部分：

| 部分         |            语言 | 职责                                                         |
| ---------- | ------------: | ---------------------------------------------------------- |
| 原生 QNN 运行器 |           C++ | 仅加载一次 QNN 上下文，在 NPU 上重复执行 `QnnGraph_execute()`             |
| 实时应用       | Python/OpenCV | 捕获摄像头帧、对输入进行 letterbox/归一化处理、将帧发送给运行器、给深度着色、显示 RGB/深度/叠加画面 |

这种拆分方式使 NPU 路径保持真实且常驻，同时又让实时演示易于修改。

<Note>
  **本原型使用的目标设备：** Dragonwing IQ-8275 EVK，运行 `aarch64` 上的 Ubuntu 24.04，QCS8275/QCS8300 级别平台，Hexagon V75，USB 摄像头，并连接了显示器。相同的模式同样适用于其他 Dragonwing 目标设备，但 AI Hub 目标、QAIRT 版本和生成的上下文二进制文件必须与你的硬件/运行时相匹配。
</Note>

## 你将构建什么

完成后，实时数据路径如下所示：

```text theme={null}
USB camera
  ↓
Python live app
  ↓
320×320 NHWC float32 RGB input.raw
  ↓
persistent native C++ QNN server
  ↓
AI Hub quantized + compiled QNN context
  ↓
Hexagon NPU / HTP backend
  ↓
1×1×320×320 float32 depth output.raw
  ↓
Python unletterbox + colorize + overlay
  ↓
live RGB | DEPTH | OVERLAY window
```

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

## 原型的参考基准测试

以下所有测量均使用 `imgsz=320`。

| 路径                    |   大约延迟 |  大约 FPS | 备注             |
| --------------------- | -----: | ------: | -------------- |
| PyTorch CPU           | 629 ms | 1.6 FPS | 基线             |
| NCNN CPU              |  52 ms |  19 FPS | 固定形状的 NCNN 导出  |
| 本地 QNN / ONNX Runtime |  29 ms |  35 FPS | 常驻 ORT QNN 会话  |
| AI Hub QNN 原生 C++ 应用  |  16 ms |  62 FPS | 常驻 QNN 上下文和计算图 |

<Warning>
  基准测试数值取决于开发板镜像、QAIRT 版本、模型版本、摄像头分辨率、显示分辨率、散热状态和功耗模式。请将这些数值视为参考，而非产品规格。
</Warning>

## 前提条件

### 硬件

* 支持 HTP/NPU 的 Dragonwing IQ-8275 EVK。如果你的开发板尚未设置，请按照[在 Ubuntu 上设置 IQ-8275 EVK](/zh/Ubuntu/devices/iq8275-evk/set-up-the-device)操作。
* USB 摄像头
* 连接到开发板的显示器，用于实时 OpenCV 窗口
* 用于软件包/模型下载和 AI Hub 任务提交的网络连接

### 开发板上的软件

安装运行时、开发头文件和 Python 包：

```bash theme={null}
sudo apt update
sudo apt install -y python3.12-venv build-essential cmake pkg-config \
  qairt-headers libqnn-dev
```

确认 HTP 后端可用：

```bash theme={null}
qnn-platform-validator \
  --backend dsp \
  --coreVersion \
  --libVersion \
  --testBackend \
  --targetPath /tmp/qnnval
```

正常的环境会报告 Hexagon 架构且后端 DSP 测试通过，例如：

```text theme={null}
Core Version of the backend DSP: Hexagon Architecture V75
Unit Test on the backend DSP: Passed.
QNN is supported for backend DSP on the device.
```

设置 Python 环境：

```bash theme={null}
mkdir -p ~/yolo-depth-run
cd ~/yolo-depth-run

python3.12 -m venv ~/yolo-depth-venv
source ~/yolo-depth-venv/bin/activate

pip install --upgrade pip
pip install ultralytics opencv-python-headless onnx onnxslim onnxruntime-qnn qai-hub numpy
```

<Warning>
  请妥善保管你的 AI Hub token。如果 token 被粘贴到聊天、问题跟踪器、终端录像或共享日志中，请在你的 AI Hub 账户中撤销或轮换它。
</Warning>

配置 AI Hub（只需一次）：

```bash theme={null}
source ~/yolo-depth-venv/bin/activate
qai-hub configure --api_token "$QAI_HUB_API_TOKEN"
```

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

下载/加载 Ultralytics YOLO 深度模型，并导出固定 `320×320` 的 ONNX 模型。

```bash theme={null}
cd ~/yolo-depth-run
source ~/yolo-depth-venv/bin/activate

python - <<'PY'
from ultralytics import YOLO
model = YOLO("yolo26n-depth.pt")
model.export(format="onnx", imgsz=320, device="cpu", simplify=True)
PY
```

预期输出为：

```text theme={null}
~/yolo-depth-run/yolo26n-depth.onnx
```

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

某些导出器可能会将图输出同时放在 `graph.output` 和 `graph.value_info` 中。AI Hub 可能会因重复名称错误而拒绝该模型。下面这个小型清理脚本会删除所有重复的 `value_info` 条目。

```bash theme={null}
cd ~/yolo-depth-run
source ~/yolo-depth-venv/bin/activate

python - <<'PY'
import onnx
src = "yolo26n-depth.onnx"
dst = "yolo26n-depth-aihub-clean.onnx"
model = onnx.load(src)
outputs = {o.name for o in model.graph.output}
kept = [v for v in model.graph.value_info if v.name not in outputs]
removed = [v.name for v in model.graph.value_info if v.name in outputs]
del model.graph.value_info[:]
model.graph.value_info.extend(kept)
onnx.checker.check_model(model)
onnx.save(model, dst)
print("removed duplicate value_info:", removed)
print("wrote", dst)
PY
```

## 第 2 步：采集校准图像

模型输入为 NHWC float32 RGB，归一化到 `[0, 1]`，并采用 Ultralytics 风格的方形 letterbox 处理为 `320×320`。

创建 `make_aihub_calib.py`：

```python theme={null}
import argparse, time
import cv2
import numpy as np


def preprocess(frame, size=320):
    h, w = frame.shape[:2]
    scale = min(size / h, size / w)
    nh, nw = int(round(h * scale)), int(round(w * scale))
    resized = cv2.resize(frame, (nw, nh), interpolation=cv2.INTER_LINEAR)
    canvas = np.full((size, size, 3), 114, dtype=np.uint8)
    top = (size - nh) // 2
    left = (size - nw) // 2
    canvas[top:top+nh, left:left+nw] = resized
    rgb = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB)
    return (rgb.astype(np.float32) / 255.0)[None, ...]


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--camera", default="0")
    ap.add_argument("--frames", type=int, default=64)
    ap.add_argument("--size", type=int, default=320)
    ap.add_argument("--skip", type=int, default=3)
    ap.add_argument("--out", default="aihub_calib_images.npz")
    args = ap.parse_args()

    cam = int(args.camera) if args.camera.isdigit() else args.camera
    cap = cv2.VideoCapture(cam, cv2.CAP_V4L2)
    if not cap.isOpened():
        raise SystemExit(f"Could not open camera {cam!r}")

    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)

    arrays = []
    i = 0
    while len(arrays) < args.frames:
        ok, frame = cap.read()
        if ok and i % args.skip == 0:
            arrays.append(preprocess(frame, args.size))
            print(f"{len(arrays)}/{args.frames}")
        i += 1
        time.sleep(0.03)
    cap.release()

    arr = np.concatenate(arrays, axis=0).astype(np.float32)
    np.savez_compressed(args.out, images=arr)
    print("saved", args.out, arr.shape, arr.dtype, float(arr.min()), float(arr.max()))


if __name__ == "__main__":
    main()
```

采集校准数据：

```bash theme={null}
cd ~/yolo-depth-run
source ~/yolo-depth-venv/bin/activate
python make_aihub_calib.py --camera 0 --frames 64 --out aihub_calib_images.npz
```

为获得更好的视觉质量，请使用来自代表性场景的更多帧：

```bash theme={null}
python make_aihub_calib.py --camera 0 --frames 300 --out aihub_calib_images_300.npz
```

## 第 3 步：使用 AI Hub 进行量化

本示例使用 W8A16 量化：8 位权重和 16 位激活。创建 `submit_aihub_quant.py`：

```python theme={null}
from pathlib import Path
import numpy as np
import qai_hub as hub

client = hub.Client()
cal = np.load("aihub_calib_images.npz")["images"].astype("float32")

# Calibration entries are keyed by ONNX input name.
# This ONNX input is NHWC: images [1,320,320,3].
calibration_data = {"images": [cal[i:i+1] for i in range(cal.shape[0])]}
print("calibration samples", len(calibration_data["images"]), calibration_data["images"][0].shape)

job = client.submit_quantize_job(
    model="yolo26n-depth-aihub-clean.onnx",
    calibration_data=calibration_data,
    weights_dtype=hub.QuantizeDtype.INT8,
    activations_dtype=hub.QuantizeDtype.INT16,
    name="yolo-depth-w8a16-qcs8275",
)
print("job_id", job.job_id)
print("job_url", getattr(job, "url", None) or getattr(job, "_url", None))
job.wait()
print("status", job.get_status())

result = job.get_target_model()
out_dir = Path("aihub_quantized")
out_dir.mkdir(exist_ok=True)
if hasattr(result, "download"):
    print("downloaded", result.download(str(out_dir / "yolo-depth-w8a16.onnx")))
elif hasattr(result, "download_model"):
    print("downloaded", result.download_model(str(out_dir / "yolo-depth-w8a16.onnx")))
else:
    print("target model", result)
```

运行它：

```bash theme={null}
cd ~/yolo-depth-run
source ~/yolo-depth-venv/bin/activate
python submit_aihub_quant.py
```

<Note>
  量化后的 ONNX 计算图内部通常包含整数张量。在本原型中，对外的公开输出仍为 `FLOAT [1,1,320,320]`。反量化之前的内部张量为 `uint16`，随后通过 `DequantizeLinear` 得到公开的 float 输出。在假定输出类型或布局之前，请先检查你自己的计算图。
</Note>

## 第 4 步：将量化模型编译为 QNN DLC

在 AI Hub 中，为与你的开发板 SoC/NPU 代次相匹配的目标编译量化模型，并选择 **Qualcomm AI Runtime / QNN DLC** 目标运行时。

将编译好的 DLC 下载到开发板：

```text theme={null}
~/yolo-depth-run/aihub_compiled/yolo26n-depth-aihub-qcs8275-qnn-dlc.dlc
```

在编写原生应用之前，先使用 `qnn-net-run` 进行健全性检查：

```bash theme={null}
cd ~/yolo-depth-run
mkdir -p aihub_dlc_test/input aihub_dlc_test/out

# input/images.raw must be one preprocessed [1,320,320,3] float32 tensor.
# Use your own preprocessing script or the live app's letterbox function.

echo "images:=aihub_dlc_test/input/images.raw" > aihub_dlc_test/input_list.txt

qnn-net-run \
  --backend /usr/lib/libQnnHtp.so \
  --dlc_path aihub_compiled/yolo26n-depth-aihub-qcs8275-qnn-dlc.dlc \
  --input_list aihub_dlc_test/input_list.txt \
  --output_dir aihub_dlc_test/out \
  --log_level warn
```

如果此步骤失败，请先修复模型/运行时问题，再构建原生应用。

## 第 5 步：生成 QNN 上下文二进制文件

下载的 DLC 可能包含拓扑、参数和权重，而不包含预构建的 HTP 上下文缓存。原生应用可以自行组建，但标准的快速路径是先生成一次 QNN 上下文二进制文件，然后直接加载它。

```bash theme={null}
cd ~/yolo-depth-run
mkdir -p qnn_context

qnn-context-binary-generator \
  --backend /usr/lib/libQnnHtp.so \
  --dlc_path aihub_compiled/yolo26n-depth-aihub-qcs8275-qnn-dlc.dlc \
  --binary_file yolo26n-depth-aihub-qcs8275-context.bin \
  --output_dir qnn_context \
  --log_level warn 2>&1 | tee qnn_context/generate_context.log
```

根据工具版本的不同，生成的文件可能带有双重后缀：

```text theme={null}
~/yolo-depth-run/qnn_context/yolo26n-depth-aihub-qcs8275-context.bin.bin
```

<Tip>
  请将 QNN 上下文二进制文件视为目标专用文件。它与硬件目标以及 QAIRT/QNN 版本绑定。当你更换开发板镜像、QAIRT 版本、目标设备或模型时，请重新生成它。
</Tip>

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

原生运行器做三件事：

1. 从 `/usr/lib` 动态加载 QNN provider。
2. 使用 `QnnContext_createFromBinary()` 加载生成的上下文二进制文件。
3. 复用计算图和张量，以便重复调用 `QnnGraph_execute()`。

它还提供一个简单的基于行的服务器模式，使 Python 可以在不重新加载模型的情况下流式发送帧：

```text theme={null}
READY <input_bytes> <output_bytes>
RUN <input.raw> <output.raw>
OK <inference_ms> <output_bytes>
QUIT
```

创建目录：

```bash theme={null}
cd ~/yolo-depth-run
mkdir -p qnn_app
```

创建 `qnn_app/Makefile`：

```makefile theme={null}
CXX ?= g++
CXXFLAGS ?= -std=c++17 -O3 -Wall -Wextra -I/usr/include -I/usr/include/QNN
LDFLAGS ?= -ldl

all: qnn_dlc_runner

qnn_dlc_runner: qnn_dlc_runner.cpp
	$(CXX) $(CXXFLAGS) $< -o $@ $(LDFLAGS)

clean:
	rm -f qnn_dlc_runner
```

从[配套文件页面](/zh/tutorials/aihub-qnn-native-yolo-depth-files)复制完整的 `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()`，并将输出张量写入磁盘。

构建：

```bash theme={null}
cd ~/yolo-depth-run/qnn_app
make -j$(nproc)
```

运行一次性基准测试：

```bash theme={null}
cd ~/yolo-depth-run/qnn_app

./qnn_dlc_runner \
  --dlc ../qnn_context/yolo26n-depth-aihub-qcs8275-context.bin.bin \
  --input ../aihub_dlc_test/input/images.raw \
  --output qnn_app_output.raw \
  --warmup 20 \
  --loops 500
```

预期输出类似于：

```text theme={null}
graph: graph_ymndtmzg inputs=1 outputs=1
input: images bytes=1228800 dtype=0x232
output: output_0 bytes=409600 dtype=0x232
loops=500 warmup=20
avg_ms=16.1 p50_ms=16.1 p90_ms=16.6 p99_ms=17.4 fps=62.0
wrote=qnn_app_output.raw bytes=409600
```

与 `qnn-net-run` 的输出进行对比以验证正确性：

```bash theme={null}
cd ~/yolo-depth-run/qnn_app
python3 - <<'PY'
import numpy as np
app = np.fromfile("qnn_app_output.raw", dtype=np.float32)
ref = np.fromfile("../aihub_dlc_test/out/Result_0/output_0.raw", dtype=np.float32)
print("app", app.shape, app.min(), app.mean(), app.max())
print("ref", ref.shape, ref.min(), ref.mean(), ref.max())
print("max_abs_diff", np.max(np.abs(app-ref)))
print("mean_abs_diff", np.mean(np.abs(app-ref)))
PY
```

正确的原生运行器应与 `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`

从[配套文件页面](/zh/tutorials/aihub-qnn-native-yolo-depth-files)复制完整的实时 Python 应用。一个最小化的循环如下所示：

```python theme={null}
proc.stdin.write(f"RUN {in_raw} {out_raw}\n")
proc.stdin.flush()
line = proc.stdout.readline().strip()
if not line.startswith("OK "):
    raise RuntimeError(line)
qnn_ms = float(line.split()[1])

depth_320 = np.fromfile(out_raw, dtype=np.float32).reshape(1, 1, 320, 320)[0, 0]
depth = unletterbox_depth(depth_320, meta)
color = colorize_depth(depth, cmap="spectral", mode="disparity")
overlay = cv2.addWeighted(frame, 0.45, color, 0.55, 0)
cv2.imshow("AI Hub QNN Native Live Depth", np.hstack([frame, color, overlay]))
```

从开发板的桌面会话中运行实时应用：

```bash theme={null}
cd ~/yolo-depth-run
source ~/yolo-depth-venv/bin/activate
python live_aihub_qnn_native.py --camera 0 --display-width 1280 --font-scale 1.2
```

如果摄像头索引 `0` 不正确：

```bash theme={null}
ls /dev/video*
python live_aihub_qnn_native.py --camera /dev/video0
```

建议的控制键：

| 按键          | 操作                                    |
| ----------- | ------------------------------------- |
| `q` 或 `Esc` | 退出                                    |
| `s`         | 保存 RGB 图像、着色深度 PNG、叠加 PNG 以及深度 `.npy` |

## 输出约定与数据类型检查

对于本原型：

| 张量         | 形状              | 运行时数据类型 | 备注                    |
| ---------- | --------------- | ------- | --------------------- |
| `images`   | `[1,320,320,3]` | float32 | NHWC RGB，归一化到 `[0,1]` |
| `output_0` | `[1,1,320,320]` | float32 | 类似深度/视差的输出，无需拆分输出     |

尽管 W8A16 量化在内部使用整数张量，但编译后的 QNN 运行时输出为 float32。对于每个模型都要检查这一点。常见的模式是：

```text theme={null}
output0_q -> DequantizeLinear -> output0
```

其中 `output0_q` 可能是 16 位，但公开的模型/运行时输出是 float32。

## 故障排查

### `qnn-platform-validator` 失败

NPU 后端尚未就绪。在调试模型之前，请先检查是否已安装正确的开发板镜像、固件、FastRPC 设备和 QNN 软件包。

### `qnn-context-binary-generator` 成功但应用无法加载上下文

请在将要运行应用的同一开发板/运行时上重新生成上下文。上下文二进制文件在任意 QAIRT 版本和目标之间不可移植。

### 原生应用在打印 `READY` 之前退出

在服务器模式下，请确保初始 `--input` 路径存在且字节大小正确。即使后续帧通过 `RUN` 命令提供，原型运行器在启动期间也会验证该输入路径。

在启动服务器之前创建一个虚拟输入：

```python theme={null}
np.zeros((1, 320, 320, 3), dtype=np.float32).tofile(in_raw)
```

### 摄像头能打开但显示失败

请从连接到开发板图形会话的终端运行，而不是无显示器的 SSH shell。如果使用 SSH 查看日志，请将 OpenCV 显示保留在开发板的显示器上。

### 深度颜色看起来不稳定

使用更多具有代表性的校准帧，并在实际的光照/摄像头环境中重新采集。对于实时演示，100–300 帧比 32 帧是更好的起点。

## 为什么不每帧运行一次 `qnn-net-run`？

`qnn-net-run` 非常适合验证，但它是一个命令行测试工具。如果每帧都启动它，大部分时间会花在进程启动、上下文设置和销毁上。

对于实时应用，应保持 QNN 上下文常驻：

```text theme={null}
bad live path:
  Python frame → spawn qnn-net-run → load model → run once → exit

good live path:
  start native runner once → load context once → Python sends RUN commands per frame
```

这就是在原型中常驻原生应用测得约 **16 毫秒**，而 `qnn-net-run` 路径摊销后约为 **48 毫秒**的主要原因。

## 总结

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