> ## 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.

# 在 NPU 上运行 LiteRT 模型

> 使用 QNN 委托和 Python，在 Qualcomm Dragonwing 设备的 NPU 上运行目标检测应用。

## 摘要

使用 [LiteRT](https://ai.google.dev/edge/litert) 运行时和 QNN 委托，在 Qualcomm® Dragonwing™ 设备的 NPU 上加速 AI 推理。本指南通过部署一个量化目标检测模型（YOLOX）来演示端到端工作流程，该模型处理视频输入并输出带有边界框标注的帧——可以保存到文件或流式传输到显示器。

**您将学到：**

* 配置 Dragonwing 设备并部署 QIM SDK Docker 环境
* 运行在 NPU 上加速的预构建目标检测应用
* 理解应用代码，以便将其调整为适用于您自己的模型和用例

***

## 前提条件

在继续之前，请确保您具备以下条件：

| 要求     | 详细信息                                 |
| ------ | ------------------------------------ |
| **硬件** | 支持 NPU 的 Qualcomm® Dragonwing™ 设备    |
| **主机** | 具有 SSH 客户端和 Docker 支持的 Linux 或 macOS |
| **网络** | 目标设备上具备 Wi-Fi 或以太网连接                 |
| **软件** | 目标设备上已安装 Docker                      |

***

## 步骤 1：配置设备

### 启用 Wi-Fi 和 SSH

设备需要互联网连接才能下载示例应用所需的产物。如果已配置 SSH 和 Wi-Fi，请跳过此步骤。

按照[设置 SSH 连接](https://dragonwingdocs.qualcomm.com/Technologies/Ethernet/get-started-with-ethernet#set-up-an-ssh-connection)在设备上启用 Wi-Fi 和 SSH。

### 启用摄像头支持（CamX）

如果您计划使用摄像头输入，请在平台上启用 CamX：

```shell theme={null}
echo -n "camx" > /var/data
efivar -n 882f8c2b-9646-435f-8de5-f208ff80c1bd-VendorDtbOverlays -w -f /var/data
efivar -n 882f8c2b-9646-435f-8de5-f208ff80c1bd-VendorDtbOverlays -p
sync
reboot
```

<Note>
  此步骤后设备将重启。请等待设备重新上线后再继续。
</Note>

***

## 步骤 2：设置 Docker 环境

### 拉取 QIM SDK 容器镜像

在目标设备上，拉取最新的 QIM SDK Docker 镜像：

```shell theme={null}
cd $HOME
```

```shell theme={null}
docker pull artifacts.codelinaro.org/iot-solutions-microservices/qimsdk:latest
```

### 创建所需目录

创建用于存储产物、配置文件、模型和媒体的目录：

```shell theme={null}
mkdir -p /etc/cdi /etc/docker/env /etc/models /etc/labels /etc/media /root/media /root/models /root/labels /root/configs
```

### 克隆 SDK 工具仓库

在您的**主机**上，克隆 QIM SDK Debian 仓库：

```shell theme={null}
git clone https://git.codelinaro.org/clo/le/sdk-tools.git -b imsdk-tools.lnx.1.0
cd sdk-tools/qimsdk-debian/
```

### 将配置文件复制到设备

将 CDI 和环境文件从主机传输到目标设备：

```shell theme={null}
scp -r cdi/<hardware>_qli_2x_qimsdk.json root@<IP_ADDRESS>:/etc/cdi/qimsdk.json
```

```shell theme={null}
scp -r env/<hardware>_qli_2x_qimsdk.env root@<IP_ADDRESS>:/etc/docker/env/qimsdk.env
```

<Note>
  将 `<hardware>` 替换为目标设备对应的标识符（可在仓库中查看可用选项），将 `<IP_ADDRESS>` 替换为设备的 IP 地址。<br /><br />
  例如，如果目标设备是 Qualcomm Dragonwing™ RB3 Gen 2，则将 `<hardware>` 替换为 qcs6490。
</Note>

### 启动容器

在目标设备上启动 QIM SDK 容器：

```shell theme={null}
docker run -it -d \
   --net host \
   --env-file /etc/docker/env/qimsdk.env \
   --device qualcomm.com/device=qimsdk \
   -h qimsdk \
   --name qimsdk \
   artifacts.codelinaro.org/iot-solutions-microservices/qimsdk:latest
```

### 以 root 身份访问容器

```shell theme={null}
export DOCKER_ID=$(docker ps -aq)
docker exec -it ${DOCKER_ID} sh
```

<Note>
  要验证您是否以 root 身份登录，请在容器内运行 `whoami`。输出应为 `root`。
</Note>

***

## 步骤 3：安装依赖项

在容器内（以 root 身份），安装 LiteRT 运行时和所需的软件包。

### 安装 Python 工具

```shell theme={null}
apt update
apt install python3-pip python3-venv
```

### 创建虚拟环境并安装 Python 软件包

```shell theme={null}
python3 -m venv venv-litert-demo --system-site-packages
```

```shell theme={null}
. venv-litert-demo/bin/activate
```

```shell theme={null}
pip3 install ai-edge-litert Pillow opencv-python
```

### 安装 GStreamer 和 GTK 依赖项

通过 Wayland 输出视频显示需要这些软件包：

```shell theme={null}
apt install -y libgstreamer1.0-dev gstreamer1.0-plugins-ugly gstreamer1.0-libav \
              gstreamer1.0-alsa gstreamer1.0-gtk3 python3-gi python3-gi-cairo \
              gir1.2-gtk-3.0 python3-full pkg-config cmake libcairo2-dev \
              libgirepository1.0-dev gir1.2-glib-2.0 build-essential python3-dev \
              pkg-config meson
```

***

## 步骤 4：下载应用和模型产物

仍在容器内，设置目标检测应用：

### 创建应用目录

```shell theme={null}
mkdir -p /etc/apps/ && cd /etc/apps/
```

### 下载应用脚本

```shell theme={null}
curl -L https://raw.githubusercontent.com/qualcomm/sample-apps-for-qualcomm-linux/refs/heads/main/qualcomm-linux/applications/LiteRT/object_detection.py -o /etc/apps/object_detection.py
```

### 下载模型、标签和示例视频

```shell theme={null}
curl -L https://raw.githubusercontent.com/qualcomm/sample-apps-for-qualcomm-linux/refs/heads/main/qualcomm-linux/artifacts/labels/coco_labels.txt -o /etc/labels/coco_labels.txt
```

```shell theme={null}
curl -L https://raw.githubusercontent.com/qualcomm/sample-apps-for-qualcomm-linux/refs/heads/main/qualcomm-linux/artifacts/videos/video.mp4 -o /etc/media/video.mp4
```

```shell theme={null}
curl -L https://huggingface.co/qualcomm/Yolo-X/resolve/v0.30.5/Yolo-X_w8a8.tflite -o /etc/models/yolox_quantized.tflite
```

### 退出 root shell

您需要退出并以 `qimsdk` 用户身份重新进入容器才能运行该应用：

```shell theme={null}
exit
```

***

## 步骤 5：运行目标检测应用

### 以标准用户身份进入容器

```shell theme={null}
docker exec -it ${DOCKER_ID} bash
```

### 激活 Python 环境

```shell theme={null}
. venv-litert-demo/bin/activate
```

### 运行应用

```shell theme={null}
cd /etc/apps
```

<Tabs>
  <Tab title="输出到文件">
    运行应用并将输出保存为视频文件：

    ```shell theme={null}
    python3 object_detection.py --output file
    ```

    处理完成后，取回输出视频：

    ```shell theme={null}
    exit
    ```

    ```shell theme={null}
    docker cp ${DOCKER_ID}:/tmp/output_object_detection.mp4 /etc/media/output_object_detection.mp4
    ```

    要将该文件复制到主机：

    ```shell theme={null}
    scp root@<IP_ADDRESS>:/etc/media/output_object_detection.mp4 .
    ```
  </Tab>

  <Tab title="输出到显示器">
    要通过 Wayland 将输出直接流式传输到已连接的显示器：

    ```shell theme={null}
    python3 object_detection.py --output wayland
    ```

    <Note>
      使用此模式前，请确保设备已连接显示器且 Wayland 正在运行。
    </Note>
  </Tab>
</Tabs>

***

<h2 id="create-object_detection-py">
  代码讲解：使用 OpenCV 和 LiteRT 进行目标检测
</h2>

本节讲解 `object_detection.py` 应用。可将其作为参考，在 Qualcomm Dragonwing 设备上使用 LiteRT 构建自定义推理应用。

<Note>
  以下代码中的后处理是为来自 [Qualcomm AI Hub](https://aihub.qualcomm.com/) 的目标检测模型设计的。
  对于自定义模型，请更新后处理逻辑以匹配模型的输出格式和要求。
</Note>

### 导入软件包

```python theme={null}
#!/usr/bin/env python3
import cv2
import numpy as np
import argparse
import ai_edge_litert.interpreter as tflite
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst
```

### 解析输出参数

```python theme={null}
parser = argparse.ArgumentParser(description="Run object detection and output to file or Wayland.")
parser.add_argument("--output", choices=["file", "wayland"], default="file",
                    help="Choose output mode: 'file' (default) or 'wayland'")
args = parser.parse_args()
```

### 配置模型参数

```python theme={null}
MODEL_PATH = "/etc/models/yolox_quantized.tflite"  # YOLOX quantized model
LABEL_PATH = "/etc/labels/coco_labels.txt"
VIDEO_IN = "/etc/media/video.mp4"
VIDEO_OUT = "output_object_detection.mp4"
DELEGATE_PATH = "libQnnTFLiteDelegate.so"

FRAME_W, FRAME_H = 1600, 900
FPS_OUT = 30
CONF_THRES = 0.25
NMS_IOU_THRES = 0.50
BOX_SCALE = 3.2108588218688965
BOX_ZP = 31.0
SCORE_SCALE = 0.0038042240776121616
```

### 使用 QNN 委托加载模型

QNN 委托可在 NPU 上启用推理：

```python theme={null}
delegate_options = {'backend_type': 'htp'}
delegate = tflite.load_delegate(DELEGATE_PATH, delegate_options)
interpreter = tflite.Interpreter(model_path=MODEL_PATH, experimental_delegates=[delegate])
interpreter.allocate_tensors()

in_det = interpreter.get_input_details()
out_det = interpreter.get_output_details()
in_h, in_w = in_det[0]["shape"][1:3]

labels = [l.strip() for l in open(LABEL_PATH)]
```

### 设置视频捕获和预处理

```python theme={null}
cap = cv2.VideoCapture(VIDEO_IN)
sx, sy = FRAME_W / in_w, FRAME_H / in_h
frame_rs = np.empty((FRAME_H, FRAME_W, 3), np.uint8)
input_tensor = np.empty((1, in_h, in_w, 3), np.uint8)
```

### 配置输出管道

```python theme={null}
if args.output == "file":
    fourcc = cv2.VideoWriter_fourcc(*"mp4v")
    out_writer = cv2.VideoWriter(VIDEO_OUT, fourcc, FPS_OUT, (FRAME_W, FRAME_H))
else:
    Gst.init(None)
    pipeline = Gst.parse_launch(
        'appsrc name=src is-live=true block=true format=time caps=video/x-raw,format=BGR,width=1600,height=900,framerate=30/1 ! videoconvert ! waylandsink'
    )
    appsrc = pipeline.get_by_name('src')
    pipeline.set_state(Gst.State.PLAYING)

frame_cnt = 0
```

### 在主循环中运行推理

读取每个视频帧、运行推理、应用 NMS 并绘制边界框：

```python theme={null}
while True:
    ok, frame = cap.read()
    if not ok:
        break
    frame_cnt += 1

    cv2.resize(frame, (FRAME_W, FRAME_H), dst=frame_rs)
    cv2.resize(frame_rs, (in_w, in_h), dst=input_tensor[0])

    interpreter.set_tensor(in_det[0]['index'], input_tensor)
    interpreter.invoke()

    boxes_q = interpreter.get_tensor(out_det[0]['index'])[0]
    scores_q = interpreter.get_tensor(out_det[1]['index'])[0]
    classes_q = interpreter.get_tensor(out_det[2]['index'])[0]

    boxes = BOX_SCALE * (boxes_q.astype(np.float32) - BOX_ZP)
    scores = SCORE_SCALE * scores_q.astype(np.float32)
    classes = classes_q.astype(np.int32)

    mask = scores >= CONF_THRES
    if np.any(mask):
        boxes_f = boxes[mask]
        scores_f = scores[mask]
        classes_f = classes[mask]

        x1, y1, x2, y2 = boxes_f.T
        boxes_cv2 = np.column_stack((x1, y1, x2 - x1, y2 - y1))

        idx_cv2 = cv2.dnn.NMSBoxes(
            bboxes=boxes_cv2.tolist(),
            scores=scores_f.tolist(),
            score_threshold=CONF_THRES,
            nms_threshold=NMS_IOU_THRES
        )

        if len(idx_cv2):
            idx = idx_cv2.flatten()
            sel_boxes = boxes_f[idx]
            sel_scores = scores_f[idx]
            sel_classes = classes_f[idx]

            sel_boxes[:, [0, 2]] *= sx
            sel_boxes[:, [1, 3]] *= sy
            sel_boxes = sel_boxes.astype(np.int32)

            sel_boxes[:, [0, 2]] = np.clip(sel_boxes[:, [0, 2]], 0, FRAME_W - 1)
            sel_boxes[:, [1, 3]] = np.clip(sel_boxes[:, [1, 3]], 0, FRAME_H - 1)

            for (x1i, y1i, x2i, y2i), sc, cl in zip(sel_boxes, sel_scores, sel_classes):
                cv2.rectangle(frame_rs, (x1i, y1i), (x2i, y2i), (0, 255, 0), 2)
                lab = labels[cl] if cl < len(labels) else str(cl)
                cv2.putText(frame_rs, f"{lab} {sc:.2f}", (x1i, max(10, y1i - 5)),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

    if args.output == "file":
        out_writer.write(frame_rs)
    else:
        data = frame_rs.tobytes()
        buf = Gst.Buffer.new_allocate(None, len(data), None)
        buf.fill(0, data)
        buf.duration = Gst.util_uint64_scale_int(1, Gst.SECOND, FPS_OUT)
        timestamp = cap.get(cv2.CAP_PROP_POS_MSEC) * Gst.MSECOND
        buf.pts = buf.dts = int(timestamp)
        appsrc.emit('push-buffer', buf)
```

### 清理资源

```python theme={null}
cap.release()
if args.output == "file":
    out_writer.release()
    print(f"Done - processed video saved to {VIDEO_OUT}")
else:
    appsrc.emit('end-of-stream')
    pipeline.set_state(Gst.State.NULL)
    print("Done - video streamed to Wayland sink")
```

***

## 故障排除

| 问题               | 解决方案                                                                   |
| ---------------- | ---------------------------------------------------------------------- |
| `docker pull` 失败 | 验证设备可访问互联网。检查 DNS 设置和代理配置。                                             |
| QNN 委托加载失败       | 确保 CDI 和环境文件与您的硬件匹配。验证容器是使用 `--device qualcomm.com/device=qimsdk` 启动的。 |
| 显示器上没有视频输出       | 确认 Wayland 正在运行且显示器已连接。可先尝试 `file` 输出模式以验证推理是否正常工作。                    |
| 模型下载失败           | 检查网络连接。模型托管在 Hugging Face 上，在某些环境中可能需要代理设置。                            |
| FPS 低或推理慢        | 验证模型是否在 NPU（HTP 后端）上运行。检查委托选项中的 `backend_type` 是否设置为 `'htp'`。          |

***

## 后续步骤

* **尝试不同的模型**：将 YOLOX 模型替换为来自 [Qualcomm AI Hub](https://aihub.qualcomm.com/) 的其他量化模型，用于图像分类、姿态估计或分割等任务。
* **使用实时摄像头输入**：通过将 `VIDEO_IN` 路径改为设备捕获源，修改应用以使用摄像头画面而非预录制的视频。
* **调整检测参数**：调整 `CONF_THRES` 和 `NMS_IOU_THRES`，为您的用例优化检测精度。
* **构建自定义应用**：以代码讲解为模板，创建面向 Dragonwing NPU 的自有推理管道。
