【问题标题】:CUDA NPP Median Filter for 16 bit images用于 16 位图像的 CUDA NPP 中值滤波器
【发布时间】:2016-12-09 12:22:34
【问题描述】:

最终更新:已解决。 WDDM 超时也是一个问题。从WDDM timeout fix 找到解决方案。谢谢罗伯特。

更新:感谢 Robert 指出过滤器的中心不是 0,0。不幸的是,如果过滤器增加,您发布的代码将对我造成破坏,例如 17x17。这可能是由于您没有考虑图像“侧面”的边框。无论如何,这是最新的代码,但仍然表现出与以前相同的问题......

//npp
#include "npp.h"
#include "nppi.h"
#include "device_launch_parameters.h"

#include <iostream>

int main() {

    //Image size.
    int imageWidth = 6592; 
    int imageHeight = 4400;

    //Misc.
    int bytesPerPixel = 2;
    int totalPixels = imageWidth*imageHeight;
    int filterSize = 17;
    int halfFilter = filterSize/2;
    cudaError success2;
    NppStatus success1;

    //Mask & Origin for CUDA.
    NppiSize cudaMask; 
    cudaMask.height = filterSize; 
    cudaMask.width = filterSize;
    NppiPoint cudaAnchor;
    cudaAnchor.x = halfFilter;
    cudaAnchor.y = halfFilter;

    //ROI for CUDA.
    int left = halfFilter;
    int right = (imageWidth-1) - halfFilter;
    int top = halfFilter;
    int bot = (imageHeight-1) - halfFilter;
    NppiSize cudaROI;
    cudaROI.height  = bot - top;
    cudaROI.width   = right - left;

    //Step size.
    int step = imageWidth * bytesPerPixel;

    //Create a new "image".
    unsigned short* image = new unsigned short[totalPixels];
    for(int i=0; i<imageWidth; i++)
        for(int j=0; j<imageHeight; j++)
            image[j*imageWidth+i] = 10;

    //Allocate mem on device.
    Npp16u *dSrc, *dDst;
    Npp8u *dBuf;
    Npp32u bufferSize;

    //This call always returns a bufferSize==0.  That doesn't seem right...
    success1 = nppiFilterMedianGetBufferSize_16u_C1R(cudaROI, cudaMask, &bufferSize);
    std::cout << "get bufferSize returned: " << (int)success1 << std::endl;
    std::cout << bufferSize << std::endl;
    success2 = cudaMalloc( (void**)&dBuf, bufferSize);
    std::cout << "cudaMalloc 1 returned: " << (int)success2 << std::endl;
    success2 = cudaMalloc( (void**)&dSrc, totalPixels*sizeof(Npp16u));
    std::cout << "cudaMalloc 2 returned: " << (int)success2 << std::endl;
    success2 = cudaMalloc( (void**)&dDst, totalPixels*sizeof(Npp16u));
    std::cout << "cudaMalloc 3 returned: " << (int)success2 << std::endl;

    //Copy host image to device.
    success2 = cudaMemcpy( dSrc, image, totalPixels*sizeof(Npp16u), cudaMemcpyHostToDevice);
    std::cout << "cudaMemcpy 1 returned: " << (int)success2 << std::endl;


    //Copy source to destination.
    success1 = nppiCopy_16u_C1R( dSrc, step, dDst, step, cudaROI);
    std::cout << "npp Copy 1 returned: " << (int)success1 << std::endl;


    //Filter.
    Npp32u offset = top*step + left*bytesPerPixel;
    success1 = nppiFilterMedian_16u_C1R(    dSrc + offset,
                                            step,
                                            dDst + offset,
                                            step,
                                            cudaROI, cudaMask, cudaAnchor, dBuf);
    std::cout << "npp Filter  returned: " << (int)success1 << std::endl;


    //Copy resultant back to host.
    success2 = cudaMemcpy( image, dDst, totalPixels*sizeof(Npp16u), cudaMemcpyDeviceToHost);
    std::cout << "cudaMemcpy 2 returned: " << (int)success2 << std::endl;

    //Clean.
    success2 = cudaFree(dDst);
    success2 = cudaFree(dBuf);
    success2 = cudaFree(dSrc);
    delete image;

    system("pause");
    return 0;

}

我正在尝试为 29mp 图像计算中值滤波器。过滤器尺寸为 13x13。图像的宽度和高度如下所示。 不知什么原因,下面的代码会崩溃,请问有没有人知道原因?

我注意到的奇怪的事情:

  1. nppiFilterMedian_16u_C1R() 出现错误。该函数本身返回无错误条件,但以下 cudaMemcpy() 会。没有过滤器, cudaMemcpy() 工作得很好。

  2. 此外,获取 16 位过滤器的缓冲区大小始终返回大小为 0。我测试了 8 位和 32 位,它们返回非零值...

  3. 我认为这可能是 NPPI 库的一个错误 (?)。 它似乎取决于尺寸(如果您使用减小图像的宽度/高度,它对于 13x13 过滤器尺寸会正常工作)。但是,我的过滤器尺寸需要增加到 31x31。

其他重要信息: Windows x64 应用程序,CUDA 运行时 7.5,NPP 版本 7.5。 GPU 设备是 Quadro k2200(4GB 全局内存)。

【问题讨论】:

  • 请贴出实际编译的代码
  • 当掩码超出定义的输入图像区域时,中值滤波器未定义(并且非法)。当您设置代码时,图像的边界区域就是这种情况。在equivalent intel ipp documentation 中,您会注意到需要减小输出大小,即:“为确保在处理图像边界像素时有效操作,应用程序应正确定义额外的边界像素”您违反了此规则
  • 罗伯特,我发布了可编译的代码。我仍在研究您提到的有关边框像素的内容。现在,我只是想降低投资回报率,这样就不需要复制/复制边框了。
  • 我采用了您发布的代码并根据对我有意义的内容对其进行了修改,降低了投资回报率并增加了图像中的起点,并且错误消失了。我没有花很多时间看它。但是我以前遇到过这个问题,并且进行这样的调整会使问题消失。在宣布胜利之前,您还应该使用cuda-memcheck 运行您的代码。这将强制进行严格的错误检查,从而使潜在错误浮出水面。
  • 罗伯特,如果你不介意,你能把你的模组代码贴出来吗?我当前的版本缩小了尺寸,但仍然存在错误。并感谢 cuda-memcheck 的建议。

标签: image cuda filtering median npp


【解决方案1】:

中值滤波函数将在图像上逐点传递掩码。此掩码具有指定的尺寸(原始代码中的 9x9)。锚点将确定如何为每个像素定位此蒙版。当锚点为 0,0 时,遮罩会这样定位:

p**
***
***

其中p代表像素位置,掩码大小为3x3。对于 1,1 的锚点,每个像素的掩码定位将是:

***
*p*
***

因此,我们看到锚点和掩码大小将确定每个像素周围的特定边界,该边界必须可供中值滤波函数访问。在处理图像边界中的像素时,我们必须确保该边界落在有效像素上。

您开始使用的情况是 9x9 蒙版和 0,0 锚点,这意味着我们只需要“额外”像素作为图像“末端”的边界。因此修改很简单:限制 ROI 高度,不处理图像的最后几行,对应于 mask 尺寸。对于这种情况,我们可以简单地将 ROI 高度减去 10,误差就会消失:

$ cat t1223.cu
//npp
#include "npp.h"
#include "nppi.h"
#include <iostream>

int main() {

//When the filter size is 9x9....
int imageWidth = 6592; //breaks if > 5914 && imageHeight = 4400
int imageHeight = 4400; //breaks if > 3946 && imageWidth = 6592

//Misc.
int bytesPerPixel = 2;
int totalPixels = imageWidth*imageHeight;
cudaError success2;
NppStatus success1;

//ROI for CUDA.
NppiSize cudaROI;
cudaROI.height  = imageHeight-10;
cudaROI.width   = imageWidth;

//Mask & Origin for CUDA.
NppiSize cudaMask; NppiPoint cudaAnchor;
cudaMask.height = 9; //filter size
cudaMask.width = 9;
cudaAnchor.x = 0;
cudaAnchor.y = 0;

//Step size.
int step = imageWidth * bytesPerPixel;

//Create a new "image".
unsigned short* image = new unsigned short[totalPixels];
for(int i=0; i<imageWidth; i++)
    for(int j=0; j<imageHeight; j++)
        image[j*imageWidth+i] = 10;


//Allocate mem on device.
Npp16u *dSrc, *dDst;
Npp8u *dBuf;
Npp32u bufferSize;

//This call always returns a bufferSize==0.  That doesn't seem right...
success1 = nppiFilterMedianGetBufferSize_16u_C1R(cudaROI, cudaMask, &bufferSize);
std::cout << "get bufferSize returned: " << (int)success1 << std::endl;
std::cout << bufferSize << std::endl;
success2 = cudaMalloc( (void**)&dBuf, bufferSize);
std::cout << "cudaMalloc 1 returned: " << (int)success2 << std::endl;
success2 = cudaMalloc( (void**)&dSrc, totalPixels*sizeof(Npp16u));
std::cout << "cudaMalloc 2 returned: " << (int)success2 << std::endl;
success2 = cudaMalloc( (void**)&dDst, totalPixels*sizeof(Npp16u));
std::cout << "cudaMalloc 3 returned: " << (int)success2 << std::endl;

//Copy host image to device.
success2 = cudaMemcpy( dSrc, image, totalPixels*sizeof(Npp16u), cudaMemcpyHostToDevice);
std::cout << "cudaMemcpy 1 returned: " << (int)success2 << std::endl;

//Copy source to destination.
success1 = nppiCopy_16u_C1R( dSrc, step, dDst, step, cudaROI);
std::cout << "npp Copy 1 returned: " << (int)success1 << std::endl;

//Filter.
success1 = nppiFilterMedian_16u_C1R(dSrc,
                                    step,
                                    dDst,
                                    step,
                                    cudaROI, cudaMask, cudaAnchor, dBuf);
std::cout << "npp Filter  returned: " << (int)success1 << std::endl;

//Copy resultant back to host.
success2 = cudaMemcpy( image, dDst, totalPixels*sizeof(Npp16u), cudaMemcpyDeviceToHost);
std::cout << "cudaMemcpy 2 returned: " << (int)success2 << std::endl;

//Clean.
success2 = cudaFree(dBuf);
success2 = cudaFree(dSrc);
success2 = cudaFree(dDst);
delete image;

return 0;
}
$ nvcc -arch=sm_35 -o t1223 t1223.cu -lnppi
$ cuda-memcheck ./t1223
========= CUDA-MEMCHECK
get bufferSize returned: 0
0
cudaMalloc 1 returned: 0
cudaMalloc 2 returned: 0
cudaMalloc 3 returned: 0
cudaMemcpy 1 returned: 0
npp Copy 1 returned: 0
npp Filter  returned: 0
cudaMemcpy 2 returned: 0
========= ERROR SUMMARY: 0 errors
$

请注意,如果锚点被移动(例如,在上述情况下移动到 4,4 而不是 0,0),那么这意味着“边界”像素需要可用于约 5 行 在图像开始之前。我们可以通过正确设置 ROI 并偏移处理开始来解决这个问题,方法是向传递给中值滤波器的源指针添加行偏移,如下所示:

success1 = nppiFilterMedian_16u_C1R(dSrc + 5*imageWidth,

请注意,我并不是要在此处提供有关中值过滤的完整教程,而只是尝试找出导致实际功能失败的问题。左侧和右侧过滤器掩码边界也是您可能需要考虑的。在图像边界的左侧和右侧,这些像素掩码边界可能会索引到上一个或下一个图像行,从而“包裹”图像,可能会在过滤后的像素中产生奇怪的效果。

编辑:响应新代码发布,现在的主要问题似乎是您不了解如何偏移图像。

在 C/C++ 中,如果我有一个指针,并且我想将该指针偏移一定数量的元素,我只需添加我想要偏移它的元素数量。无需按字节缩放。如果您研究过我之前在上面给出的偏移量示例,您会注意到没有按字节缩放任何内容。如果我们想偏移 5 行,它只是 5 乘以图像宽度,如上所示。

另外,您使用 cudaROI 来通知您的 src->dst 复制操作,这对我来说没有意义,所以我修改了它。最后,我修改了代码,使它可以用锚在角落或中心来构建。

这是对我的代码的修改,在两种锚定情况下都可以正确编译和运行:

$ cat t1225.cu
//npp
#include "npp.h"
#include "nppi.h"
#include "device_launch_parameters.h"

#include <iostream>

int main() {

    //Image size.
    int imageWidth = 6592;
    int imageHeight = 4400;

    //Misc.
    int bytesPerPixel = 2;
    int totalPixels = imageWidth*imageHeight;
    int filterSize = 17;
    int halfFilter = filterSize/2;
    cudaError success2;
    NppStatus success1;

    //Mask & Origin for CUDA.
    NppiSize cudaMask;
    cudaMask.height = filterSize;
    cudaMask.width = filterSize;
    NppiPoint cudaAnchor;
#ifndef ANCHOR_CORNER
    cudaAnchor.x = halfFilter;
    cudaAnchor.y = halfFilter;
#else
    cudaAnchor.x = 0;
    cudaAnchor.y = 0;
#endif
    NppiSize imgSize;
    imgSize.width = imageWidth;
    imgSize.height = imageHeight;

    //ROI for CUDA.
    int left = halfFilter;
    int right = (imageWidth-1) - halfFilter;
    int top = halfFilter;
    int bot = (imageHeight-1) - halfFilter;
    NppiSize cudaROI;
    cudaROI.height  = bot - top;
    cudaROI.width   = right - left;

    //Step size.
    int step = imageWidth * bytesPerPixel;

    //Create a new "image".
    unsigned short* image = new unsigned short[totalPixels];
    for(int i=0; i<imageWidth; i++)
        for(int j=0; j<imageHeight; j++)
            image[j*imageWidth+i] = 10;

    //Allocate mem on device.
    Npp16u *dSrc, *dDst;
    Npp8u *dBuf;
    Npp32u bufferSize;

    //This call always returns a bufferSize==0.  That doesn't seem right...
    success1 = nppiFilterMedianGetBufferSize_16u_C1R(cudaROI, cudaMask, &bufferSize);
    std::cout << "get bufferSize returned: " << (int)success1 << std::endl;
    std::cout << bufferSize << std::endl;
    success2 = cudaMalloc( (void**)&dBuf, bufferSize);
    std::cout << "cudaMalloc 1 returned: " << (int)success2 << std::endl;
    success2 = cudaMalloc( (void**)&dSrc, totalPixels*sizeof(Npp16u));
    std::cout << "cudaMalloc 2 returned: " << (int)success2 << std::endl;
    success2 = cudaMalloc( (void**)&dDst, totalPixels*sizeof(Npp16u));
    std::cout << "cudaMalloc 3 returned: " << (int)success2 << std::endl;

    //Copy host image to device.
    success2 = cudaMemcpy( dSrc, image, totalPixels*sizeof(Npp16u), cudaMemcpyHostToDevice);
    std::cout << "cudaMemcpy 1 returned: " << (int)success2 << std::endl;


    //Copy source to destination.
    success1 = nppiCopy_16u_C1R( dSrc, step, dDst, step, imgSize);
    std::cout << "npp Copy 1 returned: " << (int)success1 << std::endl;


    //Filter.
#ifndef ANCHOR_CORNER
    Npp32u offset = top*imageWidth + left;
#else
    Npp32u offset = 0;
#endif
    success1 = nppiFilterMedian_16u_C1R(    dSrc + offset,
                                            step,
                                            dDst + offset,
                                            step,
                                            cudaROI, cudaMask, cudaAnchor, dBuf);
    std::cout << "npp Filter  returned: " << (int)success1 << std::endl;


    //Copy resultant back to host.
    success2 = cudaMemcpy( image, dDst, totalPixels*sizeof(Npp16u), cudaMemcpyDeviceToHost);
    std::cout << "cudaMemcpy 2 returned: " << (int)success2 << std::endl;

    //Clean.
    success2 = cudaFree(dDst);
    success2 = cudaFree(dBuf);
    success2 = cudaFree(dSrc);
    delete image;

    return 0;

}
$ nvcc -o t1225 t1225.cu -lnppi
$ cuda-memcheck ./t1225
========= CUDA-MEMCHECK
get bufferSize returned: 0
0
cudaMalloc 1 returned: 0
cudaMalloc 2 returned: 0
cudaMalloc 3 returned: 0
cudaMemcpy 1 returned: 0
npp Copy 1 returned: 0
npp Filter  returned: 0
cudaMemcpy 2 returned: 0
========= ERROR SUMMARY: 0 errors
$ nvcc -DANCHOR_CORNER -o t1225 t1225.cu -lnppi
$ cuda-memcheck ./t1225
========= CUDA-MEMCHECK
get bufferSize returned: 0
0
cudaMalloc 1 returned: 0
cudaMalloc 2 returned: 0
cudaMalloc 3 returned: 0
cudaMemcpy 1 returned: 0
npp Copy 1 returned: 0
npp Filter  returned: 0
cudaMemcpy 2 returned: 0
========= ERROR SUMMARY: 0 errors

【讨论】:

  • Robert,如果增加过滤器大小,此代码将中断。我刚刚发布了包含您的建议的代码版本,但是由于 filterSize 较大,NPP 仍然会中断。
  • 当然,如果您什么都不做但增加过滤器大小(假设为 17x17),那么代码将按原样中断,因为您没有按照我给出的说明进行操作。如果我在我的答案中采用这个确切的代码,并将过滤器大小增加到 17x17(而不是发布的 9x9 大小),并且将 ROI 高度减少 20(而不是我发布的 10答案),以考虑更大的过滤器大小,然后代码成功运行完成,就像我之前的代码在我的答案中所做的那样。
  • 是的,我同意并且忽略了说我也改变了投资回报率。当我将 ROI 高度降低 20 时,出现以下错误:npp Filter returned: -1000, CUDA ERROR #30 (using FilterMedian kernel #4): unknown error。
  • 我正在掌握您的 cmets,但我的代码没有 (0,0) 的锚点。它锚定在遮罩的中心,这就是为什么我将处理偏移了一半的过滤器——但更重要的是,如果我在每一侧(或其他任意大的数字)将 ROI 减少 100,错误仍然存​​在.这是令人不安的。
  • 另外我注意到您可能正在使用 Windows。如果启用了这些较大的过滤器大小,npp 处理时间可能足以触发 WDDM 超时,因此,如果我发布的确切代码运行时出现错误,请注意这一点。
猜你喜欢
  • 2012-09-28
  • 1970-01-01
  • 2023-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-19
相关资源
最近更新 更多