OpenCL 學習step by step (7) 灰度圖Histogram計算(1)


     histogram翻譯成中文就是直方圖,在計算機圖像處理和視覺技術中,通常用histogram來進行圖像匹配,從而完成track,比如meanshift跟蹤算法中,經常要用到圖像的直方圖。

     灰度圖的histogram計算,首先要選擇bin(中文可以稱作槽)的數量,對於灰度圖,像素的范圍通常是[0-255],所以bin的數目就是256,然后我們循環整幅圖像,統計出每種像素值出現的次數,放到對應的bin中。比如bin[0]中放的就是整幅圖像中灰度值為0的像素個數,bin[1]中放的就是整幅圖像中灰度值為1的像素個數……

     下面的直方圖就是灰度圖lenna對應的直方圖。

imageimage

      灰度圖直方圖的cpu計算特別簡單,定義一個數組hostBin[256],初始化所有數組元素為0,然后循環整幅圖像,得到直方圖,代碼如下:

//cpu求直方圖
void cpu_histgo()
    {
    int i, j;
    for(i = 0; i < height; ++i)
        {
        for(j = 0; j < width; ++j)
            {
            //printf("data: %d\n", data[i * width + j] );
            hostBin[data[i * width + j]]++;
            //printf("hostbin %d=%d\n", data[i * width + j], hostBin[data[i * width + j]]);
            }
        }
    }

    如何使用opencl,來計算灰度圖,就沒有那么簡單了。我們知道gpu的優勢是並行計算,如何把圖像分塊,來並行計算直方圖,是我們討論的重點。下面是一副512*512圖像的thread,workgroup划分:

image

     我們設定圖像的寬度是bins的整數倍,即256的倍數,高度是workgroup size(本程序中,設置為128)的倍數,如果圖像高寬不是bins和workgroup size的倍數,則我們通過下面的公式把圖像的寬度和高度變成它們的倍數:

//width是binSize的整數倍,height是groupsize的整數倍
width = (width / binSize ? width / binSize: 1) * binSize;
height = (height / groupSize ? height / groupSize: 1) * groupSize;

     則512*512的圖像可以分為8個work group,每個workgroup包括128個thread,每個thread計算256個像素的直方圖,並把結果放到該thread對應的local memroy空間,在kenrel代碼結束前,合並一個workgroup中所有thread的直方圖,生成一個workgroup塊的直方圖,最后在host端,合並8個workgroup塊的直方圖,產生最終的直方圖。

    openCL的memory對象主要有3個,dataBuffer用來傳入圖像數據,而minDeviceBinBuf大小是workgroup number *256, 即每個workgroup對應一個bin,另外一個kernel函數的第二個參數,它的大小為workgroup size*256, 用於workgroup中的每個線程存放自己256個像素的直方圖結果。

//創建2個OpenCL內存對象
dataBuf = clCreateBuffer(
    context,
    CL_MEM_READ_ONLY,
    sizeof(cl_uchar) * width  * height,
    NULL,
    0);

//該對象存放每個block塊的直方圖結果
midDeviceBinBuf = clCreateBuffer(
    context,
    CL_MEM_WRITE_ONLY,
    sizeof(cl_uint) * binSize * subHistgCnt,
    NULL,
    0);

   …

    status = clSetKernelArg(kernel, 1, groupSize * binSize * sizeof(cl_uchar), NULL); //local memroy size, lds for amd

下面看看kernel代碼是如何計算workgroup塊的直方圖。

__kernel
void histogram256(__global const uchar* data,
                  __local uchar* sharedArray,
                  __global uint* binResult)
{
    size_t localId = get_local_id(0);
    size_t globalId = get_global_id(0);
    size_t groupId = get_group_id(0);
    size_t groupSize = get_local_size(0);

下面這部分代碼初始化每個thread對應的local memory,也就是對應的256個bin中計數清零。sharedArray大小是workgroup size * 256 = 128 * 256

image

    //初始化共享內存
    for(int i = 0; i < BIN_SIZE; ++i)
        sharedArray[localId * BIN_SIZE + i] = 0;

通過barrier設置workgroup中所有thread的同步點,保證所有thread都完成初始化操作。

    barrier(CLK_LOCAL_MEM_FENCE);
   

下面的代碼,計算thread中,256個像素的直方圖,比如對於workgroup 0中的thread 0它計算的256個像素為綠條的部分像素,注意:每個thread的包含的像素並不是連續的

image
   
//計算thread直方圖
    for(int i = 0; i < BIN_SIZE; ++i)
    {
        uint value = (uint)data[groupId * groupSize * BIN_SIZE + i * groupSize + localId];
        sharedArray[localId * BIN_SIZE + value]++;
    }
    通過fence,保證每個thread都完成各自的直方圖計算。
    barrier(CLK_LOCAL_MEM_FENCE); 
    下面是合並各個thread的直方圖形成整個workgroup像素塊的直方圖,每個thread合並2個bin,比如thread 0,合並bin0和bin128。

image


   //合並workgroup中所有線程的直方圖,產生workgroup直方圖
    for(int i = 0; i < BIN_SIZE / groupSize; ++i)
    {
        uint binCount = 0;
        for(int j = 0; j < groupSize; ++j)
            binCount += sharedArray[j * BIN_SIZE + i * groupSize + localId];
           
        binResult[groupId * BIN_SIZE + i * groupSize + localId] = binCount;
    }
}

最終在host端,我們還要把每個workgroup塊的直方圖合並成得到整個圖像的直方圖,主要代碼如下:

// 合並子塊直方圖值

for(i = 0; i < subHistgCnt; ++i)
    {
    for( j = 0; j < binSize; ++j)
        {
        deviceBin[j] += midDeviceBin[i * binSize + j];
        }
    }

完整的代碼請參考:

工程文件gclTutorial7

代碼下載:

http://files.cnblogs.com/mikewolf2002/gclTutorial.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM