Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/image-processing/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Image processing 如何解释这个GPU计算?_Image Processing_Opencl_Gpgpu - Fatal编程技术网

Image processing 如何解释这个GPU计算?

Image processing 如何解释这个GPU计算?,image-processing,opencl,gpgpu,Image Processing,Opencl,Gpgpu,我正在使用OpenCL在我的GPU上实现2D图像处理内核。我从我的GPU得到了非常令人困惑的结果。该代码使用2X2模具,计算模具中每个输入样本的平均值,并将计算出的平均值添加到模具内输出图像中的每个样本 以下是CPU的代码: for(int i2 = 1; i2 < n1; ++i2) { for(int i1 = 1; i1 < n2; ++i1) { r00 = h_r[i2 ][i1 ]; r01 = h_r[i2 ][i1-1]; r10 =

我正在使用OpenCL在我的GPU上实现2D图像处理内核。我从我的GPU得到了非常令人困惑的结果。该代码使用2X2模具,计算模具中每个输入样本的平均值,并将计算出的平均值添加到模具内输出图像中的每个样本

以下是CPU的代码:

for(int i2 = 1; i2 < n1; ++i2) {
  for(int i1 = 1; i1 < n2; ++i1) {
    r00 = h_r[i2  ][i1  ];
    r01 = h_r[i2  ][i1-1];
    r10 = h_r[i2-1][i1  ];
    r11 = h_r[i2-1][i1-1];
    rs = 0.25f*(r00+r01+r10+r11);
    s[i2  ][i1  ] += rs;
    s[i2  ][i1-1] += rs;
    s[i2-1][i1  ] += rs;
    s[i2-1][i1-1] += rs;
  }    
}    
作为输入图像,应用此内核后,我得到以下输出图像:

2 4 4 2
4 8 8 4
4 8 8 4
2 4 4 2
在我的OpenCL实现中,我有以下内核:

_kernel void soSmoothingNew(__global const float* restrict d_r,
                            __global float* restrict d_s,  
                            int n1, 
                            int n2,)
{
  int g1 = get_global_id(0);
  int g0 = get_global_id(1);

  int i1 = g1+1; 
  int i2 = g0+1; 

  if (i1 >= n2) return;
  if (i2 >= n1) return;

  float r00, r01, r10, r11, rs;
  r01 = d_r[i2*n2+(i1-1)];
  r10 = d_r[(i2-1)*n2 + i1];
  r11 = d_r[(i2-1)*n2 + (i1-1)];
  rs = 0.25f*(r00+r01+r10+r11);
  d_s[i2*n2 + i1] += rs;
  d_s[i2*n2 + (i1-1)] += rs;
  d_s[(i2-1)*n2 + i1] += rs;
  d_s[(i2-1)*n2 + (i1-1)] += rs;

}
结果是:

2 2 2 2 
4 6 6 4 
4 6 6 4 
2 4 4 2 
我使用以下代码从主机执行内核:

size_t local_group_size[2] = {4,4};
size_t global_group_size_block[2] = {ceil((n1/local_group_size[0]) + 1) * local_group_size[0],
  ceil((n2/local_group_size[1]) + 1) * local_group_size[1]}; 

err = clEnqueueNDRangeKernel(queue, kernel1, 2, NULL, global_group_size_block, local_group_size, 0, NULL, NULL);
为了简洁起见,我省略了
clSetKernelArg
clCreateBuffer
和其他OpenCL调用。请注意,我还有另一个内核,它在执行这个内核之前将GPU上的输出
d_s
数组归零


我很难理解GPU上的线程是如何运行以达到这个结果的。对此的任何见解都将不胜感激。

正如void\u ptr所提到的,在写回输出时,您的问题肯定是一个竞争条件。解决这个问题的一个简单方法是让每个工作项负责完全计算其输出像素。您的算法也可以在此过程中简化

每个像素位于四个2x2像素框内,构成图像的一个3x3区域

O O O
O X O  X is the one we want to compute with a work item.
O O O
2x2区域可称为、Bs、Cs和Ds:

A A O  O B B  O O O  O O O
A A O  O B B  C C O  O D D
O O O  O O O  C C O  O D D
您可能已经注意到,在使用原始算法时,X像素是4个独立平均值的一部分。生成的像素正好包含1.0f*X。可以以类似的方式对周围的每个像素进行加权,并使用此掩码将其添加到最终输出值:

0.25  0.50  0.25
0.50  1.00  0.50
0.25  0.50  0.25
这些值有助于到达下面的内核

_kernel void soSmoothing(__global const float* restrict d_r, __global float* restrict d_s, int n1, int n2)
{
    //(idX,idY) represents the position to compute and write to d_s
    int idX = get_global_id(0);
    int idY = get_global_id(1);

    //using zero-base indices  
    if(idX >= n1) return;
    if(idY >= n2) return;

    float outValue = 0.0f;

    if(idX > 0){
        outValue += 0.50f * d_r[idX-1 + idY*n1]  + 0.25 * d_r[idX + idY*n1];
        if (idY > 0){
            outValue += 0.25f * d_r[idX-1 + (idY-1)*n1];
        }
        if (idY < (n2-1)){
            outValue += 0.25f * d_r[idX-1 + (idY+1)*n1];
        }
    }

    if(idX < (n1-1)){
        outValue += 0.50f * d_r[idX+1 + idY*n1] + 0.25 * d_r[idX + idY*n1];
        if (idY > 0){
            outValue += 0.25f * d_r[idX+1 + (idY-1)*n1];
        }
        if (idY < (n2-1)){
            outValue += 0.25f * d_r[idX+1 + (idY+1)*n1];
        }
    }

    if (idY > 0){
        outValue += 0.50f * d_r[idX + (idY-1)*n1] + 0.25 * d_r[idX + idY*n1];
    }
    if (idY < (n2-1)){
        outValue += 0.50f * d_r[idX + (idY+1)*n1] + 0.25 * d_r[idX + idY*n1];
    }

    d_s[idX + idY*n1] = outValue;
}

正如void_ptr所提到的,在写回输出时,您的问题肯定是一个竞争条件。解决这个问题的一个简单方法是让每个工作项负责完全计算其输出像素。您的算法也可以在此过程中简化

每个像素位于四个2x2像素框内,构成图像的一个3x3区域

O O O
O X O  X is the one we want to compute with a work item.
O O O
2x2区域可称为、Bs、Cs和Ds:

A A O  O B B  O O O  O O O
A A O  O B B  C C O  O D D
O O O  O O O  C C O  O D D
您可能已经注意到,在使用原始算法时,X像素是4个独立平均值的一部分。生成的像素正好包含1.0f*X。可以以类似的方式对周围的每个像素进行加权,并使用此掩码将其添加到最终输出值:

0.25  0.50  0.25
0.50  1.00  0.50
0.25  0.50  0.25
这些值有助于到达下面的内核

_kernel void soSmoothing(__global const float* restrict d_r, __global float* restrict d_s, int n1, int n2)
{
    //(idX,idY) represents the position to compute and write to d_s
    int idX = get_global_id(0);
    int idY = get_global_id(1);

    //using zero-base indices  
    if(idX >= n1) return;
    if(idY >= n2) return;

    float outValue = 0.0f;

    if(idX > 0){
        outValue += 0.50f * d_r[idX-1 + idY*n1]  + 0.25 * d_r[idX + idY*n1];
        if (idY > 0){
            outValue += 0.25f * d_r[idX-1 + (idY-1)*n1];
        }
        if (idY < (n2-1)){
            outValue += 0.25f * d_r[idX-1 + (idY+1)*n1];
        }
    }

    if(idX < (n1-1)){
        outValue += 0.50f * d_r[idX+1 + idY*n1] + 0.25 * d_r[idX + idY*n1];
        if (idY > 0){
            outValue += 0.25f * d_r[idX+1 + (idY-1)*n1];
        }
        if (idY < (n2-1)){
            outValue += 0.25f * d_r[idX+1 + (idY+1)*n1];
        }
    }

    if (idY > 0){
        outValue += 0.50f * d_r[idX + (idY-1)*n1] + 0.25 * d_r[idX + idY*n1];
    }
    if (idY < (n2-1)){
        outValue += 0.50f * d_r[idX + (idY+1)*n1] + 0.25 * d_r[idX + idY*n1];
    }

    d_s[idX + idY*n1] = outValue;
}
这是您所询问的“行和列”方法。其思想是同时处理输出图像的非重叠区域

我使用的16x16像素示例可以扩展到任何图像大小。每个黑框代表一个像素。蓝色的不同阴影仅用于区分要计算的工作项区域

如果将图像分成两个像素宽的列,则可以在单独的线程中处理每一列。这可以防止列之间的全局写入冲突。对奇数编号的列重复——ie dx=1,以计算与列重叠的2x2分组,如第二幅图所示。此方法仅防止水平方向上的写入冲突,但仍需要考虑行

为了避免列中的写入冲突,需要进一步将列划分为行,如上图所示。让我们让一个工作项计算单个2x2输出,以使内核尽可能简单。行也分两个阶段计算——dy=0和dy=1。总的来说,这是一个四步算法,每一步都可以称为“令人尴尬的并行”。这些步骤可以通过它们相对于原点的位置偏移来参考:(dx,dy)=A(0,0)、B(1,0)、C(1,0)和D(1,1)。请注意,组不一定使用相同数量的工作项,这取决于最后一行/列是否足够大以进行计算

显示内核调用期间两个示例工作项负责的说明。“行和列”基本上已成为“4个棋盘格”。下面是内核,它将计算2x2组像素的平均值并添加全局缓冲区。只要每个组A、B、C和D单独执行,就不需要检查全局写入冲突

_kernel void soSmoothing(__global const float* restrict inData, __global float* restrict outData, int width, int height, int dx, int dy)
{
    //(idX,idY) represents the position to compute and write to outData
    int idX = get_global_id(0);
    int idY = get_global_id(1);

    //(pixelX, pixelY) is the top-left corner of the square to compute
    int pixelX = idX *2 +dx;
    int pixelY = idY *2 +dy;

    if(pixelX > (width-2)) return;
    if(pixelY > (height-2)) return;

    //do the math and add to the corresponding addresses in outData
    int topLeftAddress = pixelX + pixelY * width;
    float avg = 0.25f * (inData[topLeftAddress] + inData[topLeftAddress +1] + inData[topLeftAddress + width] + inData[topLeftAddress + width +1]);
    outData[topLeftAddress] = outData[topLeftAddress] + avg;
    outData[topLeftAddress +1] = outData[topLeftAddress +1] + avg;
    outData[topLeftAddress +width] = outData[topLeftAddress +width] + avg;
    outData[topLeftAddress +width +1] = outData[topLeftAddress +width +1] + avg;
}
主机代码的步骤:

1) 将命令队列设置为阻塞

2) 设置缓冲区:1x输入,1x输出

3) 将输入缓冲区复制到设备,将设备上的输出初始化为0

4a)使用dx=0,dy=0将内核排队

4b)使用dx=1,dy=0将内核排队

4c)使用dx=0,dy=1将内核排队

4d)使用dx=1,dy=1将内核排队

5) 将输出缓冲区读回主机

请注意,在所有内核执行完成之前,不需要将输出复制到主机

优势:

  • 所有4个内核运行的输入相同,这意味着您可以非常轻松地在多达4个单独的设备上运行它,并在主机上对它们的输出求和。也许可以尝试在cpu上运行1个内核,在gpu上运行3个内核。您还可以分解内核来处理图像的较小区域,并让任意多的opencl设备参与。传输的开销最终将成为一个主要瓶颈,因此这可能不值得付出努力
  • 简单的内核,易于调试
  • 内核是“链接”的,因此它们添加到以前的输出中。可以以任何顺序运行
  • 内核(+主机代码)可以轻松扩展以支持3x3或其他单元维度
缺点:

  • 输入图像读取4x
  • 输出像素被写入4倍
  • 每个工作项一次仅计算4个像素。计算更大的区域可能更快,但代码复杂度更高
好了。请让我知道这是如何运作的