Медианный фильтр для АЭС CUDA для 16-битных изображений

Окончательное обновление: решено. Тайм-аут WDDM также был проблемой. Нашел решение от: Исправление тайм-аута WDDM. Спасибо, Роберт.

Обновление: Спасибо Роберту за то, что он указал, что центр фильтра не 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. Размер фильтра 13х13. Ширина и высота изображения показаны ниже. По неизвестной причине произойдет сбой следующего кода, и я спрашиваю, знает ли кто-нибудь почему?

Странные вещи, которые я заметил:

  1. Ошибка происходит с nppiFilterMedian_16u_C1R(). Сама функция возвращает условие без ошибки, но делает следующее cudaMemcpy(). Без фильтра cudaMemcpy() работает просто отлично.

  2. Кроме того, получение размера буфера для 16-битного фильтра всегда возвращает размер 0. Я протестировал 8-битный и 32-битный, которые возвращают ненулевые значения...

  3. Я думаю, что это возможно ошибка (?) С библиотекой NPPI. Кажется, это зависит от размера (если вы уменьшите ширину / высоту изображения, оно будет работать нормально при размере фильтра 13x13). Однако размеры моего фильтра должны быть до 31x31.

Другая важная информация: приложение для Windows x64, среда выполнения CUDA 7.5, версия NPP 7.5. Устройство с графическим процессором Quadro K2200 (4 ГБ, глобальная память).

1 ответ

Решение

Функция медианного фильтра будет передавать маску по изображению, точка за точкой. Эта маска имеет указанные размеры (9x9 в вашем исходном коде). Опорная точка будет определять, как эта маска расположена для каждого пикселя. Когда точка привязки равна 0,0, маска будет расположена следующим образом:

p**
***
***

где p представляет местоположение пикселя, а размер маски составляет 3х3. Для точки привязки 1,1 расположение маски на пиксель будет:

***
*p*
***

Поэтому мы видим, что точка привязки и размер маски будут определять определенную границу вокруг каждого пикселя, которая должна быть доступна для функции медианного фильтра. При работе с пикселями на границе изображения мы должны убедиться, что эта граница попадает в допустимые пиксели.

Случай, с которого вы начали, маска 9x9 и точка привязки 0,0, означает, что нам нужны только "лишние" пиксели для границы в "конце" изображения. Поэтому модификация проста: ограничьте высоту области интереса, чтобы не обрабатывать последние несколько строк изображения, соответствующие размеру маски. Для этого случая мы можем просто вычесть 10 из высоты ROI, и ошибки исчезнут:

$ 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
Другие вопросы по тегам