NVIDIA Holoscan 是 NVIDIA 的多模态实时 AI 传感器处理平台,旨在帮助开发者构建端到端传感器处理流程。该平台的核心组件是 NVIDIA Holoscan SDK,其功能包括:
- 用于低延迟传感器和网络连接的组合硬件系统
- 针对数据处理和 AI 优化的库
- 灵活部署:边缘或云
- Python 和 C++等各种编程语言
Holoscan SDK 可用于为多种行业和应用场景构建流式 AI 流程,包括医疗设备、边缘高性能计算和工业检测等领域。有关更多信息,请参阅 使用 NVIDIA Holoscan 开发生产就绪型 AI 传感器处理应用,以获取详细信息。
Holoscan SDK 通过充分利用软件和硬件来加速流式 AI 应用。它可以与 RDMA 技术 结合,通过 GPU 加速进一步提高端到端流程性能。通常,端到端传感器处理流程包括:
- 传感器数据输入
- 加速计算和 AI 推理
- 实时可视化、驱动和数据流输出
此流程中的所有数据都存储在 GPU 显存中,Holoscan 原生运算符可以直接访问,而无需主机设备内存传输。
本文介绍了如何通过集成 Holoscan SDK 和开源库 OpenCV,在不进行额外显存传输的情况下实现端到端 GPU 加速工作流程。
什么是 OpenCV?
OpenCV(开源计算机视觉库)是一个功能齐全的开源计算机视觉库,包含超过 2500 种算法,涵盖图像和视频操作、物体和面部检测以及 OpenCV 深度学习模块等方面。
OpenCV 支持 GPU 加速,其中包括一个 CUDA 模块,该模块提供了一组类和函数,以便充分利用 CUDA 计算能力。它通过使用 NVIDIA CUDA 运行时 API 实现,并提供实用功能、低级视觉基元和高级算法。
借助 OpenCV 中提供的全面 GPU 加速算法和运算符,开发者可以基于 Holoscan SDK 实施更复杂的流程 (图 2)。
在 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 库一起使用,例如 CuPy、PyTorch、JAX、TensorFlow 以及 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 is not None , "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: assert len (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 is not None , "Unsupported CuPy array dtype" channels = 1 if len (arr.shape) = = 2 else 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 传感器处理社区的中央存储库,提供了丰富的资源和示例代码。