计算机视觉/视频分析

在 NVIDIA Holoscan SDK 中 构建集成 OpenCV 的零拷贝 AI 传感器处理流程

NVIDIA Holoscan 是 NVIDIA 的多模态实时 AI 传感器处理平台,旨在帮助开发者构建端到端传感器处理流程。该平台的核心组件是 NVIDIA Holoscan SDK,其功能包括:

  • 用于低延迟传感器和网络连接的组合硬件系统
  • 针对数据处理和 AI 优化的库
  • 灵活部署:边缘或云
  • Python 和 C++等各种编程语言

Holoscan SDK 可用于为多种行业和应用场景构建流式 AI 流程,包括医疗设备、边缘高性能计算和工业检测等领域。有关更多信息,请参阅 使用 NVIDIA Holoscan 开发生产就绪型 AI 传感器处理应用,以获取详细信息。

Holoscan SDK 通过充分利用软件和硬件来加速流式 AI 应用。它可以与 RDMA 技术 结合,通过 GPU 加速进一步提高端到端流程性能。通常,端到端传感器处理流程包括:

  • 传感器数据输入
  • 加速计算和 AI 推理
  • 实时可视化、驱动和数据流输出

此流程中的所有数据都存储在 GPU 显存中,Holoscan 原生运算符可以直接访问,而无需主机设备内存传输。

The workflow shows a typical pipeline for Ultrasound Segmentation application, including input source with VideoStreamReplayOp -> Format Conversion from uint8 to float32, and resize the image through FormatConverterOp -> InferenceOp -> Post processing on inference results through SegmentationPostprocessorOP -> Visualization through HolovizIo.
图 1.超声波分割应用的典型流程

本文介绍了如何通过集成 Holoscan SDK 和开源库 OpenCV,在不进行额外显存传输的情况下实现端到端 GPU 加速工作流程。

什么是 OpenCV?

OpenCV(开源计算机视觉库)是一个功能齐全的开源计算机视觉库,包含超过 2500 种算法,涵盖图像和视频操作、物体和面部检测以及 OpenCV 深度学习模块等方面。

OpenCV 支持 GPU 加速,其中包括一个 CUDA 模块,该模块提供了一组类和函数,以便充分利用 CUDA 计算能力。它通过使用 NVIDIA CUDA 运行时 API 实现,并提供实用功能、低级视觉基元和高级算法。

借助 OpenCV 中提供的全面 GPU 加速算法和运算符,开发者可以基于 Holoscan SDK 实施更复杂的流程 (图 2)。

The workflow shows an enhanced pipeline for ultrasound segmentation application integrating with OpenCV and Holoscan SDK. Including input source with VideoStreamReplayOp -> Format Conversion from uint8 to float32, and resize the image through FormatConverterOp -> Pre processing with OpenCV -> InferenceOp -> Post processing on inference results through SegmentationPostprocessorOP -> Visualization through HolovizOp.
图 2. 基于 OpenCV 和 Holoscan SDK 的增强型超声分割流程

在 Holoscan SDK 管道中集成 OpenCV 运算符 

要开始在 Holoscan SDK 管道中集成 OpenCV 运算符,您需要以下内容:

  • OpenCV > = 4.8.0
  • Holoscan SDK > = v0.6

请按照以下指南操作:opencv/opencv_contrib,安装带有 CUDA 模块 OpenCV。如果您想使用 Holoscan SDK 和 OpenCV CUDA 构建镜像,请参阅 NVIDIA Holoscan/holohub 的 Dockerfile

作为 Holoscan SDK 中的数据类型,Tensor 被定义为由单一数据类型的元素组成的多维数组。Tensor 类是 Holoscan SDK 中的数据类型的包装器,DLManagedTensorCtx 保存 DLManagedTensor 对象的结构体。Tensor 类支持 DLPack 和 NumPy 数组接口 (__array_interface__ __cuda_array_interface__),因此它可以与其他 Python 库一起使用,例如 CuPyPyTorchJAXTensorFlow 以及 Numba

然而,OpenCV 的数据类型是 GPUMat,它没有实现 __cuda_array_interface__,因此无法实现端到端 GPU 加速的工作流或应用程序。为此,需要实现两个函数,将 GpuMat 与 CuPy 数组进行相互转换,以便直接访问及使用 Holoscan Tensor。

 

从 GpuMat 到 CuPy 数组的无缝零拷贝 

OpenCV Python 绑定的 GpuMat 对象提供了一个cudaPtr方法,用于访问 GpuMat 对象的 GPU 显存地址。此内存指针可以用于直接初始化 CuPy 数组,从而避免主机和设备之间不必要的数据传输,实现高效的数据处理。

以下函数用于从 GpuMat 创建 CuPy 数组。该函数的源代码位于 HoloHub 的内窥镜深度估算应用程序中。

import cv2
import cupy as cp
  
def gpumat_to_cupy(gpu_mat: cv2.cuda.GpuMat) -> cp.ndarray:
    w, h = gpu_mat.size()
    size_in_bytes = gpu_mat.step * w
    shapes = (h, w, gpu_mat.channels())
    assert gpu_mat.channels() <=3, "Unsupported GpuMat channels"
  
    dtype =None
    if gpu_mat.type() in [cv2.CV_8U,cv2.CV_8UC1,cv2.CV_8UC2,cv2.CV_8UC3]:
        dtype = cp.uint8
    elif gpu_mat.type() == cv2.CV_8S:
        dtype = cp.int8
    elif gpu_mat.type() == cv2.CV_16U:
        dtype = cp.uint16
    elif gpu_mat.type() == cv2.CV_16S:
        dtype = cp.int16
    elif gpu_mat.type() == cv2.CV_32S:
        dtype = cp.int32
    elif gpu_mat.type() == cv2.CV_32F:
        dtype = cp.float32
    elif gpu_mat.type() == cv2.CV_64F:
        dtype = cp.float64 
  
assert dtype isnotNone, "Unsupported GpuMat type"
    
    mem = cp.cuda.UnownedMemory(gpu_mat.cudaPtr(), size_in_bytes, owner=gpu_mat)
    memptr = cp.cuda.MemoryPointer(mem, offset=0)
    cp_out = cp.ndarray(
        shapes,
        dtype=dtype,
        memptr=memptr,
        strides=(gpu_mat.step, gpu_mat.elemSize(), gpu_mat.elemSize1()),
    )
    return cp_out

请注意,我们在此函数中使用了 非自有内存 创建 CuPy 数组的 API。在某些情况下,OpenCV 运算符将创建需要由 CuPy 处理的新设备内存,其生命周期不限于一个运算符,而是整个工作流。在这种情况下,从 GpuMat 启动的 CuPy 数组知道所有者并保留对对象的引用。欲了解更多详细信息,请参阅 CuPy 互操作性文档

从 Holoscan Tensor 到 GpuMat 的无缝零拷贝 

随着 OpenCV 4.8 的发布,OpenCV 的 Python 绑定现在支持直接从 GPU 显存指针初始化 GpuMat 对象。此功能通过实现与 GPU 驻留数据的直接交互来提高数据处理和处理效率,从而无需在主机和设备显存之间传输数据。

在基于 Holoscan SDK 的工作流应用程序中,GPU 显存指针可以通过由 CuPy 数组提供的 _cuda_array_interface_ 获得。以下列出了用于利用 CuPy 数组创建 GpuMat 对象的函数。有关详细实现,请参阅提供的源代码 HoloHub 内窥镜深度估计应用程序

import cv2
import cupy as cp
  
def gpumat_from_cp_array(arr: cp.ndarray) -> cv2.cuda.GpuMat:
    assertlen(arr.shape) in (2, 3), "CuPy array must have 2 or 3 dimensions to be a valid GpuMat"
    type_map = {
        cp.dtype('uint8'): cv2.CV_8U,
        cp.dtype('int8'): cv2.CV_8S,
        cp.dtype('uint16'): cv2.CV_16U,
        cp.dtype('int16'): cv2.CV_16S,
        cp.dtype('int32'): cv2.CV_32S,
        cp.dtype('float32'): cv2.CV_32F,
        cp.dtype('float64'): cv2.CV_64F
    }
    depth = type_map.get(arr.dtype)
    assert depth isnotNone, "Unsupported CuPy array dtype"
    channels =1iflen(arr.shape) ==2else arr.shape[2]
    mat_type = depth + ((channels -1) << 3)
    
    mat = cv2.cuda.createGpuMatFromCudaMemory(
    arr.__cuda_array_interface__['shape'][1::-1], 
    mat_type, 
    arr.__cuda_array_interface__['data'][0]
  )
    return mat

集成 OpenCV 运算符 

借助前面的两个函数,您可以在基于 Holoscan SDK 的工作流中使用 OpenCV-CUDA 操作,从而避免内存传输。以下是实现步骤:

  • 创建调用 OpenCV Operator 的自定义操作符。有关详细信息,请参阅 Holoscan SDK 用户指南文档
  • 在 Operator 中的计算函数中:
    • 从前一个运算符接收消息,并从 Holoscan Tensor 创建 CuPy 数组。
    • 使用 gpumat_from_cp_array 函数创建 GpuMat。
    • 使用自定义的 OpenCV Operator 进行处理。
    • 使用 gpumat_to_cupy 函数从 GpuMat 创建 CuPy 数组。

请参阅下方的演示代码。有关完整的源代码,请参阅 HoloHub 内窥镜深度估计应用程序的源代码

def compute(self, op_input, op_output, context):
        stream = cv2.cuda_Stream()
        message = op_input.receive("in")
  
        cp_frame = cp.asarray(message.get(""))  # CuPy array
        cv_frame = gpumat_from_cp_array(cp_frame)  # GPU OpenCV mat
  
        ## Call OpenCV Operator 
        cv_frame = cv2.cuda.XXX(hsv_merge, cv2.COLOR_HSV2RGB)
  
        cp_frame = gpumat_to_cupy(cv_frame)
        cp_frame = cp.ascontiguousarray(cp_frame)
  
        out_message = Entity(context)
        out_message.add(hs.as_tensor(cp_frame), "")
        op_output.emit(out_message, "out")

总结 

将 OpenCV CUDA 操作符集成到基于 Holoscan SDK 构建的应用程序中,只需实现两个函数,实现 OpenCV 的 GpuMat 和 CuPy 数组的转换。

这些功能允许在自定义运算符中直接访问 Holoscan Tensors.通过调用这些功能,您可以无缝创建端到端 GPU 加速应用程序,而无需内存传输,从而提高性能。

首先,下载 Holoscan SDK 2.0 并查看 版本说明。如果您有任何问题或想分享信息,请访问 NVIDIA 开发者论坛

欲了解如何将其他外部库集成到 NVIDIA Holoscan SDK 管线中,请参阅 HoloHub 教程。您还可以从示例代码和应用入手,nvidia-holoscan/holohub 是 NVIDIA Holoscan AI 传感器处理社区的中央存储库,提供了丰富的资源和示例代码。

 

Tags