NPPI 颜色空间转换、尺寸变换
1、NPP介绍
NPP(NVIDIA Performance Primitives)是专为 CUDA 架构设计的图像和信号处理 GPU 加速库。库涵盖了丰富的函数集,包括滤波、边缘检测、颜色转换等,利用 GPU 的并行计算能力,提高图像处理应用的执行速度。通过NPP 库,能够轻松地在支持 CUDA 的 NVIDIA GPU 上实现高性能的图像和信号处理任务,从而为应用程序提供更快速和高效的数据处理能力。
在 NVIDIA Performance Primitives (NPP) 库中,分NPPC、NPPI 和 NPPS 三个不同的命名空间,用于表示不同的功能和功能类别。
-
NPPC (NVIDIA Performance Primitives Core): NPPC 包含了 NPP 库的核心功能,提供了一些基本的操作和功能,如数据类型定义、常量定义等。这个命名空间通常用于包含 NPP 库中的核心组件。
-
NPPI (NVIDIA Performance Primitives Imaging): NPPI 包含了与图像处理相关的功能。这个命名空间提供了用于执行各种图像处理任务的函数,如滤波、颜色空间转换、图像金字塔等。
-
NPPS (NVIDIA Performance Primitives Signal): NPPS 包含了与信号处理相关的功能。这个命名空间提供了用于处理信号和时间序列数据的函数,包括傅里叶变换、卷积、滤波器设计等。
头文件在/usr/local/cuda/include/下:
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下:
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、音视频(H264/H265/AAC)封装、解封装、编解码pipeline,支持NVIDIA、昇腾DVPP硬编解码。项目地址:https://github.com/BreakingY/Media-Codec-Pipeline
4、simple rtsp server,小而高效的rtsp服务器,支持H264、H265、AAC、PCMA;支持TCP、UDP;支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-server
5、simple rtsp client,rtsp客户端,支持TCP、UDP、H264、H265、AAC、PCMA,支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-client
6、libflv,flv muxer/demuxer,支持H264/H265、AAC。地址:https://github.com/BreakingY/libflv
7、mpeg2 ts ps muxer/demuxer,支持H264/H265/MPEG1 audio/MP3/AAC/AAC_LATM/G711。项目地址:https://github.com/BreakingY/libmpeg2core
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库