# 推理

## 概述

为 RVC 平台 转换的模型可以部署在 OAK 设备 上执行推理。 以下章节指导您为所需的 AI 模型设置简单的 推理管线。 我们利用 DepthAI 构建推理管线，由以下节点组成：

 * [内置节点](https://docs.luxonis.com/software-v3/depthai/depthai-components/nodes.md)（直接在 Luxonis 设备上运行），以及
 * [主机节点](https://docs.luxonis.com/software-v3/depthai/depthai-components/host_nodes.md)（在主机上运行）。

两种类型的节点可以互换连接。 内置节点稳定、优化，并在 Luxonis 设备上确保高效性能，而主机节点提供更大的灵活性，并可根据特定用例进行定制。 请查看 [DepthAI
Nodes](https://docs.luxonis.com/software-v3/ai-inference/inference/depthai-nodes.md) 库，其中包含我们内部收集的 Python 主机节点。

推理管线可以手动逐节点定义。 然而，我们也提供基于相关 NN Archive 的管线创建一定程度的自动化（例如，自动将神经网络与负责解码其输出的特定主机节点连接）。 详细信息请参见下文。

> 如果所选模型未转换为所需的 RVC 平台，请参考
> [转换](https://docs.luxonis.com/software-v3/ai-inference/conversion.md)
> 部分。

## 安装

创建推理管线需要 DepthAI (v3) 库。 使用我们的自定义主机节点（例如用于模型输出解码）需要 DepthAI Nodes 库。 您可以使用 pip 安装它们：

```bash
pip install depthai --force-reinstall
pip install depthai-nodes
```

## 推理管线

我们在此展示一个简单的推理管线模板。 它由以下四个主要部分组成，我们在下面详细描述：

 * Camera, - 模型和解析器；- 队列；- 结果。

```python
import depthai as dai
from depthai_nodes.node import ParsingNeuralNetwork

model = "..." # NN Archive 或 HubAI 模型标识符

# 创建管线
with dai.Pipeline() as pipeline:

    # Camera
    camera = pipeline.create(dai.node.Camera).build()

    # 模型和解析器
    nn_with_parser = pipeline.create(ParsingNeuralNetwork).build(
        camera, model
    )

    # 队列
    parser_output_queue = nn_with_parser.out.createOutputQueue()

    # 启动管线
    pipeline.start()

    while pipeline.isRunning():

        # 结果
        ...
```

> 除了定义
> **HubAI 模型标识符**
> 外，上述模板应可开箱即用。 但请注意，某些 OAK 设备有内部 FPS 限制（例如 OAK-D Lite）。 您可以通过
> `pipeline.create(ParsingNeuralNetwork).build(... fps=<limit>)`
> 设置 FPS 限制。

### Camera

推理管线从 [Camera](https://docs.luxonis.com/software-v3/depthai/depthai-components/nodes/camera.md) 节点开始。 它是用于推理的图像帧的来源。
该节点可以按如下方式添加到管线中：

```python
camera_node = pipeline.create(dai.node.Camera).build()
```

### 模型和解析器

推理包含两个步骤。 首先，模型对输入数据进行预测。 其次，一个后处理节点，也称为解析器，用于处理模型输出。 此步骤可选，如果跳过，则返回原始模型输出。 您可以在 [DepthAI
Nodes](https://github.com/luxonis/depthai-nodes) 库中找到有关可用解析器的更多信息。

模型使用 [NeuralNetwork](https://docs.luxonis.com/software-v3/depthai/depthai-components/nodes/neural_network.md) 节点设置。 可以建立模型-解析器对：

 * 自动，使用 ParsingNeuralNetwork 节点，或
 * 手动，将它们初始化为独立节点并连接在一起。

前者自动将模型输出与相关 [NN Archive](https://docs.luxonis.com/software-v3/ai-inference/nn-archive.md) 中定义的适当解析器连接。
这抽象掉了所有配置细节，因此是与解析器交互的首选方式。 创建的节点（独立或非独立）可以通过连接到其他节点或定义为管线队列，以与标准 DepthAI 节点相同的方式使用。

#### 自动设置

ParsingNeuralNetwork 节点通过为模型输出添加自动解析功能，扩展了标准 NeuralNetwork 节点。 它可以从 depthai_nodes 包中导入，如下所示：

```python
from depthai_nodes.node import ParsingNeuralNetwork
```

并直接从以下任意一种实例化：

(1) NN Archive 对象，或

```python
# 设置 NN Archive
nn_archive = dai.NNArchive(<path/to/NNArchiveName.tar.xz>)

# 设置模型（带解析器）并将其链接到相机输出
nn_with_parser = pipeline.create(ParsingNeuralNetwork).build(
    cameraNode, nn_archive
)
```

(2) HubAI，通过指定 模型标识符（HubAI 平台上模型的唯一标识符。
更多信息请参见[模型上传/下载](https://docs.luxonis.com/cloud/hubai/model-registry/upload-download.md)章节）。

```python
# 设置 HubAI 模型标识符
model = "..."

# 设置模型（带解析器）
nn_with_parser = pipeline.create(ParsingNeuralNetwork).build(
    camera_node, model
)
```

初始化时，管道会自动检测连接设备的平台，并设置模型以及相关的解析器。 此外，它还会设置相机节点并将其链接到模型输入。

> 如果您计划使用私有的 HubAI 模型，请确保配置
> **Luxonis Hub API 密钥**
> 。相关说明请参见
> [API 密钥最佳实践](https://docs.luxonis.com/software-v3/oak-apps/apikey-good-practices.md)
> 页面。正确配置后，API 密钥将自动用于向 HubAI 平台验证您的请求。

#### 手动设置

模型和解析器也可以实例化为独立的节点。

首先，导入感兴趣的 DepthAI Nodes 解析器或实现自己的解析器。

```python
from depthai_nodes.node import <ParserNode>
# 或者：
class ParserNode(dai.node.ThreadedHostNode):
    def __init__(self) -> None:
        super().__init__()
        self.input = self.createInput()
        self.out = self.createOutput()
    def build(self) -> "ParserNode":
        return self
    def run(self) -> None:
        nn_out_raw = self.input.get()
        nn_out_processed = ... # 自定义后处理
        self.out.send(nn_out_processed)
```

其次，通过调用管道上的 create() 方法，将模型和解析器初始化为单独的节点：

```python
model = pipeline.create(dai.node.NeuralNetwork)
parser = pipeline.create(<ParserNode>)
```

节点使用默认参数初始化，并且可以根据需要进一步配置，方式如下：

 * 在初始化时，可以通过将参数值作为参数传递给 create() 方法来设置配置： parser = pipeline.create(<ParserNode>, <ParameterName>=<ParameterValue>, ...)
   如果配置多个参数，可以将它们组织成一个 dict 并将其作为参数传递给解析器的 build() 方法： parser = pipeline.create(<ParserNode>).build(config_dict);
 * 初始化后，可以通过使用 setter 方法来更改配置： parser.<SetterMethodName>(<ParameterValue>). 您可以在 [DepthAI Nodes API
   参考](https://docs.luxonis.com/software-v3/ai-inference/inference/depthai-nodes.md) 页面上找到特定解析器可用的所有 setter 方法。

第三，设置模型可执行文件（即 RVC2 的 .blob 文件，或 RVC4 的 .dlc 文件）：

```python
model.setModelPath(<path/to/model_executable>)
```

最后，准备相机流并将独立节点链接以构成管道：

```python
width, height = ... # 模型输入尺寸
camera_stream = camera.requestOutput(size=(width, height))
camera_stream.link(model.input)
model.out.link(parser.input)
```

> 如果您有兴趣构建更高级的解析器——类似于我们原生解析器自动处理
> **NN Archives**
> 进行设置的方式——可以查看
> [DepthAI Nodes](https://github.com/luxonis/depthai-nodes)
> 库的
> [parsers](https://github.com/luxonis/depthai-nodes/tree/main/depthai_nodes/node/parsers)
> 章节。在那里，您可以了解我们在实践中是如何实现它们的。

### 队列

队列用于从管道的特定节点获取数据。 要获取输入模型的图像帧，可以使用直通队列：

```python
frame_queue = nn_with_parser.passthrough.createOutputQueue()
```

要获取（解析后的）模型输出，可以使用输出队列。 其定义取决于模型头的数量：

#### 单头

```python
parser_output_queue = nn_with_parser.out.createOutputQueue()
```

#### 多头

```python
head0_parser_output_queue = nn_with_parser.getOutput(0).createOutputQueue()
head1_parser_output_queue = nn_with_parser.getOutput(1).createOutputQueue()
...
```

### 结果

在管道通过 pipeline.start() 启动后，可以从定义的队列中获取输出。 您可以像这样获取输入帧和解析后的模型输出：

```python
while pipeline.isRunning():

    # 获取相机输出
    frame_queue_output = frame_queue.get()
    frame = frame_queue_output.getCvFrame()
    ...

    # 获取解析后的输出
    parser_output = parser_output_queue.get()
    ...
```

已解析的模型输出返回为：

 * 通用的 [DepthAI 消息](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages.md)，或
 * 自定义编写的 [DepthAI 节点消息](https://github.com/luxonis/depthai-nodes/blob/main/depthai_nodes/message/README.md)

请阅读 [DepthAI 节点 API 参考](https://docs.luxonis.com/software-v3/ai-inference/inference/depthai-nodes.md)，以了解相关格式以及如何为您的用例使用它们。

## 示例

请查阅 [OAK 示例](https://docs.luxonis.com/software-v3/ai-inference/inference/oak-examples.md) 页面。

## 故障排除

以下是一些常见问题及其解决方案。

### 设置模型 SHAVEs

SHAVEs 是 RVC2 VPU 中运行神经网络的计算核心。 有时，模型构建时使用了与目标设备支持的 SHAVE 数量不同的值。

如果模型编译时使用的 SHAVE 数量多于设备实际拥有的数量，流水线将失败并出现类似于以下内容的 RuntimeError：

```bash
NeuralNetwork: Blob compiled for ... shaves, but only ... are available in current configuration
```

这种情况通常出现在旧设备（如 OAK-D Lite）上。

相反，如果模型编译时使用的 SHAVE 数量少于设备可用数量，它仍会运行，但您可能会看到如下警告：

```bash
[14442C103180EECF00] [2.1] [4.736] [NeuralNetwork(2)] [warning] Network compiled for 8 shaves, maximum available 13, compiling for 6 shaves likely will yield in better performance
```

在这种情况下，模型未充分利用可用计算资源，重新编译模型以更好地匹配设备的 SHAVE 数量可以提高性能。

要修复使用的 SHAVE 数量，您可以：

 * 如果模型是使用旧版 [Blobconverter](https://docs.luxonis.com/software-v3/ai-inference/conversion/rvc-conversion/online/blobconverter.md)
   导出的，则可以重新编译模型并匹配 SHAVE 数量，或者
 * 如果模型是在 [HubAI](https://docs.luxonis.com/cloud/hubai/model-registry/detailed-conversion.md) 中导出的，则无需重新编译 - 您可以在流水线初始化时设置 SHAVE
   数量以匹配设备要求：

```python
nn_archive = dai.NNArchive(...)
nn_with_parser = pipeline.create(ParsingNeuralNetwork).build(
    ..., nn_archive
)
# 设置 SHAVE 数量
nn_with_parser.setNNArchive(
    nn_archive, numShaves=<Number>
)
```

> 在
> **DepthAI v3**
> 中，不再支持旧的
> `SHAVE`
> 配置方法。
> 请避免使用：
> ```python
> nn = pipeline.create(dai.node.NeuralNetwork)
> nn.setNumShaves(6)
> ```

### 更改解析器参数

要修改解析器参数，您首先需要访问解析器对象。

 * 具有独立解析器节点的流水线： 直接访问解析器节点即可。
 * 具有 ParsingNeuralNetwork 节点的流水线： 在这种情况下，解析器与 AI 模型集成在一起。 通过调用 ParsingNeuralNetwork 节点上的 .getParser() 方法来获取它。

获得解析器后，使用相关的 set 方法更新其参数。 示例：

```python
parser.setConfThreshold(0.5)
```

### 不同图像尺寸用于可视化和模型输入

您可以为模型输入和可视化使用不同的图像尺寸。 使用 ImageManip 节点将图像在发送到模型之前调整大小，同时保留原始分辨率用于显示。

示例：

```python
cam = pipeline.create(dai.node.Camera).build()

# 请求特定图像尺寸用于捕获
cam_out = cam.requestOutput(size=(<width1>, <height1>))

# 创建并配置用于模型输入的调整大小节点
resize_node = pipeline.create(dai.node.ImageManip)
resize_node.initialConfig.setOutputSize(<width2>, <height2>)
cam_out.link(resize_node.inputImage)

# 使用调整大小后的输入定义模型
nn_with_parser: ParsingNeuralNetwork = pipeline.create(ParsingNeuralNetwork).build(
    resize_node.out, ...
)

# 使用原始分辨率进行可视化
video_queue = cam_out.out.createOutputQueue() # 高分辨率流
detection_queue = nn_with_parser.out.createOutputQueue() # 低分辨率流上的检测结果
...
```

### 从 HubAI 下载模型时的存储位置

当您从 HubAI 下载模型时，它会被存储在项目根目录的 .depthai_cached_models 文件夹中。 此缓存包含之前运行的所有模型。 如果模型已缓存，则会从本地加载，而不是重新下载。 要强制重新下载，可以在下载模型时使用
useCached=False 参数。 示例：

```python
nn_archive = dai.NNArchive(dai.getModelFromZoo(model_description, useCached=False))
nn_with_parser = pipeline.create(ParsingNeuralNetwork).build(
    ..., nn_archive
)
```

或者，您可以删除 .depthai_cached_models 文件夹并重新运行流水线。

## 延伸阅读

以下部分提供了关于 RVC4 平台上推理过程的额外信息和见解，以及如何在 Qualcomm 的 Hexagon 张量处理器（HTP） 上执行 NN 模型。

### 在 RVC4 上并行运行模型

在 Qualcomm SoC 的 Hexagon Tensor Processor (HTP) 上同时运行多个模型时，需要注意资源分配和调度方面的一些重要事项。HTP 会动态共享计算和片上内存资源。根据 Qualcomm 的说法，没有直接的方法可以在 HTP
上优先处理一个模型，并且这是用户不可调优的。您应将 HTP 视为一个黑盒调度器，并选择经实证效果最佳的线程策略。

#### HTP 上的资源分配

 * HTP 计算核心和 V-TCM 内存在所有并发的 SNPE 会话之间是 弹性 共享的。
 * 内部调度器采用轮询策略；多个模型之间的资源分配 不是固定的，并且可能在帧之间变化。

#### 控制手段

 * 不支持资源导向。没有 按模型的优先级、核心亲和性或配额 API。唯一的全局杠杆是 --perf_profile 标志，它影响 SoC 级别的功耗/性能权衡。

#### 实践指导

 1. 假设在新增/停止并发会话时，延迟和吞吐量会波动。
 2. 使用 SNPE 时序日志（例如分层分析）来衡量端到端延迟，而不是猜测资源份额。
