为什么OpenCL内核不使用普通的x与Image2D协调?

时间:2017-02-05 02:30:05

标签: c++ image-processing docker opencl gpgpu

TLDR;

对于到达这里的人,在试图弄清楚如何使用OpenCL进行高斯模糊或灰度时,最终的工作代码为here。请注意,在那个repo中,我实际上是使用Nvidia的Docker包装器在Docker中运行整个GPU访问。您可以在“Dockerfile”中查看为使代码运行而需要采取的步骤,或者如果您有这样的设置并且在Nvidia GPU上运行,则只需使用Nvidia-Docker运行它。

原始问题:

在OpenCL图像过滤器应用程序中使用以下内核,我得到了预期的结果,即输入图像的返回灰度版本:

const sampler_t sampler =   CLK_NORMALIZED_COORDS_FALSE |
                            CLK_ADDRESS_CLAMP_TO_EDGE |
                            CLK_FILTER_NEAREST;

__kernel void process(__read_only  image2d_t src,
                        __write_only image2d_t dst)
{
    int x = get_global_id(0);
    int y = get_global_id(1);

    float4 color;

    color = read_imagef(src, sampler, (int2)(x, y));
    float gray = (color.x + color.y + color.z) / 3;
    write_imagef(dst, (int2)(x,y), (float4)(gray, gray, gray, 0));
}

到目前为止,这么好。然后我尝试创建一个只复制图像顶部和左边框的内核:

const sampler_t sampler =   CLK_NORMALIZED_COORDS_FALSE |
                            CLK_ADDRESS_CLAMP_TO_EDGE |
                            CLK_FILTER_NEAREST;

__kernel void process(__read_only  image2d_t src,
                        __write_only image2d_t dst)
{
    int x = get_global_id(0);
    int y = get_global_id(1);

    float4 color;

    if (x < 10 || y < 10) 
    {
        color = read_imagef(src, sampler, (int2)(x, y));
        write_imagef(dst, (int2)(x,y), (float4)(color.x, color.y, color.z, 0));
    } 
    else 
    {
        write_imagef(dst, (int2)(x,y), (float4)(0,0,0,0));
    }
}

返回的图像不是我的预期: Image that appears incorrectly processed

我正在以这种方式加载输入图像:

//  Load an image using the OpenCV library and create an OpenCL
//  image out of it
cl::Image2D LoadImage(cl::Context context, char *fileName, int &width, int &height)
{
    cv::Mat image = cv::imread(fileName, CV_LOAD_IMAGE_COLOR);
    cv::Mat imageRGBA;

    width = image.rows;
    height = image.cols;

    cv::cvtColor(image, imageRGBA, CV_RGB2RGBA);

    char *buffer = reinterpret_cast<char *>(imageRGBA.data);

    cl::Image2D clImage(context,
                            CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
                            cl::ImageFormat(CL_RGBA, CL_UNORM_INT8),
                            width,
                            height,
                            0,
                            buffer);
    return clImage;
}

输出图像:

cl::Image2D imageOutput(context,
            CL_MEM_WRITE_ONLY,
            cl::ImageFormat(CL_RGBA, CL_UNORM_INT8),
            width,
            height,
            0,
            NULL);

内核:

cl::Program program(context, util::loadProgram("border.cl"), true); 
cl::make_kernel<cl::Image2D, cl::Image2D> filter(program, "process");
cl::NDRange global(width, height);
filter(cl::EnqueueArgs(queue, global), clImageInput, imageOutput);

然后回读图像:

cl::size_t<3> origin;
origin[0] = 0; origin[1] = 0, origin[2] = 0;
cl::size_t<3> region;
region[0] = width; region[1] = height; region[2] = 1;
float* oup = new float[width * height];

queue.enqueueReadImage(imageOutput, CL_TRUE, origin, region, 0, 0, oup);

cv::imwrite(filename_out, cv::Mat(width, height, CV_8UC4, oup)); 

为什么图像的处理方式如此?只选择y坐标小于10的像素似乎有效,但选择x坐标小于10的像素似乎在图像上错开。

如果我在内核中使用以下行编写测试图像:

write_imagef(dst, (int2)(x,y), (float4)((float)x / 512.0f, 0, 0, 0));

我得到以下图片:

Red channel test gradient

第一个奇怪的是蓝色通道正在设置,而不是红色。我不知道为什么我总是以RGBA顺序加载和保存图像。其次,条带是非常不寻常的,我不知道如何解释这一点。

如果我在内核中使用以下行:

write_imagef(dst, (int2)(x,y), (float4)(0, (float)y / 512.0f, 0, 0));

我得到以下图片:

enter image description here

这看起来就像我期望的那样。

如果需要,我可以提供更多代码,但在完全相同的线束中使用灰度内核可以正常工作。正如此处未列出的另一个内核那样只是复制所有像素。

我正在使用OpenCL 1.2运行代码和Nvidia Geforce 980M

2 个答案:

答案 0 :(得分:2)

我还没有看到任何明显的东西。一个奇怪的事情:你的图像是CL_RGBA,CL_UNORM_INT8但是你正在把它读成一个浮点数组?你是如何展示它的?其次,我不熟悉你的内核启动技术;什么是filter,是否以2的维度启动?关于你所看到的问题,我建议使用消除过程来找出问题所在。例如,(1)如果删除条件并复制所有像素,您会获得整个图像吗? (2)如果你根据X位置和基于Y位置的绿色通道渐变编写红色通道渐变,那么如何在条件为假的情况下写入黑色。你有双梯度吗?根据结果​​,继续分解问题,直到找到原因。它看起来很像行间距问题,也许在显示功能中?

答案 1 :(得分:0)

好的,问题是我读高度和宽度的方式是向后的,即

width = image.rows;
height = image.cols;

应该是

height = image.rows;
width = image.cols;

经过更正后,其余的代码可以保持不变,除了我将图像保存到磁盘的最后一行,这里需要再次交换值,即

cv::imwrite(filename_out, cv::Mat(width, height, CV_8UC4, oup)); 

需要改为:

cv::imwrite(filename_out, cv::Mat(height, width, CV_8UC4, oup)); 

我认为这最终归结为图像的矩阵方法,其中第一个坐标实际上是行号,即高度,第二个坐标是列号,即宽度。

@Dithermaster提到的诊断确实有帮助,打印出假定的宽度和高度也是如此,这最终是错误的。

有趣的是,通过在代码中同时存在这些错误,像素复制的像素工作正常,但是一旦开始基于x,y坐标执行操作,就会得到一些非常时髦的结果。