NVIDIA Holoscan SDK v2.9.0

自带模型 (BYOM)

Holoscan 平台针对执行 AI 推理工作流进行了优化。本节介绍用户如何轻松修改 bring_your_own_model 示例来创建自己的 AI 应用程序。

在本示例中,我们将介绍

  • 使用 FormatConverterOpInferenceOpSegmentationPostprocessorOp 算子将 AI 推理添加到工作流中。

  • 如何修改本示例中的现有代码以创建超声分割应用程序,从而可视化脊柱侧弯分割模型的结果。

注意

示例源代码和运行说明可以在 GitHub 上的 examples 目录中找到,或者在 NGC 容器和 Debian 软件包中的 /opt/nvidia/holoscan/examples 下找到,以及它们的可执行文件。

下图显示了 byom.py 示例中使用的算子和工作流。

byom_workflow.png

图 10 BYOM 推理工作流

示例代码已包含创建上述管道所需的管道,其中视频由 VideoStreamReplayer 加载并传递到两个分支。第一个分支直接进入 Holoviz 以显示原始视频。此工作流中的第二个分支通过 AI 推理,可用于生成叠加层,例如边界框、分割掩码或文本,以添加其他信息。

第二个分支有三个我们尚未遇到的算子。

  • 格式转换器:输入视频流经过预处理阶段,将张量转换为适当的形状/格式,然后再馈送到 AI 模型中。此处用于将图像的数据类型从 uint8 转换为 float32,并调整大小以匹配模型的期望。

  • 推理:此算子使用提供的模型对输入视频流执行 AI 推理。它支持对多个输入视频流和模型进行推理。

  • 分割后处理器:此后处理阶段获取推理的输出,可以是最终的 softmax 层(多类)或 sigmoid(2 类),并发出一个包含最高概率类别索引的 uint8 值的张量。分割后处理器的输出随后被馈送到 Holoviz 可视化器以创建叠加层。

要跟随本示例,您可以运行以下命令下载超声数据集

复制
已复制!
            

./scripts/download_ngc_data --url nvidia/clara-holoscan/holoscan_ultrasound_sample_data:20220608

您还可以根据您自己的输入视频和模型调整算子参数,并将您的视频和模型转换为 Holoscan 可以理解的格式,从而使用您自己的数据集进行操作。

输入视频

视频流重放器支持读取编码为 GXF 实体的视频文件。这些文件与超声数据集一起提供,作为 ultrasound_256x256.gxf_entitiesultrasound_256x256.gxf_index 文件。

注意

要使用您自己的视频数据,您可以使用 convert_video_to_gxf_entities.py 脚本(安装在 /opt/nvidia/holoscan/bin 中或 在 GitHub 上)来编码您的视频。请注意 - 使用此脚本 - 生成的 GXF 张量文件中的元数据将指示应在读取时将数据复制到 GPU。

输入模型

目前,Holoscan 中的推理算子能够加载 ONNX 模型,或为将运行模型的 GPU 架构构建的 TensorRT 引擎文件。TensorRT 引擎在应用程序运行时从 ONNX 自动生成。

如果您要将模型从 PyTorch 转换为 ONNX,则您的输入很可能是 NCHW 格式,并且需要转换为 NHWC 格式。我们提供了一个名为 graph_surgeon.py 的示例转换脚本,安装在 /opt/nvidia/holoscan/bin 中或在 GitHub 上 提供。您可能需要根据需要修改尺寸,然后再修改模型。

提示

为了更好地理解您的模型,以及此步骤是否必要,可以使用 netron.app 等网站。

在修改应用程序之前,让我们看一下现有代码,以更好地了解其工作原理。

复制
已复制!
            

import os from argparse import ArgumentParser from holoscan.core import Application from holoscan.operators import ( FormatConverterOp, HolovizOp, InferenceOp, SegmentationPostprocessorOp, VideoStreamReplayerOp, ) from holoscan.resources import UnboundedAllocator class BYOMApp(Application): def __init__(self, data): """Initialize the application Parameters ---------- data : Location to the data """ super().__init__() # set name self.name = "BYOM App" if data == "none": data = os.environ.get("HOLOSCAN_INPUT_PATH", "../data") self.sample_data_path = data self.model_path = os.path.join(os.path.dirname(__file__), "../model") self.model_path_map = { "byom_model": os.path.join(self.model_path, "identity_model.onnx"), } self.video_dir = os.path.join(self.sample_data_path, "racerx") if not os.path.exists(self.video_dir): raise ValueError(f"Could not find video data:{self.video_dir=}")

  • 内置的 FormatConvertOpInferenceOpSegmentationPostprocessorOp 算子在第 7910 行导入。这 3 个算子分别构成了我们 AI 管道的预处理、推理和后处理阶段。

  • UnboundedAllocator 资源在第 13 行导入。我们的应用程序的算子使用它进行内存分配。

  • identity 模型的路径在第 35-38 行定义。此模型将其输入张量传递给其输出,并充当本示例的占位符。

  • 视频文件的目录在第 40 行定义。

接下来,我们看一下应用程序 YAML 文件中定义的算子及其参数。

复制
已复制!
            

def compose(self): host_allocator = UnboundedAllocator(self, name="host_allocator") source = VideoStreamReplayerOp( self, name="replayer", directory=self.video_dir, **self.kwargs("replayer") ) preprocessor = FormatConverterOp( self, name="preprocessor", pool=host_allocator, **self.kwargs("preprocessor") ) inference = InferenceOp( self, name="inference", allocator=host_allocator, model_path_map=self.model_path_map, **self.kwargs("inference"), ) postprocessor = SegmentationPostprocessorOp( self, name="postprocessor", allocator=host_allocator, **self.kwargs("postprocessor") ) viz = HolovizOp(self, name="viz", **self.kwargs("viz"))

  • UnboundedAllocator 资源类的实例被创建(第 2 行),并由后续算子用于内存分配。此分配器根据需要在主机上动态分配内存。对于延迟成为问题的应用程序,可以使用支持内存池的分配器,例如 BlockMemoryPoolRMMAllocator

  • 预处理器算子(第 8 行)负责将来自源视频的输入视频转换为 AI 模型可以使用的格式。

  • 推理算子(第 12 行)将来自预处理器的输出馈送到 AI 模型以执行推理。

  • 后处理器算子(第 20 行)对来自推理算子的输出进行后处理,然后将其向下游传递到可视化器。在这里,分割后处理器检查模型输出的概率,以确定哪个类别最有可能,并发出此类别索引。然后,Holoviz 算子使用它来创建分割掩码叠加层。

复制
已复制!
            

%YAML 1.2 replayer: # VideoStreamReplayer basename: "racerx" frame_rate: 0 # as specified in timestamps repeat: true # default: false realtime: true # default: true count: 0 # default: 0 (no frame count restriction) preprocessor: # FormatConverter out_tensor_name: source_video out_dtype: "float32" resize_width: 512 resize_height: 512 inference: # Inference backend: "trt" pre_processor_map: "byom_model": ["source_video"] inference_map: "byom_model": ["output"] postprocessor: # SegmentationPostprocessor in_tensor_name: output # network_output_type: None data_format: nchw viz: # Holoviz width: 854 height: 480 color_lut: [ [0.65, 0.81, 0.89, 0.1], ]

  • 预处理器将张量转换为 float32 值(第 11 行),并确保图像调整大小为 512x512(第 12-13 行)。

  • pre_processor_map 参数(第 17-18 行)将模型名称映射到输入张量名称。此处,“source_video”与预处理器的输出张量名称匹配(第 10 行)。inference_map 参数将模型名称映射到输出张量名称。此处,“output”与后处理器的输入张量名称匹配(第 23 行)。有关 InferenceOp 参数的更多详细信息,请参阅 自定义推理算子 或参考 推理

  • network_output_type 参数在第 24 行被注释掉,以提醒我们自己,第二个分支目前没有生成任何有趣的东西。如果未指定,则此参数对于 SegmentationPostprocessorOp 默认为 “softmax”。

  • 在第 30-32 行定义的颜色查找表在此处用于创建分割掩码叠加层。表中的每个条目的值都是介于 0.0 和 1.0 之间的 RGBA 值。对于 alpha 值,0.0 是完全透明,1.0 是完全不透明。

最后,我们定义应用程序和工作流。

复制
已复制!
            

# Define the workflow self.add_flow(source, viz, {("output", "receivers")}) self.add_flow(source, preprocessor, {("output", "source_video")}) self.add_flow(preprocessor, inference, {("tensor", "receivers")}) self.add_flow(inference, postprocessor, {("transmitter", "in_tensor")}) self.add_flow(postprocessor, viz, {("out_tensor", "receivers")}) def main(config_file, data): app = BYOMApp(data=data) # if the --config command line argument was provided, it will override this config_file app.config(config_file) app.run() if __name__ == "__main__": # Parse args parser = ArgumentParser(description="BYOM demo application.") parser.add_argument( "-d", "--data", default="none", help=("Set the data path"), ) args = parser.parse_args() config_file = os.path.join(os.path.dirname(__file__), "byom.yaml") main(config_file=config_file, data=args.data)

  • 2 行的 add_flow() 定义了第一个分支以显示原始视频。

  • 3-6 行的 add_flow() 命令定义了第二个分支以显示分割掩码叠加层。

要创建超声分割应用程序,我们需要更换输入视频和模型以使用超声文件,并调整参数以确保输入视频正确调整大小以符合模型的期望。

我们将需要修改 Python 和 YAML 文件,以将我们的应用程序更改为超声分割应用程序。

复制
已复制!
            

class BYOMApp(Application): def __init__(self, data): """Initialize the application Parameters ---------- data : Location to the data """ super().__init__() # set name self.name = "BYOM App" if data == "none": data = os.environ.get("HOLOSCAN_INPUT_PATH", "../data") self.sample_data_path = data self.model_path = os.path.join(self.sample_data_path, "ultrasound_segmentation", "model") self.model_path_map = { "byom_model": os.path.join(self.model_path, "us_unet_256x256_nhwc.onnx"), } self.video_dir = os.path.join(self.sample_data_path, "ultrasound_segmentation", "video") if not os.path.exists(self.video_dir): raise ValueError(f"Could not find video data:{self.video_dir=}")

  • 更新 self.model_path_map 以指向超声分割模型(第 20-23 行)。

  • 更新 self.video_dir 以指向超声视频文件的目录(第 25 行)。

复制
已复制!
            

replayer: # VideoStreamReplayer basename: "ultrasound_256x256" frame_rate: 0 # as specified in timestamps repeat: true # default: false realtime: true # default: true count: 0 # default: 0 (no frame count restriction) preprocessor: # FormatConverter out_tensor_name: source_video out_dtype: "float32" resize_width: 256 resize_height: 256 inference: # Inference backend: "trt" pre_processor_map: "byom_model": ["source_video"] inference_map: "byom_model": ["output"] postprocessor: # SegmentationPostprocessor in_tensor_name: output network_output_type: softmax data_format: nchw viz: # Holoviz width: 854 height: 480 color_lut: [ [0.65, 0.81, 0.89, 0.1], [0.2, 0.63, 0.17, 0.7] ]

  • basename 更新为超声视频文件的基本名称(第 2 行)。

  • AI 模型期望图像的宽度和高度为 256x256,更新预处理器的参数以将输入调整大小为 256x256(第 11-12 行)。

  • AI 模型的最终输出层是 softmax,因此我们将其指示给后处理器(第 23 行)。

  • 由于此模型在两个类别之间进行预测,因此我们需要在 Holoviz 的颜色查找表中添加另一个条目(第 31 行)。请注意,第一个颜色条目的 alpha 值为 0.1(第 30 行),因此背景类别的掩码可能不可见。我们刚刚添加的第二个条目是绿色,alpha 值为 0.7,因此它将很容易看到。

以上更改足以将 BYOM 示例更新为超声分割应用程序。

一般来说,在部署您自己的 AI 模型时,您需要考虑第二个分支中的算子。本示例使用了一个非常典型的 AI 工作流

  • 输入:这可以是磁盘上的视频、来自捕获设备的输入流或其他数据流。

  • 预处理:您可能需要预处理输入流,以将张量转换为 AI 模型期望的形状和格式(例如,转换数据类型和调整大小)。

  • 推理:您的模型需要采用 ONNX 或 TensorRT 格式。

  • 后处理:一个后处理模型输出的算子,使其成为可以被下游算子轻松使用的格式。

  • 输出:后处理后的流可以显示或被其他下游算子使用。

Holoscan SDK 附带了许多 内置算子,您可以使用它们来配置自己的工作流。如果需要,您可以编写自己的自定义算子,或访问 Holohub 以获取其他实现和算子的想法。

按照上述指示修改应用程序后,运行该应用程序应显示带有分割掩码叠加层的超声视频,类似于下图。

app_ultrasound.png

图 11 超声分割

注意

如果您在未修改的情况下运行 byom.py 应用程序,并且正在使用 debian 安装,您可能会遇到以下错误消息

复制
已复制!
            

[error] Error in Inference Manager ... TRT Inference: failed to build TRT engine file.

在这种情况下,修改模型目录的写入权限应该会有所帮助(谨慎使用)

复制
已复制!
            

sudo chmod a+w /opt/nvidia/holoscan/examples/bring_your_own_model/model

内置的 InferenceOp 算子提供了 推理 的功能。此算子有一个 receivers 端口,可以连接到任意数量的上游端口以实现多 AI 推理,以及一个 transmitter 端口以将结果发送到下游。以下是算子的一些参数的描述以及如何使用它们的一般指南。

  • backend:如果输入模型为 tensorrt engine file 格式,请选择 trt 作为后端。如果输入模型为 onnx 格式,请选择 trtonnx 作为后端。

  • allocator:可以传递给此算子以指定如何分配输出张量。

  • model_path_map:包含字典键,其中包含引用每个模型的唯一字符串。这些值设置为磁盘上模型文件的路径。所有模型都必须是 onnxtensorrt engine file 格式。如果 TensorRT 引擎文件不存在,Holoscan 推理模块将执行 onnxtensorrt 模型转换。

  • pre_processor_map:此字典应包含与 model_path_map 相同的键,映射到每个模型的输出张量名称。

  • inference_map:此字典应包含与 model_path_map 相同的键,映射到每个模型的输出张量名称。

  • enable_fp16:布尔变量,指示是否应使用半精度来加速推理。默认值为 False,并使用单精度(32 位 fp)值。

  • input_on_cuda:指示输入张量是在设备上还是在主机上。

  • output_on_cuda:指示输出张量是在设备上还是在主机上。

  • transmit_on_cuda:如果为 True,则表示来自推理的数据传输将在设备上进行,否则表示来自推理的数据传输将在主机上进行。

颜色通道顺序

了解您的模型期望的通道顺序非常重要。这可能由训练数据、训练时执行的预训练转换或应用程序中使用的预期推理格式指示。

例如,如果您的推理数据是 RGB,但您的模型期望 BGR,您将需要在 yaml 文件中的 segmentation_preprocessor 中添加以下内容:out_channel_order: [2,1,0]

规范化您的数据

同样,流式数据的默认缩放比例为 [0,1],但取决于您的模型是如何训练的,您可能期望 [0,255]

对于上述情况,您将在 YAML 文件中的 segmentation_preprocessor 中添加以下内容

scale_min: 0.0 scale_max: 255.0

网络输出类型

模型通常具有不同的输出类型,例如 SigmoidSoftmax 或其他类型,您可能需要检查模型的最后几层以确定哪种类型适用于您的情况。

正如我们上面的超声分割示例一样,我们在 YAML 文件中添加了以下内容:network_output_type: softmax

上一篇 视频重放器(分布式)
下一篇 创建应用程序
© 版权所有 2022-2024 NVIDIA。 最后更新于 2025 年 1 月 27 日。