NPP初探
1. NPP是什么?为什么要用NPP?
npp是图像和信号处理的GPU加速库,在我们安装cuda的时候,其实也安装了对应的一些library,只是以前LZ就是纯粹的调用cuda和cudnn来做深度学习,做训练和测试,也没有关注cuda的一些具体组成部分,自从参加完GTC CHINA以后,LZ居然真的要用到这些了,世界真是奇妙。
原因很简单,因为要处理的图像数据都在GPU上,那么可以使用opencv+cuda,也可以直接使用npp进行处理,两者LZ都试了,在服务器上gcc和g++版本太低了,升级个版本估计相当于重新配置一遍环境,LZ于是决定还是重新用npp写了一遍。
2. NPP的函数命名方式
-
“A”:如果图像是4通道图像,则为“ A”,这表示结果alpha通道不受图元影响。
-
“Cn”:图像由n个通道压缩像素组成,其中n可以是1、2、3或4。
-
"C" :(在通道信息之后)指示图元仅在颜色通道之一(“感兴趣的通道”)上运行。所有其他输出通道不受原语影响。
-
”I“ 表示primitive“in-place”的方式工作。在这种情况下,图像数据指针通常命名为pSrcDst,以指示图像数据同时用作源和目标。
-
“ M” 表示“mask操作”。这些类型的图元具有附加的“mask图像”作为输入。目标图像中的每个像素对应于mask图像中的像素。仅处理具有相应非零掩码像素的像素。
-
“ R” 表示图元仅在矩形的“感兴趣区域”或“ ROI”上运行。所有ROI基元都采用NppiSize类型的附加输入参数,该参数指定基元应处理的矩形区域的宽度和高度。有关基元如何在ROI上运行的详细信息,请参见:感兴趣区域(ROI)。ROI太重要了,你做crop,resize都需要,非常非常有用!
-
“ Sfs” 表示结果值在写入之前通过固定的缩放比例和饱和度进行处理。
上面的后缀始终按字母顺序显示。The suffixes above always appear in alphabetical order. E.g. a 4 channel primitive not affecting the alpha channel with masked operation, in place and with scaling/saturation and ROI would have the postfix: “AC4IMRSfs”.
3. 图像数据
1. 图像数据通过一对参数传入和传出NPPI primitive:
- 指向图像基础数据类型的指针。
- 以字节为单位的行步长(有时也称为行跨步)。ps:这个很重要,要会计算,LZ好几次程序出问题,都是这个算错了/(ㄒoㄒ)/~~
这种相当低级的图像数据传递方式背后的总体思路是易于将其引入现有软件项目中:
通过将原始指针传递给基础像素数据类型,而不是结构化(按颜色)通道像素数据,可以在各种情况下使用该功能,从而避免了有风险的类型转换或昂贵的图像数据副本。
分别传递数据指针和行步,而不是更高级别的图像结构,可以轻松实现,因为不需要特定的图像表示,从而避免了将图像数据从主机应用程序打包到NPP特定图像表示的麻烦。
(PS:所以需要对图像存储结构、顺序、大小、深度、类型等一些参数都要很清楚才可以)
2. Line Step
行步(也称为“行跨步”或“行步”)允许通过在行的末尾添加许多未使用的字节,将大小不等的图像行从对齐好的地址上开始。这种类型的行填充很长时间以来一直是数字图像处理中的常规做法,并且并非仅限于GPU图像处理。
行步是一行中包含填充的字节数。解释该数字的另一种方法是说,它是图像中连续行的第一个像素之间的字节数,或者通常是任何像素列中两个相邻像素之间的字节数。
出现行台阶的一般原因是,像素的均匀对齐行可以优化内存访问模式。
即使NPP中的所有功能都适用于任意对齐的图像,但只有对齐良好的图像数据才能实现最佳性能。 CUDA运行时中使用NPP图像分配器或2D内存分配器分配的任何图像数据均已对齐。
特别是在支持CUDA的较旧GPU上,未对齐数据的性能下降可能会很大(数量级)。
传递给NPPI primitive的所有图像数据都需要提供一个行步骤。重要的是要记住,此行步骤始终以字节而不是像素为单位指定。
3. Parameter Names for Image Data
a. Passing Source-Image Data
-
通常原图像数据通过指针传递,可以命名为:pSrc
-
批量的图像指针,通过NppiImageDescriptor type 命名为:pSrcBatchList
-
Source-Planar-Image Pointer Array(以平面方式排列的图像数据类型):pSrc1, pSrc2, …
-
Source-Image Line Step, 源图像行的步长是图像中连续行之间的字节数。 源图像行步长参数为: nSrcStep
-
Source-Planar-Image Line Step Array,源平面图像线步进数组是一个数组,其中数组的每个元素都包含输入图像中特定平面的连续行之间的字节数。 源平面图像线步进数组参数为:rSrcStep[]
-
Source-Planar-Image Line Step, 源平面图像行步长是多平面输入图像的特定平面中连续行之间的字节数。 源平面图像线步进参数为:nSrcStep1, nSrcStep2, …
b. Passing Destination-Image Data
把上述Src全部转换成Dst即可, 如:pDst
c. Passing In-Place Image Data
在源图像或者目标图像前加上字母p即可,如:pSrcDst
d. Passing Mask-Image Data
- Mask-Image Pointer:pMask
- Mask-Image Line Step:nMaskStep
- Passing Channel-of-Interest Data:nCOI
4. Image Data Alignment Requirements
数据如果不做alignment,那么有时候会影响代码效率的,也可以看下LZ写的bmp的一篇博客
NPP要求像素数据遵守某些对齐约束。
对于2和4通道图像,以下对齐要求成立:
data_pointer%(\ #channels * sizeof(通道类型))== 0
例如。 具有基础类型Npp8u(8位无符号)的4通道图像将要求所有像素落在4的倍数的地址上(4通道* 1字节大小)。
由于所有像素都按照其自然大小对齐的逻辑结果,因此2通道和4通道图像的图像线步长也必须是像素大小的倍数。
对于1通道和3通道图像,仅要求像素指针与基础数据类型对齐,即pData%sizof(data type)==0。因此,相应的行步也要满足此要求。
5. Image Data Related Error Codes
这个是LZ养成的习惯,一定要使用NppStatus做做检查,如果出了问题,会有对应的错误标识
typedef enum
{
/* negative return-codes indicate errors */
NPP_NOT_SUPPORTED_MODE_ERROR = -9999,
NPP_INVALID_HOST_POINTER_ERROR = -1032,
NPP_INVALID_DEVICE_POINTER_ERROR = -1031,
NPP_LUT_PALETTE_BITSIZE_ERROR = -1030,
NPP_ZC_MODE_NOT_SUPPORTED_ERROR = -1028, /**< ZeroCrossing mode not supported */
NPP_NOT_SUFFICIENT_COMPUTE_CAPABILITY = -1027,
NPP_TEXTURE_BIND_ERROR = -1024,
NPP_WRONG_INTERSECTION_ROI_ERROR = -1020,
NPP_HAAR_CLASSIFIER_PIXEL_MATCH_ERROR = -1006,
NPP_MEMFREE_ERROR = -1005,
NPP_MEMSET_ERROR = -1004,
NPP_MEMCPY_ERROR = -1003,
NPP_ALIGNMENT_ERROR = -1002,
NPP_CUDA_KERNEL_EXECUTION_ERROR = -1000,
NPP_ROUND_MODE_NOT_SUPPORTED_ERROR = -213, /**< Unsupported round mode*/
NPP_QUALITY_INDEX_ERROR = -210, /**< Image pixels are constant for quality index */
NPP_RESIZE_NO_OPERATION_ERROR = -201, /**< One of the output image dimensions is less than 1 pixel */
NPP_OVERFLOW_ERROR = -109, /**< Number overflows the upper or lower limit of the data type */
NPP_NOT_EVEN_STEP_ERROR = -108, /**< Step value is not pixel multiple */
NPP_HISTOGRAM_NUMBER_OF_LEVELS_ERROR = -107, /**< Number of levels for histogram is less than 2 */
NPP_LUT_NUMBER_OF_LEVELS_ERROR = -106, /**< Number of levels for LUT is less than 2 */
NPP_CORRUPTED_DATA_ERROR = -61, /**< Processed data is corrupted */
NPP_CHANNEL_ORDER_ERROR = -60, /**< Wrong order of the destination channels */
NPP_ZERO_MASK_VALUE_ERROR = -59, /**< All values of the mask are zero */
NPP_QUADRANGLE_ERROR = -58, /**< The quadrangle is nonconvex or degenerates into triangle, line or point */
NPP_RECTANGLE_ERROR = -57, /**< Size of the rectangle region is less than or equal to 1 */
NPP_COEFFICIENT_ERROR = -56, /**< Unallowable values of the transformation coefficients */
NPP_NUMBER_OF_CHANNELS_ERROR = -53, /**< Bad or unsupported number of channels */
NPP_COI_ERROR = -52, /**< Channel of interest is not 1, 2, or 3 */
NPP_DIVISOR_ERROR = -51, /**< Divisor is equal to zero */
NPP_CHANNEL_ERROR = -47, /**< Illegal channel index */
NPP_STRIDE_ERROR = -37, /**< Stride is less than the row length */
NPP_ANCHOR_ERROR = -34, /**< Anchor point is outside mask */
NPP_MASK_SIZE_ERROR = -33, /**< Lower bound is larger than upper bound */
NPP_RESIZE_FACTOR_ERROR = -23,
NPP_INTERPOLATION_ERROR = -22,
NPP_MIRROR_FLIP_ERROR = -21,
NPP_MOMENT_00_ZERO_ERROR = -20,
NPP_THRESHOLD_NEGATIVE_LEVEL_ERROR = -19,
NPP_THRESHOLD_ERROR = -18,
NPP_CONTEXT_MATCH_ERROR = -17,
NPP_FFT_FLAG_ERROR = -16,
NPP_FFT_ORDER_ERROR = -15,
NPP_STEP_ERROR = -14, /**< Step is less or equal zero */
NPP_SCALE_RANGE_ERROR = -13,
NPP_DATA_TYPE_ERROR = -12,
NPP_OUT_OFF_RANGE_ERROR = -11,
NPP_DIVIDE_BY_ZERO_ERROR = -10,
NPP_MEMORY_ALLOCATION_ERR = -9,
NPP_NULL_POINTER_ERROR = -8,
NPP_RANGE_ERROR = -7,
NPP_SIZE_ERROR = -6,
NPP_BAD_ARGUMENT_ERROR = -5,
NPP_NO_MEMORY_ERROR = -4,
NPP_NOT_IMPLEMENTED_ERROR = -3,
NPP_ERROR = -2,
NPP_ERROR_RESERVED = -1,
/* success */
NPP_NO_ERROR = 0, /**< Error free operation */
NPP_SUCCESS = NPP_NO_ERROR, /**< Successful operation (same as NPP_NO_ERROR) */
/* positive return-codes indicate warnings */
NPP_NO_OPERATION_WARNING = 1, /**< Indicates that no operation was performed */
NPP_DIVIDE_BY_ZERO_WARNING = 6, /**< Divisor is zero however does not terminate the execution */
NPP_AFFINE_QUAD_INCORRECT_WARNING = 28, /**< Indicates that the quadrangle passed to one of affine warping functions doesn't have necessary properties. First 3 vertices are used, the fourth vertex discarded. */
NPP_WRONG_INTERSECTION_ROI_WARNING = 29, /**< The given ROI has no interestion with either the source or destination ROI. Thus no operation was performed. */
NPP_WRONG_INTERSECTION_QUAD_WARNING = 30, /**< The given quadrangle has no intersection with either the source or destination ROI. Thus no operation was performed. */
NPP_DOUBLE_SIZE_WARNING = 35, /**< Image size isn't multiple of two. Indicates that in case of 422/411/420 sampling the ROI width/height was modified for proper processing. */
NPP_MISALIGNED_DST_ROI_WARNING = 10000, /**< Speed reduction due to uncoalesced memory accesses warning. */
} NppStatus;
4. Region-of-Interest (ROI)
在实践中,处理图像的矩形子区域通常比处理完整图像更为普遍。 NPP的大多数图像处理原语都允许处理此类子区域,也称为关注区域或ROI。
所有支持ROI处理的原语在其名称后缀中均带有“ R”标记。 在大多数情况下,ROI是作为单个NppiSize结构传递的,该结构提供ROI的宽度和高度。 这就提出了一个问题,即图元如何知道该矩形(宽度,高度)在图像中的位置。 ROI的“起始像素”由图像数据指针隐式给出。 即 用户无需显式传递左上角(最低的内存地址)的像素坐标,而是仅使图像数据指针偏移以指向ROI的第一个像素。
实际上,这意味着对于图像(pSrc,nSrcStep),并且ROI的起始像素位于位置(x,y),将通过
pSrcOffset = pSrc + y * nSrcStep + x * PixelSize;
作为图元的图像数据源。 PixelSize通常计算为
PixelSize = NumberOfColorChannels * sizeof(PixelDataType).
E.g. for a pimitive like nppiSet_16s_C4R() we would have
NumberOfColorChannels == 4;
sizeof(Npp16s) == 2;
and thus PixelSize = 4 * 2 = 8;
Masked Operation
一些原始支持mask操作。 这些变量的后缀中的“ M”表示mask的操作。 支持mask操作的基元会消耗通过“mask图像指针”和“mask图像行阶”提供的其他输入图像。 这些图元将mask图像解释为布尔图像。 Npp8u类型的值被解释为布尔值,其中值0表示false,任何非零值表示true。
除非另外指出,否则仅在其空间上对应的遮罩像素为真(非零)的像素上执行该操作。 例如。 mask复制操作只会复制ROI中具有相应非零mask像素的那些像素。
5. Channel-of-Interest API
一些原语允许将操作限制在多通道图像内的单个感兴趣通道上。 这些原语以字母“ C”为后缀(在通道信息之后,例如nppiCopy_8u_C3CR()这个LZ还踩过坑,后来发现这个目标图像是多通道的,一定要目标图像是单通道还是多通道。。。)。 通常通过使图像数据指针偏移以直接指向感兴趣的通道而不是ROI中第一像素的基点来选择感兴趣的通道。 一些原语还显式地指定选定的频道号,并通过整数将其传递,例如 nppiMean_StdDev_8u_C3CR()。
1. Select-Channel Source-Image Pointer
这是指向源图像的第一个像素内的关注通道的指针。 例如。 如果pSrc是指向三通道图像ROI内第一个像素的指针。 使用适当的选择通道复制原语,可以通过将指针偏移一个,从而将该源图像的第二通道复制到pDst给定的目标图像的第一通道中:
nppiCopy_8u_C3CR(pSrc + 1, nSrcStep, pDst, nDstStep, oSizeROI);
2. Select-Channel Source-Image
一些原语允许用户通过指定频道号(nCOI)选择感兴趣的频道。 该方法通常用于图像统计功能。 例如,
nppiMean_StdDev_8u_C3CR(pSrc, nSrcStep, oSizeROI, nCOI, pDeviceBuffer, pMean, pStdDev );
感兴趣的通道可以是1, 2, 或者3
3. Select-Channel Destination-Image Pointer
对于目标图像同理可得
nppiCopy_8u_C3CR(pSrc, nSrcStep, pDst + 1, nDstStep, oSizeROI);
Source-Image Sampling
大量NPP图像处理功能会消耗至少一个源图像并生成一个输出图像(例如nppiAddC_8u_C1RSfs()或nppiFilterBox_8u_C1R())。属于此类别的所有NPP功能也都在ROI上运行(请参见兴趣区域(ROI)),对于这些功能,应考虑将这些功能描述为目标ROI。换句话说,ROI在目标图像中描述了一个矩形区域,并且该区域内的所有像素都由所讨论的功能写入。
为了成功使用这些功能,重要的是要了解用户定义的目标ROI如何影响算法正在读取输入图像中的哪些像素。为了简化对ROI传播的讨论(即给定目标ROI,源中的ROI是什么),区分两个主要情况是有意义的:
- 点操作:这些是类似nppiAddC_8u_C1RSfs()的原语。每个输出像素仅需要读取一个输入像素。
- 邻居操作:这些是类似nppiFilterBox_8u_C1R()的基元,需要从源图像读取一组像素才能产生单个输出。
1. Point-Wise Operations
如上所述,逐点运算消耗来自输入图像的单个像素(如果所讨论的运算具有多个输入图像,则消耗每个输入图像的单个像素)以产生单个输出像素。
2. Neighborhood Operations
在邻域操作的情况下,在一个或多个输入图像中读取多个输入像素(像素的“邻域”),以便计算单个输出像素。 过滤功能和形态运算的所有功能都是邻域运算。
这些函数中的大多数具有影响邻域的大小和相对位置的参数:mask-size结构和achor-point结构。 这两个参数将在下一节中详细介绍
a. Mask-Size Parameter
许多NPP邻域操作允许用户通过通常名为NppiSize类型的oMaskSize的参数来指定邻域的大小。 在那些情况下,从源读取的像素邻域恰好是mask的大小。 假设mask anchor 在位置(0,0)(请参见下面的锚点参数),并且大小为(w,h),即
*
* assert(oMaskSize.w == w);
* assert(oMaskSize.h == h);
* assert(oAnchor.x == 0);
* assert(oAnchor.y == 0);
*
b. Anchor-Point Parameter
许多执行邻域操作的NPP原语允许用户通过通常称为NppiPoint类型的oAnchor的参数指定邻域的相对位置。 使用锚点,开发人员可以选择相对于当前像素索引的mask位置(请参见“mask大小参数”)。
使用与Mask-Size Parameter中相同的示例,但是这次的锚点位置为(a,b):
assert(oMaskSize.w == w);
* assert(oMaskSize.h == h);
* assert(oAnchor.x == a);
* assert(oAnchor.y == b);
c. Sampling Beyond Image Boundaries
一般而言,NPP基元特别是NPP邻域操作要求读取和写入的所有像素位置均有效且在相应图像的边界内。在定义的图像数据区域之外进行采样会导致不确定的行为,并可能导致系统不稳定。
这在实践中造成了一个问题:在处理全尺寸图像时,不能选择目标ROI与源图像相同的大小。由于邻域操作从扩大的源ROI读取像素,因此必须缩小目标ROI,以使扩大的源ROI不会超过源图像的大小。
对于无法接受的目标图像尺寸“缩小”的情况,NPP提供了一组边界扩展的Copy原语。例如。 nppiCopyConstBorder_8u_C1R(),nppiCopyReplicateBorder_8u_C1R()和nppiCopyWrapBorder_8u_C1R()。用户可以使用这些原语通过三种扩展模式之一来“扩展”源图像的大小。然后,可以将展开的图像安全地传递到邻域操作,从而生成完整尺寸的结果.
参考地址:
1.https://docs.nvidia.com/cuda/npp/nppi_conventions_lb.html
在使用当中一定要注意开辟内存关闭内存,要检查cudaMemcpy时候成功,指针是否为空等一系列问题。
Deadline是第一生产力,当快到项目截止点,那种效率真的奇高。。。
PS:
累计确诊78631,总算增速在放缓了,累计治愈也有三万多人了,这一切都离不开一线医护人员的努力,警察和军队的支持,各个小区的配合。谈胜利还显得有些太早了,只希望春天的脚步近一些再近一些,期待春暖花开的日子!