NPPI 颜色空间转换、尺寸变换

1、NPP介绍

        NPP(NVIDIA Performance Primitives)是专为 CUDA 架构设计的图像和信号处理 GPU 加速库。库涵盖了丰富的函数集,包括滤波、边缘检测、颜色转换等,利用 GPU 的并行计算能力,提高图像处理应用的执行速度。通过NPP 库,能够轻松地在支持 CUDA 的 NVIDIA GPU 上实现高性能的图像和信号处理任务,从而为应用程序提供更快速和高效的数据处理能力。 

        在 NVIDIA Performance Primitives (NPP) 库中,分NPPC、NPPI 和 NPPS 三个不同的命名空间,用于表示不同的功能和功能类别。

  1. NPPC (NVIDIA Performance Primitives Core): NPPC 包含了 NPP 库的核心功能,提供了一些基本的操作和功能,如数据类型定义、常量定义等。这个命名空间通常用于包含 NPP 库中的核心组件。

  2. NPPI (NVIDIA Performance Primitives Imaging): NPPI 包含了与图像处理相关的功能。这个命名空间提供了用于执行各种图像处理任务的函数,如滤波、颜色空间转换、图像金字塔等。

  3. NPPS (NVIDIA Performance Primitives Signal): NPPS 包含了与信号处理相关的功能。这个命名空间提供了用于处理信号和时间序列数据的函数,包括傅里叶变换、卷积、滤波器设计等。

        头文件在/usr/local/cuda/include/下:

ea1f62b6fa5f4c1283b06d364d9a0e9a.png

        nppi_*.h是NPPI的头文件;npps_*.h是npps的头文件;nppcore.h和nppdefs.h是NPPC的头文件

        nppi.h包含了NPPI的所有头文件;npps.h包含了NPPS的所有头文件;nnp.h包含了nppi.h、npps.h、nppcore.h、nppdefs.h,即包含了NPP的所有头文件,所以写代码的时候引入npp.h就可以了。

        库文件在/usr/local/cuda/lib64下:

b0acd2ce09bd48579118d5e750686628.png

        NPP有自己的内存和流管理API,但NPP就是使用CUDA实现的,所以NPP 使用的显存和 CUDA API 分配的显存通常是可以互相访问的。

        NPP的内存分配 API
        nppsMalloc_ 和 nppsFree_ 等函数: NPP 提供了一组内存分配和释放函数,以便在进行图像和信号处理任务时在 GPU 上分配和释放内存。这些函数的命名类似于 nppsMalloc_ 和 nppsFree_。
        CUDA 的内存分配 API:
        cudaMalloc 和 cudaFree 等函数: CUDA 提供了一组底层的 GPU 内存分配和释放函数,如 cudaMalloc 和 cudaFree。        
        联系:
        互通性: NPP 和 CUDA 的内存分配 API 通常是可以互通的。这意味着可以在 NPP 上下文中使用 CUDA API 分配的内存,反之亦然。这样可以使得开发者在不同的 GPU 加速任务中更加灵活。
        相似性: NPP 的内存分配 API 在命名和概念上与 CUDA 的 API 相似,这使得熟悉 CUDA 的开发者可以相对容易地理解和使用 NPP 的内存管理功能。

        需要注意的是,虽然它们具有互通性,但在某些情况下可能存在一些细微差异,因此在实际使用中最好遵循相应的文档和最佳实践,以确保正确和高效地管理 GPU 内存。

2、NPPI 颜色转换和尺寸缩放API:

        首先介绍一下图像像素排列方式,图像排列方式分为packed(打包格式)和planar(平面格式)两种模式,表示像素在内存中的排列方式,以RGB为例:

        packed模式:

        [R0, G0, B0] [R1, G1, B1] [R2, G2, B2] [R3, G3, B3]
        planar模式:

        [R0, R1, R2, R3] [G0, G1, G2, G3] [B0, B1, B2, B3]

2.1 颜色空间转换API:

        颜色空间转换的API在nppi_color_conversion.h中,API名称规则:

        nppi<源色彩空间>To<目标色彩空间>_<单通道位深>_<图像像素排列方式>

例如:

// pSrc:指向源图像数据的指针,这是一个 3 通道 8 位无符号整数(8u)的打包格式 YUV 图像。
// nSrcStep:源图像中相邻行之间的字节步幅。通常,它等于每行像素的字节数。该值通常为图像宽度乘以每个像素的字节数。
// pDst:指向目标图像数据的指针,这是一个 3 通道 8 位无符号整数(8u)的打包格式 RGB 图像。
// nDstStep:目标图像中相邻行之间的字节步幅。通常,它等于每行像素的字节数。该值通常为图像宽度乘以每个像素的字节数。
// oSizeROI:一个结构,包含了要处理的图像的 ROI(Region of Interest)大小。oSizeROI.width 表示宽度,oSizeROI.height 表示高度。
// nppStreamCtx:应用程序管理的流上下文,允许调用者指定用于执行函数的特定流。这允许异步执行和与其他 CUDA 操作的协同执行。
// 返回值:函数的执行状态。可能的返回值包括 NPP_SUCCESS(成功),以及各种错误码,表示参数错误、内存分配错误等。
NppStatus nppiYUVToRGB_8u_C3R_Ctx(const Npp8u * pSrc, int nSrcStep, Npp8u * pDst, int nDstStep, NppiSize oSizeROI, NppStreamContext nppStreamCtx);

        表示3通道8位无符号packed模式YUV到3通道8位无符号packed模式RGB颜色转换,_Ctx表示要使用NppStreamContext,即使用cuda流加速

        图像像素排列方式主要有以下几种:

        1、C3R:3通道packed模式+ROI

        2、AC4R:4通道packed模式+ROI

        3、P3R:3通道planar模式+ROI

        4、C3P3R:3通道planar模式+ROI

        5、AC4P4R:4通道planar模式+ROI

        A:图像是4通道图像,表示结果alpha通道不受图元影响。
        Cn:图像通道数,n可以是1、2、3或4。如RGB三个通道, RGBA四个通道;
        Pn:此时是planar模式,n表示包含几个平面,比如RRRGGGBBB,则n=3
        R: 只在矩形的“感兴趣区域”或“ROI”上操作。所有的ROI都有一个NppiSize类型的参数

2.2 尺寸转换API:

        命名规则和颜色转换API类似,例如:

// pSrc:指向源图像数据的指针,这是一个 3 通道 8 位无符号整数(8u)的打包格式 RGB 图像。
// nSrcStep:源图像中相邻行之间的字节步幅。通常,它等于每行像素的字节数。该值通常为图像宽度乘以每个像素的字节数。
// oSrcSize:源图像的大小,包含宽度和高度信息。oSrcSize.width 表示宽度,oSrcSize.height 表示高度。
// oSrcRectROI:源图像的感兴趣区域(Region of Interest),用于指定要在源图像上执行操作的区域。
// pDst:指向目标图像数据的指针,这是一个 3 通道 8 位无符号整数(8u)的打包格式 RGB 图像。
// nDstStep:目标图像中相邻行之间的字节步幅。通常,它等于每行像素的字节数。该值通常为图像宽度乘以每个像素的字节数。
// oDstSize:目标图像的大小,包含宽度和高度信息。oDstSize.width 表示宽度,oDstSize.height 表示高度。
// oDstRectROI:目标图像的感兴趣区域(Region of Interest),用于指定要在目标图像上写入数据的区域。
// eInterpolation:插值方法的枚举值,指定在图像缩放时如何进行插值。具体的插值方法可能包括 NPP_INTER_LINEAR、NPP_INTER_CUBIC 等。
// nppStreamCtx:应用程序管理的流上下文,允许调用者指定用于执行函数的特定流。这允许异步执行和与其他 CUDA 操作的协同执行。
// 返回值:函数的执行状态。可能的返回值包括 NPP_SUCCESS(成功),以及各种错误码,表示参数错误、内存分配错误等。
NppStatus
nppiResize_8u_C3R_Ctx(const Npp8u * pSrc, int nSrcStep, NppiSize oSrcSize, NppiRect oSrcRectROI,
                            Npp8u * pDst, int nDstStep, NppiSize oDstSize, NppiRect oDstRectROI, int eInterpolation, NppStreamContext nppStreamCtx);

3、完整代码:

#include <npp.h>
#include <opencv2/opencv.hpp>
// g++ -o YUV2RGB YUV2RGB.cpp `pkg-config --cflags --libs opencv4` -I/usr/local/cuda/include -L/usr/local/cuda/lib64 -lcudart  -lnppicc -lnppig
int main(int argc, char **argv)
{
    if (argc < 2) {
        printf("./bin image\n");
        return 0;
    }
    // opencv转yuv420 有问题 转出来的图像是单通道的,高度是原始图像的1.5倍,这里使用yuv444格式进行测试,如需使用yuv420可以使用其他库,如ffmpeg
    // 读取 BGR 图像并转换为 YUV444 格式
    cv::Mat mat_bgr_img = cv::imread(argv[1]);
    cv::Mat mat_yuv_img;
    cv::cvtColor(mat_bgr_img, mat_yuv_img, cv::COLOR_BGR2YUV); // packed YUV
    cv::imwrite("./yuv444.jpg", mat_yuv_img);

    int width = mat_yuv_img.cols;
    int height = mat_yuv_img.rows;
    int step = mat_yuv_img.step;
    printf("step : %d\nwidth : %d\nheight : %d\nwidth * 3 : %d\n", step, width, height, width * 3);
    if (step != width * 3) {
        printf("step != width * 3\n");
    }
    /*YUV->RGB*/
    // YUV
    Npp8u *pu8_yuv_dev = nullptr;
    cudaMalloc((void **)&pu8_yuv_dev, step * height);
    cudaMemcpy(pu8_yuv_dev, mat_yuv_img.data, step * height, cudaMemcpyHostToDevice);
    // RGB
    Npp8u *pu8_rgb_dev = nullptr;
    cudaMalloc((void **)&pu8_rgb_dev, width * height * 3);
    // 输入:packed YUV  输出:packed RGB
    NppStatus npp_ret = nppiYUVToRGB_8u_C3R(pu8_yuv_dev, step, pu8_rgb_dev, width * 3, {width, height});
    printf("npp_ret = %d \n", npp_ret);

    //write to file
    unsigned char *pu8_rgb_host = (unsigned char *)malloc(width * height * 3);
    memset(pu8_rgb_host, 0, width * height * 3);
    cudaMemcpy(pu8_rgb_host, pu8_rgb_dev, width * height * 3, cudaMemcpyDeviceToHost);

    FILE *file = fopen("RGB.raw", "wb");
    if (file == NULL) {
        fprintf(stderr, "Unable to open the file.\n");
        return 1;
    }
    fwrite(pu8_rgb_host, 1, width * height * 3, file);
    fclose(file);

    cv::Mat newimage(height, width, CV_8UC3);
    memcpy(newimage.data, pu8_rgb_host, width * height * 3);
    cv::cvtColor(newimage, newimage, cv::COLOR_RGB2BGR); // opencv默认使用的是BGR
    cv::imwrite("./yuv2BGR.jpg", newimage);
    /*resize*/ 
    Npp8u *pu8_src_data_dev = pu8_rgb_dev;

    Npp8u *pu8_dst_data_dev = NULL;
    int resize_width = width / 2, resize_height = height / 2;
    NppiSize npp_src_size{width, height};
    NppiSize npp_dst_size{resize_width, resize_height};
    cudaMalloc((void **)&pu8_dst_data_dev, resize_width * resize_height * 3 * sizeof(Npp8u));
    cudaMemset(pu8_dst_data_dev, 0, resize_width * resize_height * 3 * sizeof(Npp8u));
    nppiResize_8u_C3R((Npp8u *)pu8_src_data_dev, width * 3, npp_src_size, NppiRect{0, 0, width, height},
                      (Npp8u *)pu8_dst_data_dev, resize_width * 3, npp_dst_size, NppiRect{0, 0, resize_width, resize_height},
                      NPPI_INTER_LINEAR);

    cv::Mat newimage_resize(resize_height, resize_width, CV_8UC3);
    cudaMemcpy(newimage_resize.data, pu8_dst_data_dev, resize_height * resize_width * 3, cudaMemcpyDeviceToHost);
    cv::cvtColor(newimage_resize, newimage_resize, cv::COLOR_RGB2BGR); // opencv默认使用的是BGR
    cv::imwrite("./rzImage_npp.jpg", newimage_resize);

    cudaFree(pu8_dst_data_dev);
    cudaFree(pu8_rgb_dev);
    cudaFree(pu8_yuv_dev);
    free(pu8_rgb_host);
    return 0;
}

         

        我的开源:

         1、Nvidia视频硬解码、渲染、软/硬编码并写入MP4文件。项目地址:https://github.com/BreakingY/Nvidia-Video-Codec
        2、Jetson Jetpack5.x视频编解码。项目地址:https://github.com/BreakingY/jetpack-dec-enc
        3、ffmpeg音视频(H264/H265/AAC)封装、解封装、编解码pipeline,支持NVIDIA硬编解码。项目地址:https://github.com/BreakingY/FFmpeg-Media-Codec-Pipeline
        4、simple rtsp server,小而高效的rtsp服务器,支持H264、H265、AAC、PCMA;支持TCP、UDP;支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-server

        5、simple rtp client,rtsp客户端,支持TCP、UDP、H264、H265、AAC、PCMA,支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-client

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值