Skip navigation

2D kép indexelése

Átekintés

A kernelek írásakor sokszor előfordul, hogy 2-3D adastruktúrákkal kell dolgoznunk. Ebben a fejezetben egy kép generálásán keresztül mutatjuk be, hogy hogyan lehet 2D tömböket kezelni.

A példa egy egyszerű hullám mintát generál.

Hullám minta

A teljes program forráskódja Letölthető innen, vagy az oldal alján megtekinthető.

Pixelek kezelése

A program kezeléséhez először tisztában kell lenni a pixelek kezelésével.

A képeket általában lineáris tömbökben szoktuk kezelni sorba fűzve. A több dimenziós tömbök használata egyszerűbb lenne, de az egy nagy tömb használata a leg-hatékonyabb módszer.

Ehhez a sorokat, oszlopokat, és színcsatornákat egyszerűen összefűzzük:

  • Minden pixelt 3-4 érték határoz meg;
  • A sorokban levő pixel hármasok együtt lesznek;
  • A sorokat a memóriában egymás után tesszük.

Pixel elrendezés képtömbben

Megvalósítás

A program megvalósításáhzo először a CPU oldalon elő kell készítenünk az adatokat. Ez gyakorlatilag a képtömb lefoglalását takarja.

    unsigned char* result = new unsigned char[N*N*3];
    unsigned char* dev_result;

    cudaMalloc((void**)&dev_result, N*N*4 * sizeof(unsigned char));

Látható, hogy két tömböt foglalunk, az egyiket a központi, a másikat a grafikus memóriában.

Mindd a két képtömb N*N pixelt tartalmaz. Továbbá RGB képet fogunk tárolni pixelenként 4 színcsatornával, ezért összesen N*N*4 értéket kell lefoglalnunk

A következő efladat, el kell indítanunka programunkat. A korábbiaktól eltérüen most kihasználjuk, hogy a blokkok, és a rács mérete dim3 typusú, vagyis lehet akár 2-3.dimenziós is.

    dim3 blockDim = dim3(BLOCKDIM, BLOCKDIM, 1);
    dim3 gridDim = dim3((N + BLOCKDIM - 1) / BLOCKDIM, (N + BLOCKDIM - 1) / BLOCKDIM, 1);

    rippleKernel <<<gridDim, blockDim >>> (dev_result, N, N, 50);

A függvény hívásakor át kell még adnunk a kép szélességét, és magassáhát. Illetve ebben a példában még meg van adva a hullámok hullámhossza (ami most 50 pixel), amivel be tudjuk állítanu, hogy milyen sűrűen legyenek a hullámok.

Végül pedig meg kell adnunk, magát a kernelt.

__global__ void rippleKernel(unsigned char* result, int w, int h, float waveLength)
{
    // ...

    return;
}

A kernelben az első feladatunk a feldolgozott pixel koordinátáinak kiszámítása. Mivel most 2D adatunk van, ezt két tendely mentén kell megtennünk.

A pixel koordináták mellet kiszámítjuk azt is, hogy a lineáris tömbünkben hanyadik helyen áll a feldolgozott pixel.

    int x = blockDim.x *blockIdx.x + threadIdx.x;
    int y = blockDim.y *blockIdx.y + threadIdx.y;

    int tid = y*w + x;

Ezek után jöhet a geometriai számítás. Kiszámítjuk a pixel intenzitását.

Ehhez annyit kell tennünk, hogy kiszámítjuk a képpont távolságát a kép középpontjától, és a kapott a kapott értéket átskálázzuk, hogy 50 pixel jelentsen egy db 2*pi -s ciklust, és használjuk a koszinus függvényt a hullám generálására.

    float dist = sqrtf((x - w / 2.0f)*(x - w / 2.0f) + (y-h/2.0f)*(y - h / 2.0f));
    float value = (cosf(dist / waveLength * CUDART_PI_F * 2)+1)*127;

Végül pedig kiírjuk az erdményt a globális memóriába.

if (x < w && y < h)
    {
        result[tid * 4] = value;
        result[tid * 4 + 1] = value;
        result[tid * 4 + 2] = value;
        result[tid * 4 + 3] = 255;
    }

Itt két fontos dologra kell figyelnünk:

  • Csak akkor próbáljunk kiírni egy képpontot, ha létezik is. Az indított blokkstruktúra ugyanis túllóghat a képen.
  • Illetve, hogy a képeken 4 színcsatorna van. Ezért a piel koordinátáját is meg kell szorozni 4-el, és 4 csatornát kell kiirnunk.

Teljes Kód

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "math_constants.h"

#include "ImageIO.h"

#include 
#include 

using namespace std;

#define N           512
#define BLOCKDIM    16

__global__ void rippleKernel(unsigned char* result, int w, int h, float waveLength);

int main(int argc, char** argv)
{
    unsigned char* result = new unsigned char[N*N*4];
    unsigned char* dev_result;

    cudaMalloc((void**)&dev_result, N*N*4 * sizeof(unsigned char));

    dim3 blockDim = dim3(BLOCKDIM, BLOCKDIM, 1);
    dim3 gridDim = dim3((N + BLOCKDIM - 1) / BLOCKDIM, (N + BLOCKDIM - 1) / BLOCKDIM, 1);

    rippleKernel << <gridDim, blockDim >> > (dev_result, N, N, 50);

    cudaMemcpy(result, dev_result, N*N*4 * sizeof(unsigned char), cudaMemcpyDeviceToHost);

    writeRGBImageToFile("image.png", result, N, N);

    return 0;
}

__global__ void rippleKernel(unsigned char* result, int w, int h, float waveLength)
{
    int x = blockDim.x *blockIdx.x + threadIdx.x;
    int y = blockDim.y *blockIdx.y + threadIdx.y;

    int tid = y*w + x;

    float dist = sqrtf((x - w / 2.0f)*(x - w / 2.0f) + (y-h/2.0f)*(y - h / 2.0f));
    float value = (cosf(dist / waveLength * CUDART_PI_F * 2)+1)*127;

    if (x < w && y < h)
    {
        result[tid * 4] = value;
        result[tid * 4 + 1] = value;
        result[tid * 4 + 2] = value;
        result[tid * 4 + 3] = 255;
    }

    return;
}

Feladatok

  1. Módosítsuk a programot, hogy más hullámhosszú hullámokat rajzoljon!
  2. Színezzük át a hullámokat más színűre (kék, piros, sárga, stb.)!
  3. Színezzük át a hullámokat két színűre (pl.: piros-kék.)!
  4. Modosítsuk a programot, hogy a hullámokat vízszintesen/függőlegesen rajzolja ki.
  5. Rajzoljuk ki hullámok helyett a batman egyenletet: http://mathworld.wolfram.com/BatmanCurve.html
    • Érdemes úgy csinálni, hogy az f(x)<0 értékeket egy színűre, a többit pedig egy másik színre állítjuk.