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

# 为自定义模型添加后处理支持

> 当内置 qtimlpostprocess 模块不支持您的模型输出格式时,向 QIM SDK 管线添加自定义后处理模块。

本页介绍当内置 `qtimlpostprocess` 模块不支持您的模型输出格式时,如何将自定义后处理模块集成到 QIM SDK 管线中。

当您的模型可以使用现有 QIM SDK 推理插件运行,但其输出张量需要经过自定义处理才能转换为标签、边界框、掩码、关键点、原始张量或其他应用特定元数据时,请使用此工作流。本指南以基于 YOLOv8 的目标检测模型作为端到端示例,演示集成过程的每个步骤。

<Info>
  **[应用开发工作流](../topic/imsdk-app-development#application-development-workflow)的步骤 3a。** 当接入的新模型输出格式不受任何内置后处理模块支持时,请使用此路径。如果您尚未确认是否需要自定义后处理模块,请先完成[步骤 2a](../topic/integrate-custom-model)。
</Info>

## 开始之前

在创建自定义后处理模块之前,请确认满足以下前提条件。只有当所有内置 qtimlpostprocess 模块都无法处理您的模型输出格式时,才需要自定义实现。

| 开始前需确认 | 验证方法 |
| - | - |
| 没有内置后处理模块支持您的模型输出 | 在目标设备上运行 gst-inspect-1.0 qtimlpostprocess,并将支持的张量格式和形状与您的模型输出进行比较。只有在所有内置模块都不兼容时才需要自定义模块。 |
| 模型可通过 QIM SDK 推理插件成功运行 | 在实现自定义后处理之前,确认模型能够正确加载并生成输出张量。 |
| 已了解输出张量布局 | 记录输出张量的数量、数据类型、维度以及每个张量所代表的信息。 |
| 需要一台用于交叉编译的主机 | 使用 Ubuntu 22.04 或 Ubuntu 24.04,并安装所需的构建工具,包括 `cmake` 和 `g++-aarch64-linux-gnu`。 |
| 设备访问 | 确保已配置 SSH 访问,以便在设备上部署、测试和调试自定义模块。 |

<Tip>
  一个模块服务于多个模型的情况非常常见。例如,`yolov8` 模块可处理输出结构相同的多个 YOLO 系列检测器。在断定需要自定义模块之前,请仔细检查报告的张量形状。
</Tip>

下图显示了添加自己的后处理支持的整体流程,从开发和集成模块到运行参考应用程序。

<img src="https://mintcdn.com/qualcomm-staging/uWQLnBcS9sxlZhgB/Key-Documents/AI-Developer-Workflow/_images/add-postprocessing-custom-model.png?fit=max&auto=format&n=uWQLnBcS9sxlZhgB&q=85&s=13f1e3ef25142381d6b84ab41193a268" alt="添加自定义后处理支持的流程概览" width="1788" height="468" data-path="Key-Documents/AI-Developer-Workflow/_images/add-postprocessing-custom-model.png" />

## AI 管线概述

Qualcomm Intelligent Multimedia SDK (QIM SDK) 提供构建 AI、多媒体和计算机视觉管线所需的构建模块。AI 工作流由三个关键的 GStreamer 阶段构成,自定义后处理模块仅扩展第三个阶段。

| 阶段 | 插件 | 职责 |
| - | - | - |
| 预处理 | `qtimlvconverter` | 将传入的数据流转换为适合 AI 推理的张量格式。 |
| 推理 | `qtimltflite`、`qtimlqnn` | 使用 AI 模型执行推理,并对输出张量进行反量化。除反量化之外,此阶段不执行任何预处理或后处理。 |
| 后处理 | `qtimlpostprocess` | 解析输出张量并生成包含机器学习元数据的缓冲区。 |

后处理阶段可以通过以下方式之一输出元数据:

* 使用 `qtimetamuxer` 将其附加到源流。
* 将其直接流式传输到 RTSP、RTMP 或 Redis 等端点。
* 将其转换为图像掩码,并使用 `qtivcomposer` 叠加到源视频帧上。

<img src="https://mintcdn.com/qualcomm-staging/uWQLnBcS9sxlZhgB/Key-Documents/AI-Developer-Workflow/_images/ai-im-sdk-pipelines.png?fit=max&auto=format&n=uWQLnBcS9sxlZhgB&q=85&s=46cfffed297177805aa12c0247e6c04f" alt="AI IM SDK 管线阶段" width="1115" height="204" data-path="Key-Documents/AI-Developer-Workflow/_images/ai-im-sdk-pipelines.png" />

<Note>
  您只需负责后处理模块。预处理、推理和元数据使用均为标准 QIM SDK 元素,`qtimlpostprocess` 插件会围绕您的模块处理批处理、输出格式协商和结果数量限制。
</Note>

### 示例:直接使用 ML 元数据

在此示例中,源流在推理插件之后不再传播,元数据被直接使用。

<img src="https://mintcdn.com/qualcomm-staging/uWQLnBcS9sxlZhgB/Key-Documents/AI-Developer-Workflow/_images/ai-im-sdk-example-direct-metadata.png?fit=max&auto=format&n=uWQLnBcS9sxlZhgB&q=85&s=233e2825ebeed8da22b513f3bfda268c" alt="直接使用 ML 元数据的管线" width="1810" height="281" data-path="Key-Documents/AI-Developer-Workflow/_images/ai-im-sdk-example-direct-metadata.png" />

### 示例:将 ML 元数据附加到源视频

在此示例中,ML 元数据附加到源视频。叠加层使用附加的元数据绘制边界框、文本和其他可视元素。结果显示在屏幕上,或通过网络流式传输。

<img src="https://mintcdn.com/qualcomm-staging/uWQLnBcS9sxlZhgB/Key-Documents/AI-Developer-Workflow/_images/ai-im-sdk-example-attached-metadata.png?fit=max&auto=format&n=uWQLnBcS9sxlZhgB&q=85&s=5ad40ab33cf302f27b58e09f8d5d0ba6" alt="将 ML 元数据附加到源视频的管线" width="1810" height="281" data-path="Key-Documents/AI-Developer-Workflow/_images/ai-im-sdk-example-attached-metadata.png" />

### 示例:将 ML 元数据转换为图像掩码

在此示例中,ML 元数据被转换为图像掩码,然后叠加 (blit) 到源流之上。

<img src="https://mintcdn.com/qualcomm-staging/uWQLnBcS9sxlZhgB/Key-Documents/AI-Developer-Workflow/_images/ai-im-sdk-example-mask-metadata.png?fit=max&auto=format&n=uWQLnBcS9sxlZhgB&q=85&s=fac6f85e637dbeb67338572a6e46d7bb" alt="将 ML 元数据转换为图像掩码的管线" width="1810" height="281" data-path="Key-Documents/AI-Developer-Workflow/_images/ai-im-sdk-example-mask-metadata.png" />

## qtimlpostprocess 插件行为

`qtimlpostprocess` 是一个可自定义的插件,为推理插件的张量输出后处理提供库接口。后处理模块负责张量解析,并生成预测结果列表。

每个后处理模块处理一种类型的 ML 模型及其变体,例如所有 YOLOv8 检测变体。该插件负责管理模块执行、输出生成(ML 元数据、图像掩码或张量)、批处理、ML 暂存以及其他相关任务。

<img src="https://mintcdn.com/qualcomm-staging/uWQLnBcS9sxlZhgB/Key-Documents/AI-Developer-Workflow/_images/add-postprocessing-support.png?fit=max&auto=format&n=uWQLnBcS9sxlZhgB&q=85&s=d9e68461709416c49b6147b1ed5cf882" alt="输入、输出、后处理模块与插件之间的关系" width="1310" height="1151" data-path="Key-Documents/AI-Developer-Workflow/_images/add-postprocessing-support.png" />

该插件接收封装在 GStreamer 缓冲区中的张量列表作为输入。附加到每个缓冲区的 ML 元数据指定了张量数量、张量形状、模型输入张量形状、每个输入张量中有多少被流数据填充、时间戳以及批处理索引。

### 插件属性

| 插件属性 | 是否必需? | 用途 | 示例 |
| - | - | - | - |
| `module` | 必需 | 选择用于解析输出张量的后处理模块。它不定义插件输出类型,输出类型在 caps 协商期间确定。 | `module=yolov8` |
| `settings` | 可选 | 传递模块特定的 JSON 配置(字符串或文件路径),例如置信度阈值、NMS 阈值、关键点或 token。 | `settings="{\"confidence\": 75.0}"` |
| `labels` | 可选 | 提供标签文件,用于将类别 ID 或索引映射为可读标签。 | `labels=${HOME}/Downloads/qimsdk_samples/labels/yolox.json` |
| `results` | 可选 | 限制过滤后输出的结果数量。如果模型检测到的结果多于允许数量,则丢弃置信度最低的结果。 | `results=10` |

### 输出格式

输出格式通过 GStreamer 管线协商确定。系统会自动协商最合适的格式,但您也可以使用 GStreamer caps 过滤器手动指定。该插件仅支持一个源 pad,因此如果应用需要同时使用多种输出格式,请在管线中再添加一个 `qtimlpostprocess` 实例。

| 输出格式 | 描述 | 适用场景 |
| - | - | - |
| 文本元数据 | 序列化 ML 元数据,使其可以附加到源流(使用 `qtimetamuxer`)或由下游插件使用。 | 应用需要标签、边界框、得分或其他结构化结果。 |
| 图像掩码 | 生成包含边界框、标签、点或线等可视结果的透明帧。例如,目标检测模块绘制带标签的边界框,然后由 `qtivcomposer` 将掩码叠加到视频流上。 | 应用需要将 AI 结果混合回视频流。 |
| 张量 | 从后处理阶段生成张量输出。 | 管线串联多个 ML 模型,且下一个模型需要经过修改的张量输入。例如,第一阶段产生四个张量,而下一阶段需要其中三个。 |

该插件支持以下模型类型:

* 目标检测
* 图像分类
* 图像分割
* 超分辨率
* 姿态估计
* 音频分类

## 编写后处理模块

后处理模块是一个共享库,用于解析推理插件输出的张量。`qtimlpostprocess` 插件在运行时加载并运行该模块。QIM SDK 提供了种类丰富的开箱即用模块。

| 模块类别 | 内置模块示例 | 用例 |
| - | - | - |
| 目标检测 | `yolov5`、`yolov8`、`yolo-nas`、`ssd-mobilenet`、`qfd`、`qpd`、`east-textdt` | 当模型输出可解码为边界框、标签、得分或特征点时使用。 |
| 分类 | `mobilenet`、`mobilenet-softmax`、`resnet`、`ocr`、`qfr` | 当模型输出类别得分或识别结果时使用。 |
| 姿态估计 | `hrnet`、`lite-3dmm`、`posenet` | 当模型输出人体、面部或物体关键点时使用。 |
| 分割 | `deeplab`、`midas-v2`、`yolov8-seg` | 当模型输出掩码、深度图或像素级类别时使用。 |
| 超分辨率 | `srnet` | 当输出是用于图像/视频放大的增强帧或张量时使用。 |
| 音频分类 | `yamnet` | 当模型输出音频类别概率时使用。 |

在目标设备上运行 `gst-inspect-1.0 qtimlpostprocess`,可查看支持的模块及其支持的张量形状的完整列表。以下示例显示了部分输出。

```text theme={null}
  module              : Module name that is going to be used for processing the tensors
                      flags: readable, writable
                      Enum "GstMLPostProcessModules" Default: 0, "none"
                          (0): none             - No module, default invalid mode
                          (1): mediapipe-pose   - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 896, 12
                                Tensor 1: 1, 896, 1

                          (2): ocr              - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 26, 1, 37
                                Type: FLOAT32
                                Tensor 0: 1, 26-48, 37

                          (3): midas-v2         - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 256-518, 256-518, 1
                                Type: FLOAT32
                                Tensor 0: 1, 256-518, 256-518

                          (4): qfd              - 
                              Supported tensors:
                                Type: UINT8, FLOAT32
                                Tensor 0: 1, 60, 80, 1
                                Tensor 1: 1, 60, 80, 1
                                Tensor 2: 1, 60, 80, 10
                                Tensor 3: 1, 60, 80, 4
                                Type: UINT8, FLOAT32
                                Tensor 0: 1, 120, 160, 1
                                Tensor 1: 1, 120, 160, 10
                                Tensor 2: 1, 120, 160, 4
                                Type: UINT8, FLOAT32
                                Tensor 0: 1, 60, 80, 4
                                Tensor 1: 1, 60, 80, 10
                                Tensor 2: 1, 60, 80, 1
                                Type: UINT8, FLOAT32
                                Tensor 0: 1, 60, 80, 1
                                Tensor 1: 1, 60, 80, 4
                                Tensor 2: 1, 60, 80, 10

                          (5): yolov5           - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 1-136, 1-136, 18-3018
                                Tensor 1: 1, 1-136, 1-136, 18-3018
                                Tensor 2: 1, 1-136, 1-136, 18-3018
                                Type: FLOAT32
                                Tensor 0: 1, 3, 1-136, 1-136, 6-85
                                Tensor 1: 1, 3, 1-136, 1-136, 6-85
                                Tensor 2: 1, 3, 1-136, 1-136, 6-85
                                Type: FLOAT32
                                Tensor 0: 1, 21-72828, 6-85

                          (6): srnet            - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 32-4096, 32-4096
                                Type: FLOAT32
                                Tensor 0: 1, 32-4096, 32-4096, 1-3

                          (7): wave2vec         - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 124, 32

                          (8): mobilenet-softmax - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 2-2000

                          (9): ssd-mobilenet    - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 10, 4
                                Tensor 1: 1, 10
                                Tensor 2: 1, 10
                                Tensor 3: 1
                                Type: FLOAT32
                                Tensor 0: 1, 10
                                Tensor 1: 1, 10, 4
                                Tensor 2: 1, 10
                                Tensor 3: 1
                                Tensor 4: 1, 10
                                Type: FLOAT32
                                Tensor 0: 1, 100
                                Tensor 1: 1
                                Tensor 2: 1, 100, 4
                                Tensor 3: 1, 100
                                Type: FLOAT32
                                Tensor 0: 1, 25, 4
                                Tensor 1: 1, 25
                                Tensor 2: 1, 25
                                Tensor 3: 1

                          (10): easy-ocr-detector - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 8-480, 8-480, 1-5

                          (11): tensor           - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 63
                                Tensor 1: 1, 1
                                Tensor 2: 1, 1
                                Tensor 3: 1, 63

                          (12): deeplab-argmax   - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 32-2048, 32-2048
                                Type: FLOAT32
                                Tensor 0: 1, 32-2048, 32-2048, 1-150

                          (13): mediapipe-pose-landmark - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1
                                Tensor 1: 1, 25, 4

                          (14): hlandmark        - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 63
                                Tensor 1: 1, 1
                                Tensor 2: 1, 1
                                Tensor 3: 1, 63

                          (15): posenet          - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 5-251, 5-251, 1-17
                                Tensor 1: 1, 5-251, 5-251, 2-34
                                Tensor 2: 1, 5-251, 5-251, 4-64
                                Type: FLOAT32
                                Tensor 0: 1, 17, 33, 17
                                Tensor 1: 1, 34, 33, 17
                                Tensor 2: 1, 32, 33, 17
                                Tensor 3: 1, 32, 33, 17
                                Tensor 4: 1, 17, 33, 17

                          (16): hrnet            - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 1-256, 1-256, 1-17

                          (17): palmd            - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 2016, 18
                                Tensor 1: 1, 2016, 1

                          (18): ocr-recognizer   - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 250, 1, 97
                                Type: FLOAT32
                                Tensor 0: 1, 26-250, 97

                          (19): mobilenet        - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 2-6440

                          (20): qfr-softmax      - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 512
                                Tensor 1: 1, 32
                                Tensor 2: 1, 2
                                Tensor 3: 1, 2
                                Tensor 4: 1, 2
                                Tensor 5: 1, 2

                          (21): qfr              - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 512
                                Tensor 1: 1, 32
                                Tensor 2: 1, 2
                                Tensor 3: 1, 2
                                Tensor 4: 1, 2
                                Tensor 5: 1, 2

                          (22): yolov8           - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 21-42840, 4
                                Tensor 1: 1, 21-42840
                                Tensor 2: 1, 21-42840
                                Type: FLOAT32
                                Tensor 0: 1, 4, 21-42840
                                Tensor 1: 1, 1-1001, 21-42840
                                Type: FLOAT32
                                Tensor 0: 1, 5-1005, 21-42840

                          (23): qpd              - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 120, 160, 3
                                Tensor 1: 1, 120, 160, 12
                                Tensor 2: 1, 120, 160, 34
                                Tensor 3: 1, 120, 160, 17

                          (24): yamnet           - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 521

                          (25): yolo-nas         - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 21-42840, 4
                                Tensor 1: 1, 21-42840
                                Tensor 2: 1, 21-42840
                                Type: FLOAT32
                                Tensor 0: 1, 21-42840, 2
                                Tensor 1: 1, 21-42840, 2
                                Tensor 2: 1, 21-42840, 81
                                Type: FLOAT32
                                Tensor 0: 1, 5-1005, 21-42840
                                Type: FLOAT32
                                Tensor 0: 1, 21-42840, 1-1001
                                Tensor 1: 1, 21-42840, 4
                                Type: FLOAT32
                                Tensor 0: 1, 21-42840, 4
                                Tensor 1: 1, 21-42840, 1-1001

                          (26): lite-3dmm        - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 512
                                Tensor 1: 1, 265
                                Type: FLOAT32
                                Tensor 0: 1, 265

                          (27): east-textdt      - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 8-480, 8-480, 1-5
                                Tensor 1: 1, 8-480, 8-480, 1-5

                          (28): yolov8-seg       - 
                              Supported tensors:
                                Type: FLOAT32
                                Tensor 0: 1, 21-42840, 4
                                Tensor 1: 1, 21-42840
                                Tensor 2: 1, 21-42840, 1-32
                                Tensor 3: 1, 21-42840
                                Tensor 4: 1, 32-2048, 32-2048, 1-32
                                Type: FLOAT32
                                Tensor 0: 1, 21-42840, 4
                                Tensor 1: 1, 21-42840
                                Tensor 2: 1, 21-42840, 1-32
                                Tensor 3: 1, 32-2048, 32-2048, 1-32

```

如果没有内置模块适合您的模型,请自行实现。模块可以独立于 QIM SDK 构建,您只需要接口头文件和工具链。将模块构建为共享库,复制到设备上的 `/usr/lib/imsdk/qtimlpostprocess/modules/`,插件会自动检测到它,从而可以在管线中选择该模块。

### 模块和库命名

为避免名称冲突,模块共享库必须遵循 `libml-postprocess-<module-name>.so` 命名约定。`<module-name>` 必须与传递给 `qtimlpostprocess` `module` 属性的值一致。例如,YOLOv8 模块命名为 `libml-postprocess-yolov8.so`,并通过 `module=yolov8` 选择。

### 实现后处理模块接口

后处理模块提供 C++ API。由于无法直接从共享库加载 C++ API,类的实例化被封装在一个 C 函数中,该函数已在头文件中实现,您无需自行处理实例化。请在派生自 `IModule` 接口的模块类中实现以下方法。

| 方法 | 签名 | 用途 | 开发者职责 |
| - | - | - | - |
| 构造函数 / 析构函数 | — | 通用入口点;构造函数不接受参数。 | 保持初始化轻量,并安全释放已分配的资源。 |
| `Caps()` | `std::string Caps()` | 以 JSON 形式返回模块类型和支持的张量维度。 | 准确声明支持的输出张量格式和形状范围。 |
| `Configure()` | `bool Configure(const std::string& labels_file, const std::string& json_settings)` | 接收可选的标签文件和模块特定的 JSON 设置。 | 解析标签和设置,例如置信度阈值、NMS 阈值或关键点配置。 |
| `Process()` | `bool Process(const Tensors& tensors, Dictionary& mlparams, std::any& output)` | 解析推理输出张量,并生成预测结果或张量结果。 | 将原始模型输出转换为 QIM SDK 兼容的元数据、图像掩码或张量。 |

### 使用 Caps() 定义模块能力

`Caps()` 以 JSON 字符串形式返回模块类型和支持的张量形状。张量维度可以是固定值,也可以使用方括号定义范围。例如,`[1, [21, 42840], 4]` 表示第二个维度可以在 `21` 到 `42840` 之间变化。

以下示例声明了目标检测后处理、`FLOAT32` 张量格式,以及对一个、两个或三个张量输出的支持。

```json theme={null}
static const char* kModuleCaps = R"(
{
  "type": "object-detection",
  "tensors": [
    {
      "format": ["FLOAT32"],
      "dimensions": [
        [1, [21, 42840], 4],
        [1, [21, 42840]],
        [1, [21, 42840]]
      ]
    },
    {
      "format": ["FLOAT32"],
      "dimensions": [
        [1, 4, [21, 42840]],
        [1, [1, 1001], [21, 42840]]
      ]
    },
    {
      "format": ["FLOAT32"],
      "dimensions": [
        [1, [5, 1005], [21, 42840]]
      ]
    }
  ]
}
)";
```

您可以同时指定多种张量格式,例如 `"format": ["FLOAT32", "INT8"]`。

| 能力字段 | 允许的值 | 指导 |
| - | - | - |
| 模块类型 | `object-detection`、`image-classification`、`image-segmentation`、`super-resolution`、`pose-estimation`、`audio-classification` 或 `tensor` | 选择与模块返回内容相匹配的输出类型。 |
| 张量格式 | `FLOAT32`、`FLOAT16`、`INT8`、`UINT8`、`INT16`、`UINT16`、`INT32`、`UINT32`、`INT64` 或 `UINT64` | 列出模型输出支持的所有张量格式。 |
| 张量维度 | 固定维度或 `[min, max]` 范围 | 仅在维度确实会变化时才使用范围。明确保留固定维度,以避免匹配到不支持的张量。 |

### 使用 Configure() 配置模块设置

| 参数 | 是否必需? | 描述 |
| - | - | - |
| `labels_file` | 可选 | 标签文件的路径。如果模块不需要标签,则保持为空。 |
| `json_settings` | 可选 | 从插件 `settings` 属性传递的 JSON 设置。可用于置信度阈值、NMS 阈值、关键点详细信息、token 或其他模块特定的值。未提供时保持为空。 |

### 使用 Process() 解析张量

| 参数 | 描述 | 典型用法 |
| - | - | - |
| `tensors` | 推理输出张量及其结构(格式、名称、维度和数据指针)的集合。 | 读取张量格式、维度、名称和数据指针。 |
| `mlparams` | 以键值条目形式提供的附加管线元数据。 | 使用输入张量区域、张量维度或其他上下文,将模型输出映射回源帧。 |
| `output` | 模块写入解析后的预测结果、图像掩码或张量的容器。 | 返回下游 QIM SDK 插件可以使用的结构化结果。 |

<Note>
  张量输出是一种特殊情况,此时插件和模块生成的是张量而非预测结果。仅当两个 ML 模型串联,且第一个模型的输出张量必须经过修改后才能被下一个模型使用时,才使用此方式。如果不需要修改,请直接连接两个推理插件,省略后处理阶段。
</Note>

## 了解后处理模块输入

后处理模块输入分为两个字段。

**`tensors`** 保存推理输出张量并描述其结构。每个输出张量是向量中的一个条目。例如,YOLOv8 产生三个输出张量(边界框、得分、类别索引)。

* **类型:** `float`、`uint8` 等。
* **名称:** 张量名称,当两个或更多输出张量具有相同形状时用于识别。名称是唯一的,可确保选中确切的张量。
* **维度:** 张量形状。例如,具有三个输出张量的 YOLOv8:`[1,8400,4]`、`[1,8400]`、`[1,8400]`。
* **数据:** 指向张量的指针。

**`mlparams`** 为张量处理提供附加参数,这些参数不一定适用于所有模块。它描述管线如何处理输入流,当流的分辨率和宽高比与输入张量形状不匹配时非常有用。它是使用 `std::any` 实现的字典,因此您必须知道预期的键及其返回类型。

```cpp theme={null}
video::Region& region =
  std::any_cast<video::Region&>(mlparams["input-tensor-region"]);
```

**支持的键**

* 键:`"input-tensor-region"`<br />
  类型:`video::Region`<br />
  描述:指示输入张量中哪一部分由来自流的实际数据填充。其余区域视为填充 (padding)。

* 键:`"input-tensor-dimensions"`<br />
  类型:`video::Resolution`<br />
  描述:指定输入张量的大小。由于模块必须输出相对坐标,当算法生成绝对坐标时,需要使用此值将绝对坐标转换为相对坐标。

## 生成后处理模块输出

输出是一个由结果数组组成的数组。数组采用嵌套形式以支持批处理;在没有批处理时仅填充内层数组,其大小与找到的结果数量一致。结果始终使用相对尺寸,结果类型取决于模块类型。

* **图像/音频分类**
  * 名称:预测的类别。
  * 置信度:类别概率或置信度得分。
  * 颜色:用于在叠加插件中可视化的 RGBA8888 颜色。
  * Xtraparams:(可选)用于向下游导出任意结果的额外键值对。

* **目标检测**
  * Left、top、right、bottom:边界框坐标。
  * 名称:预测的类别。
  * 特征点:(可选)关键点列表;例如,人脸检测模型可以同时输出人脸关键点和边界框。
  * 置信度:类别概率或置信度得分。
  * 颜色:用于在叠加插件中可视化的 RGBA8888 颜色。
  * Xtraparams:(可选)用于向下游导出任意结果的额外键值对。

* **姿态估计**
  * 名称:预测的类别。
  * 置信度:类别概率或置信度得分。
  * 关键点:关键点向量。
  * 连接:(可选)关键点之间的连接向量。
  * 颜色:用于在叠加插件中可视化的 RGBA8888 颜色。
  * Xtraparams:(可选)用于向下游导出任意结果的额外键值对。

* **图像分割和超分辨率**
  * 输出为图像帧或掩码。

* **张量**
  * 张量列表。

## 批处理

插件会自动将张量批次拆分为单个张量,因此您无需在模块中处理批处理。例如,如果批大小为 4,则每个批次会自动调用模块四次。

## 模块辅助工具

接口头文件中包含标签解析器和 JSON 解析器。您不必使用它们,但它们为方便起见而提供。您可以使用任何解析器,但模块必须与其静态链接。

* **标签解析器:** 接收标签文件的路径并自动检测格式。
  * 换行分隔格式:行号即类别 ID。
  * JSON 格式:设置类别索引、标签和可视化颜色。此格式更灵活,因为您可以传递类别的子集,其余类别会被自动过滤掉。

* **JSON 解析器:** 解析以 JSON 字符串形式传递的设置。Qualcomm 提供的标签解析器也使用它来解析 JSON 格式的标签。

## 日志记录

模块可以将日志输出到 GStreamer 日志系统,而无需直接依赖 GStreamer。构造函数会向模块传递一个日志对象,您可以将其与 `LOG` 宏一起使用。支持的日志级别有 Error、Warning、Info、Debug、Trace 和 Log。

```c theme={null}
#define LOG(logger, level, fmt, ...)
```

```c theme={null}
LOG(logger_, kError, "ML frame with unsupported postprocessing procedure!");
LOG(logger_, kLog, "Threshold: %f", threshold_);
```

## 在主机上编译后处理模块

**前提条件**

* Ubuntu 22.04 或 Ubuntu 24.04 主机。

<Steps>
  <Step title="安装所需工具">
    ```shell theme={null}
    sudo apt-get install g++-aarch64-linux-gnu
    ```

    ```shell theme={null}
    sudo apt-get install cmake
    ```
  </Step>

  <Step title="下载所需的 .h 和 .cc 文件">
    * [`qti-json-parser.h`](https://github.com/qualcomm/qimsdk/blob/main/gstreamer/gst-plugin-mlpostprocess/modules/qti-json-parser.h)
    * [`qti-labels-parser.h`](https://github.com/qualcomm/qimsdk/blob/main/gstreamer/gst-plugin-mlpostprocess/modules/qti-labels-parser.h)
    * [`qti-ml-post-process.h`](https://github.com/qualcomm/qimsdk/blob/main/gstreamer/gst-plugin-mlpostprocess/modules/qti-ml-post-process.h)
    * [`ml-postprocess-yolov8.h`](https://github.com/qualcomm/qimsdk/blob/main/gstreamer/gst-plugin-mlpostprocess/modules/object-detection/ml-postprocess-yolov8.h)
    * [`ml-postprocess-yolov8.cc`](https://github.com/qualcomm/qimsdk/blob/main/gstreamer/gst-plugin-mlpostprocess/modules/object-detection/ml-postprocess-yolov8.cc)
  </Step>

  <Step title="将 IM SDK 头文件和模块源文件放在同一文件夹中">
    ```text theme={null}
    <root>/
      ml-postprocess-yolov8.cc
      ml-postprocess-yolov8.h
      qti-json-parser.h
      qti-labels-parser.h
      qti-ml-post-process.h
    ```
  </Step>

  <Step title="创建 CMakeLists.txt 文件">
    例如:

    ```cmake theme={null}
    cmake_minimum_required(VERSION 3.8.2)
    project(QTI_OSS_ML_MODULES LANGUAGES C CXX)

    set(CMAKE_INCLUDE_CURRENT_DIR ON)

    # Common compiler flags.
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror")
    set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-parameter")

    include_directories(
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
    )

    set(CMAKE_INCLUDE_CURRENT_DIR ON)

    set(TARGET_NAME ml-postprocess-yolov8)

    add_library(${TARGET_NAME} SHARED
    ml-postprocess-yolov8.cc
    )
    ```

    <Warning>
      后处理模块共享库必须遵循 `libml-postprocess-<module-name>.so` 命名约定。例如,YOLOv8 模块库应命名为 `libml-postprocess-yolov8.so`。
    </Warning>
  </Step>

  <Step title="创建工具链文件">
    例如,`aarch64-toolchain.cmake`:

    ```cmake theme={null}
    set(CMAKE_SYSTEM_NAME Linux)
    set(CMAKE_SYSTEM_PROCESSOR aarch64)
    set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
    set(CMAKE_CXX_FLAGS "-march=armv8-a")
    ```
  </Step>

  <Step title="配置并构建模块">
    ```shell theme={null}
    mkdir build
    ```

    ```shell theme={null}
    cd build
    ```

    ```shell theme={null}
    cmake -DCMAKE_TOOLCHAIN_FILE=../aarch64-toolchain.cmake ..
    ```

    ```shell theme={null}
    cmake --build .
    ```
  </Step>
</Steps>

## 部署和测试后处理模块

<Steps>
  <Step title="在主机上设置用户环境变量">
    ```shell theme={null}
    export USER=root
    ```
  </Step>

  <Step title="将模块部署到目标设备">
    <Steps>
      <Step title="将模块传输到目标设备">
        在主机的终端中执行:

        ```shell theme={null}
        scp libml-postprocess-yolov8.so $USER@<IP address of the target device>:~/
        ```
      </Step>

      <Step title="通过 SSH 登录目标设备">
        在主机的终端中执行:

        ```shell theme={null}
        ssh $USER@<IP address of the target device>
        ```
      </Step>

      <Step title="根据提示输入密码">
        出现提示时,输入密码:`oelinux123`。
      </Step>

      <Step title="以写权限重新挂载 /">
        ```shell theme={null}
        mount -o remount,rw /usr
        ```
      </Step>

      <Step title="将模块复制到 GStreamer 插件目录">
        ```shell theme={null}
        cp ${HOME}/libml-postprocess-yolov8.so /usr/lib/imsdk/qtimlpostprocess/modules/.
        ```
      </Step>

      <Step title="在目标设备上运行 GST inspect">
        ```shell theme={null}
        gst-inspect-1.0 qtimlpostprocess
        ```
      </Step>
    </Steps>
  </Step>

  <Step title="下载运行 GStreamer 管线所需的模型、标签和媒体文件">
    <Steps>
      <Step title="创建工件目录">
        ```shell theme={null}
        ssh root@<IP address of the target device>
        ```

        ```shell theme={null}
        mkdir -p ${HOME}/Downloads/qimsdk_samples/media ${HOME}/Downloads/qimsdk_samples/models ${HOME}/Downloads/qimsdk_samples/labels
        ```
      </Step>

      <Step title="下载标签文件">
        下载 [`yolox.json`](https://github.com/quic/sample-apps-for-qualcomm-linux/blob/main/qualcomm-linux/artifacts/json_labels/yolox.json),然后将其复制到目标设备。

        ```shell theme={null}
        scp yolox.json $USER@<IP address of the target device>:~/Downloads/qimsdk_samples/labels/
        ```
      </Step>

      <Step title="下载媒体文件">
        下载 [`video1.mp4`](https://github.com/quic/sample-apps-for-qualcomm-linux/tree/main/qualcomm-linux/artifacts/videos/video1.mp4),然后将其复制到目标设备。

        ```shell theme={null}
        scp video1.mp4 $USER@<IP address of the target device>:~/Downloads/qimsdk_samples/media/
        ```
      </Step>

      <Step title="下载模型文件">
        下载 [`yolox_quantized.tflite`](https://huggingface.co/qualcomm/Yolo-X/resolve/v0.30.5/Yolo-X_w8a8.tflite),然后将其复制到目标设备。

        ```shell theme={null}
        scp Yolo-X_w8a8.tflite $USER@<IP address of the target device>:~/Downloads/qimsdk_samples/models/yolox_quantized.tflite
        ```
      </Step>
    </Steps>
  </Step>

  <Step title="构建并运行 GStreamer 管线">
    构建并运行一个 GStreamer 管线,通过 `qtimlpostprocess` 的 `module` 属性选择您的模块,并根据需要传递标签文件和设置。

    在以下运行 YOLO-X 模型的示例管线中:

    * 管线使用离线视频作为源。
    * 使用 `v4l2h264dec` 解码器将视频解码为 YUV 格式。
    * `qtimlvconverter` 插件对 YUV 帧进行预处理。
    * `qtimltflite` 插件使用 LiteRT YOLO-X 模型运行推理。
    * 后处理插件加载 YOLO-X 模块并传递 JSON 标签文件。
    * 管线在 Wayland 上显示结果。

    ```shell theme={null}
    gst-launch-1.0 -e \
    filesrc location=${HOME}/Downloads/qimsdk_samples/media/video1.mp4 ! qtdemux ! queue ! h264parse ! v4l2h264dec capture-io-mode=4 output-io-mode=4 ! queue ! tee name=split split. ! \
    queue ! qtivcomposer name=mixer sink_1::dimensions="<1920,1080>" ! queue ! waylandsink fullscreen=true split. ! queue ! qtimlvconverter ! queue ! \
    qtimltflite delegate=external external-delegate-path=libQnnTFLiteDelegate.so external-delegate-options="QNNExternalDelegate,backend_type=htp;" \
    model=${HOME}/Downloads/qimsdk_samples/models/yolox_quantized.tflite ! queue ! qtimlpostprocess settings="{\"confidence\": 75.0}" results=10 module=yolov8 labels=${HOME}/Downloads/qimsdk_samples/labels/yolox.json \
    ! video/x-raw,format=RGBA,width=640,height=360 ! queue ! mixer.
    ```

    <Note>
      对于 `Dragonwing™ IQ-615` 和 `Dragonwing™ IQ-2390`,请将 backend\_type 更改为 `dsp`,因为 hexagon v66 架构不支持 `htp` backend\_type。

      ```shell theme={null}
      gst-launch-1.0 -e \
      filesrc location=${HOME}/Downloads/qimsdk_samples/media/video1.mp4 ! qtdemux ! queue ! h264parse ! v4l2h264dec capture-io-mode=4 output-io-mode=4 ! queue ! tee name=split split. ! \
      queue ! qtivcomposer name=mixer sink_1::dimensions="<1920,1080>" ! queue ! waylandsink fullscreen=true split. ! queue ! qtimlvconverter ! queue ! \
      qtimltflite delegate=external external-delegate-path=libQnnTFLiteDelegate.so external-delegate-options="QNNExternalDelegate,backend_type=dsp;" \
      model=${HOME}/Downloads/qimsdk_samples/models/yolox_quantized.tflite ! queue ! qtimlpostprocess settings="{\"confidence\": 75.0}" results=10 module=yolov8 labels=${HOME}/Downloads/qimsdk_samples/labels/yolox.json \
      ! video/x-raw,format=RGBA,width=640,height=360 ! queue ! mixer.
      ```
    </Note>
  </Step>
</Steps>

## 故障排除

<AccordionGroup>
  <Accordion title="模块未出现在 gst-inspect-1.0 qtimlpostprocess 中" icon="circle-question">
    请按顺序检查以下各项:

    * 库文件名严格遵循 `libml-postprocess-<module-name>.so` 约定。
    * 库已复制到设备上的 `/usr/lib/imsdk/qtimlpostprocess/modules/`。
    * 库是使用工具链文件(而非主机编译器)为 `aarch64` 构建的。
    * 模块导出了 C 兼容的入口点,以便在运行时加载共享库。
  </Accordion>

  <Accordion title="管线协商失败或模块从未被调用" icon="circle-question">
    `Caps()` 声明必须与模型实际生成的张量相匹配。将 `Caps()` 返回的模块类型、张量格式和维度与模型报告的输出张量进行比较,仅在维度确实会变化时才将其放宽为范围。

    同时确认 `module` 属性值与库文件名中的 `<module-name>` 一致。
  </Accordion>

  <Accordion title="结果在帧上的位置不正确" icon="circle-question">
    后处理模块必须输出**相对**坐标。如果您的算法生成的是绝对坐标,请使用 `input-tensor-dimensions` 进行转换,并使用 `input-tensor-region` 考虑填充区域。当宽高比不同时,流通常会以信箱 (letter-box) 方式放入输入张量。
  </Accordion>

  <Accordion title="完全没有生成任何结果" icon="circle-question">
    首先验证后处理之前的各个阶段:确认推理正在生成输出张量,然后检查 `settings` 阈值是否过滤掉了所有结果,以及 `results` 是否设置得低于预期。在 `Process()` 中使用 `LOG` 宏,确认模块正在被调用,并检查其计算出的值。
  </Accordion>
</AccordionGroup>

<Tip>
  要在测试时查看模块日志和管线警告,请在运行管线之前在设备上启用 GStreamer 调试输出:

  ```shell theme={null}
  export GST_DEBUG=2
  ```

  有关更多选项,请参阅[故障排除](../topic/troubleshooting)。
</Tip>

## 后续步骤

您的模型现已完成接入:模块会解析其输出,管线会生成可用的元数据。这与后处理已受支持的模型所达到的状态相同,因此两条路径都会进入同一个最终步骤。

<Card title="步骤 4 · 构建集成自定义 AI 模型的应用程序" icon="hammer" href="../topic/imsdk-build-application">
  选择开发路径,并按照从管线验证到生产应用程序的推荐顺序进行操作。
</Card>

## 相关信息

* [`qtimlpostprocess` 插件参考](https://imsdkdocs.qualcomm.com/plugin-reference/qtimlpostprocess):完整的属性、信号和接口参考。
* [故障排除](../topic/troubleshooting):AI 管线的通用调试指南。
