繁体   English   中英

简单 C++ OpenCL 图像处理程序上的错误 CL_INVALID_VALUE

[英]Error CL_INVALID_VALUE on simple C++ OpenCL image manipulation program

我正在用 C++ 编写一个简单的 OpenCL 程序,我需要将输入图像倒置,我正在使用 CImg 来读取和写入图像文件。 问题是即使程序编译并运行没有任何错误,输出文件也是空白的。

这是 cl 内核代码:

const sampler_t sampler = CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_NEAREST;

__kernel void img_turn(
    read_only image2d_t I,
    write_only image2d_t O
)
{
    int gid_x = get_global_id(0);
    int gid_y = get_global_id(1);

    int w = get_image_width(I);
    int h = get_image_height(I);

    if (gid_x >= w || gid_y >= h)
        return;
    
    uint4 p = read_imageui(I, sampler, (int2)(gid_x, gid_y));
    write_imageui(O, (int2)(gid_x, h - gid_y), p);
    
}

这是主机代码的一部分,首先是输入图像(已编辑):

CImg<unsigned char> img_in(img_file_name);

cl_image_format format = {
    CL_RGBA,
    CL_UNSIGNED_INT8,
};

cl_image_desc desc = {
    .image_type = CL_MEM_OBJECT_IMAGE2D,
    .image_width = (size_t) img_in.width(),
    .image_height = (size_t) img_in.height(),
    .image_row_pitch = 0,
    .image_slice_pitch = 0,
    .num_mip_levels = 0,
    .num_samples = 0,
    .buffer = NULL,
};

cl_mem input_img = clCreateImage(
    context,
    CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
    (const cl_image_format *) &format,
    (const cl_image_desc *) &desc,
    img_in.data(),
    &errNum
);

输出图像的定义(已编辑):

CImg<unsigned char> img_out(img_in.width(), img_in.height(), 1, 4);

format = {
    CL_RGBA,
    CL_UNSIGNED_INT8,
};

desc = {
    .image_type = CL_MEM_OBJECT_IMAGE2D,
    .image_width = (size_t) img_out.width(),
    .image_height = (size_t) img_out.height(),
    .image_row_pitch = 0,
    .image_slice_pitch = 0,
    .num_mip_levels = 0,
    .num_samples = 0,
    .buffer = NULL,
};

cl_mem output_img = clCreateImage(
    context,
    CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR,
    (const cl_image_format *) &format,
    (const cl_image_desc *) &desc,
    img_out.data(),
    NULL
);

以及代码的最后一部分,我将图像排入队列并运行程序(已编辑):

size_t origins[3] = {0, 0, 0};
size_t region_in[3] = {(size_t) img_in.width(), (size_t) img_in.height(), (size_t) 1};

errNum = clSetKernelArg(kernel, 0, sizeof(cl_mem), input_img);
errNum |= clSetKernelArg(kernel, 1, sizeof(cl_mem), output_img);

size_t global[2] = {(size_t) img_in.width(), (size_t) img_in.height()};
clEnqueueNDRangeKernel(command_queue, kernel, 2, NULL, global, NULL, 0, NULL, &kernel_event);

errNum = clEnqueueWriteImage(command_queue, input_img, CL_TRUE, origins, region_in, 0, 0, img_in.data(), 0, NULL, NULL);

size_t region_out[3] = {(size_t) img_out.width(), (size_t) img_out.height(), (size_t) 1};
errNum = clEnqueueReadImage(command_queue, output_img, CL_TRUE, origins, region_out, 0, 0, img_out.data(), 0, NULL, NULL);

clWaitForEvents(1, &kernel_event);
img_out.save("./output_img.png");

编译并运行程序后,会创建“output_img.png”图像文件,但它是空白的:0Bytes,使用文本编辑器打开时没有任何数据。

编辑:所以在 PeterT 的建议之后(并且在对我犯的一些愚蠢错误进行了一些更正之后),程序现在似乎正在做某事(它执行 3 秒),但仍然没有产生任何结果。

编辑2:经过一些调试,我查明了问题: clEnqueueReadImage返回错误CL_INVALID_VALUE ,并且文档指定如果由 origin 和 region 指定的读取区域超出范围,它将返回该错误......但我没有不知道为什么。 它与输入图像的大小相同,但clEnqueueWriteImage不会返回任何错误,即使使用相同的参数调用也是如此。

编辑 3:Egor 的回复已解决了该问题。 但现在它没有输出想要的结果:输入图像: 输入图像

输出图像: 输出图像

首先,您使用CL_RGBA格式创建 OpenCL 图像对象并将指针传递给CImg像素数据。 但是CImg使用“平面”结构来保存数据并且颜色通道的值不会交错(更多信息请参阅如何使用 CImg 存储像素数据? )。 例如,带有 alpha 通道的彩色图像将在内存中存储为:

R1R2R3...B1B2B3...G1G2G3...A1A2A3...

但是CL_RGBA格式意味着图像的交错通道: R1G1B1A1R2G2B2A2R3G3B3A3... 因此,在将图像复制到设备内存之前,需要将图像转换为CL_RGBA格式。 例如,使用以下函数:

struct rgba_pixel {
    unsigned char r;
    unsigned char g;
    unsigned char b;
    unsigned char a;
};

constexpr unsigned int r_channel_idx = 0;
constexpr unsigned int g_channel_idx = 1;
constexpr unsigned int b_channel_idx = 2;
constexpr unsigned int a_channel_idx = 3;

std::vector<rgba_pixel>
convert_cimg_to_rgba_buffer(const cimg_library::CImg<unsigned char>& img) {
    const unsigned int img_height = static_cast<unsigned int>(img.height());
    const unsigned int img_width = static_cast<unsigned int>(img.width());
    const unsigned int number_of_channels = static_cast<unsigned int>(img.spectrum());

    const bool has_r_channel = number_of_channels > r_channel_idx;
    const bool has_g_channel = number_of_channels > g_channel_idx;
    const bool has_b_channel = number_of_channels > b_channel_idx;
    const bool has_a_channel = number_of_channels > a_channel_idx;

    std::vector<rgba_pixel> rgba_buf(static_cast<std::size_t>(img_width) * img_height);
    for (unsigned int y = 0; y < img_height; ++y) {
        for (unsigned int x = 0; x < img_width; ++x) {
            const std::size_t pixel_idx = static_cast<std::size_t>(img_width) * y + x;
            rgba_buf[pixel_idx].r = has_r_channel ? *img.data(x, y, 0, r_channel_idx) : 0;
            rgba_buf[pixel_idx].g = has_g_channel ? *img.data(x, y, 0, g_channel_idx) : 0;
            rgba_buf[pixel_idx].b = has_b_channel ? *img.data(x, y, 0, b_channel_idx) : 0;
            rgba_buf[pixel_idx].a = has_a_channel ? *img.data(x, y, 0, a_channel_idx) : UCHAR_MAX;
        }
    }
    return rgba_buf;
}

因此将图像复制到设备的代码如下所示:

    size_t origins[3] = { 0, 0, 0 };
    size_t region[3] = { (size_t)img_in.width(), (size_t)img_in.height(), (size_t)1 };
    auto rgba_buf = convert_cimg_to_rgba_buffer(img_in);

    ret = clEnqueueWriteImage(command_queue, input_img, CL_TRUE, origins, region, 0, 0, rgba_buf.data(), 0, NULL, NULL);

此外,在保存之前需要转换输出图像。 例如使用以下函数:

void
copy_rgba_buffer_to_cimg(const std::vector<rgba_pixel>& rgba_buf, cimg_library::CImg<unsigned char>& img) {
    const unsigned int img_height = static_cast<unsigned int>(img.height());
    const unsigned int img_width = static_cast<unsigned int>(img.width());
    const unsigned int number_of_channels = static_cast<unsigned int>(img.spectrum());

    const bool has_r_channel = number_of_channels > r_channel_idx;
    const bool has_g_channel = number_of_channels > g_channel_idx;
    const bool has_b_channel = number_of_channels > b_channel_idx;
    const bool has_a_channel = number_of_channels > a_channel_idx;

    for (unsigned int y = 0; y < img_height; ++y) {
        for (unsigned int x = 0; x < img_width; ++x) {
            const std::size_t pixel_idx = static_cast<std::size_t>(img_width) * y + x;
            if (has_r_channel) *img.data(x, y, 0, r_channel_idx) = rgba_buf[pixel_idx].r;
            if (has_g_channel) *img.data(x, y, 0, g_channel_idx) = rgba_buf[pixel_idx].g;
            if (has_b_channel) *img.data(x, y, 0, b_channel_idx) = rgba_buf[pixel_idx].b;
            if (has_a_channel) *img.data(x, y, 0, a_channel_idx) = rgba_buf[pixel_idx].a;
        }
    }
}

从设备复制图像的代码如下所示:

    ret = clEnqueueReadImage(command_queue, output_img, CL_TRUE, origins, region, 0, 0, rgba_buf.data(), 0, NULL, NULL);
    copy_rgba_buffer_to_cimg(rgba_buf, img_out);
    img_out.save("./output_img.png");

接下来,使用默认属性创建命令队列。 这意味着排队到命令队列的命令将按顺序执行。 此外,您使用阻塞读取和写入(对于clEnqueueReadImageclEnqueueWriteImage函数调用, blocking_readblocking_write标志设置为CL_TRUE )。 在这种情况下,代码可以在不使用 OpenCL 事件来同步命令执行的情况下工作。 只需以正确的顺序将命令排入队列并使用阻塞读取命令即可获得结果:

    size_t origins[3] = { 0, 0, 0 };
    size_t region[3] = { (size_t)img_in.width(), (size_t)img_in.height(), (size_t)1 };
    auto rgba_buf = convert_cimg_to_rgba_buffer(img_in);

    ret = clEnqueueWriteImage(command_queue, input_img, CL_FALSE, origins, region, 0, 0, rgba_buf.data(), 0, NULL, NULL);

    size_t global[2] = { (size_t)img_in.width(), (size_t)img_in.height() };
    clEnqueueNDRangeKernel(command_queue, kernel, 2, NULL, global, NULL, 0, NULL, NULL);

    ret = clEnqueueReadImage(command_queue, output_img, CL_TRUE, origins, region, 0, 0, rgba_buf.data(), 0, NULL, NULL);
    copy_rgba_buffer_to_cimg(rgba_buf, img_out);
    img_out.save("./output_img.png");

最后,像素的新y位置应计算为get_image_height() - (gid_y + 1)因为gid_y在区间[0, get_image_height())中。 所以内核代码应该是这样的:

    write_imageui(O, (int2)(gid_x, h - gid_y - 1), p);

次要注意,如果您使用clEnqueueWriteImage直接将图像复制到设备,则可以省略clCreateImage调用的CL_MEM_USE_HOST_PTR标志。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM