使用 CUDA 和 libpng 进行图像处理

Image processing with CUDA and libpng

我一直在尝试使用 CUDA 和 libpng 库处理图像(即应用黑白滤镜)。但是,我不确定如何将图像数据传递给设备。

据我了解,libpng 库读取 png 并将信息存储在一个 png_bytep 结构中,使用此函数逐行调用 row_pointers。

    void read_png_file(char* file_name){
        char header[8];    // 8 is the maximum size that can be checked

        FILE *fp = fopen(file_name, "rb");
        if (!fp)
                abort_("[read_png_file] File %s could not be opened for reading" file_name);
        fread(header, 1, 8, fp);
        // if (png_sig_cmp(header, 0, 8))
        //         abort_("[read_png_file] File %s is not recognized as a PNG file", file_name);


        //Inicializa variables necesarias para libpng
        png_ptr =   png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

        if (!png_ptr)
                abort_("[read_png_file] png_create_read_struct failed");

        info_ptr = png_create_info_struct(png_ptr);
        if (!info_ptr)
                abort_("[read_png_file] png_create_info_struct failed");

        if (setjmp(png_jmpbuf(png_ptr)))
                abort_("[read_png_file] Error during init_io");

        //Inicializa el input/output para el archivo PNG
        png_init_io(png_ptr, fp);
        png_set_sig_bytes(png_ptr, 8);

        //Lee la información anterior a los datos de los píxeles como tal
        png_read_info(png_ptr, info_ptr);

        //Almacena información del archivo PNG 
        width = png_get_image_width(png_ptr, info_ptr);
        height = png_get_image_height(png_ptr, info_ptr);
        color_type = png_get_color_type(png_ptr, info_ptr);
        bit_depth = png_get_bit_depth(png_ptr, info_ptr);

        number_of_passes = png_set_interlace_handling(png_ptr);
        png_read_update_info(png_ptr, info_ptr);


        // Lectura del archivo PNG
        if (setjmp(png_jmpbuf(png_ptr)))
                abort_("[read_png_file] Error during read_image");

        // Reserva el espacio necesario para almacenar los datos del archivo PNG por filas
        row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height);
        for (y=0; y<height; y++)
                row_pointers[y] = (png_byte*) malloc(png_get_rowbytes(png_ptr,info_ptr));

        // Y para la copia para el device
        d_row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height);
        for (y=0; y<height; y++)
                d_row_pointers[y] = (png_byte*) malloc(png_get_rowbytes(png_ptr,info_ptr));


        png_read_image(png_ptr, row_pointers);
        
        fclose(fp);
}

我一直在尝试传递存储在 row_pointers 中的信息,创建一个名为 d_row_pointers 的副本并使用 cudaMalloc 和 cudaMemcpy 函数,如下所示:

png_bytep * row_pointers;
png_bytep * d_row_pointers;

int main(int argc, char **argv)
{       
        
        // Verifica los parámetros para ejecutar el programa
        if (argc != 3)
                abort_("Uso: ./Nombre_del_Programa <file_in> <file_out>");

        read_png_file(argv[1]);

        // CUDA
        int size = sizeof(png_bytep);
        int int_size = sizeof(int);
        
        cudaMalloc((void **)&d_row_pointers, size);
        for (y=0; y<height; y++)
                 cudaMalloc((void **)&d_row_pointers[y],png_get_rowbytes(png_ptr,info_ptr));

        cudaMalloc((void **)&d_width, int_size);
        cudaMalloc((void **)&d_height, int_size);


        cudaMemcpy(d_row_pointers, &row_pointers, size, cudaMemcpyHostToDevice);
        for (y=0; y<height; y++)
                 cudaMemcpy(d_row_pointers[y], &row_pointers[y], png_get_rowbytes(png_ptr,info_ptr), cudaMemcpyHostToDevice);
        cudaMemcpy(d_width, &width, int_size, cudaMemcpyHostToDevice);
        cudaMemcpy(d_height, &height, int_size, cudaMemcpyHostToDevice);

        // Lanzar el kernel
        process_file<<<1,1>>>(d_row_pointers, d_width, d_height);

        // Copiar los resultados de vuelta al host
        cudaMemcpy(&row_pointers, d_row_pointers, size, cudaMemcpyDeviceToHost);

        // Limpieza
        for (y=0; y<height; y++)
                cudaFree(d_row_pointers[y]);
        cudaFree(d_row_pointers);
        cudaFree(d_width);
        cudaFree(d_height);

        // Escritura de la imagen con los resultados
        write_png_file(argv[2]);

        return 0;
}

这是应该应用过滤器的 process_file 函数

__global__ void process_file(png_bytep * d_row_pointers, int * d_width, int * d_height)
{
        // Se realizan los cambios deseados en la imagen
        
        //Verificar los datos recibidos
        printf("Width = %d , Height = %d ", *d_width, *d_height);

        int rgb_total = 0;  
        float rgb_average = 0.0;
        int x = 0;
        int y = 0;
        png_byte *row;
        png_byte *ptr;

        for (y=0; y<*d_height-1; y++) {

                for (x=0; x<*d_width; x++) {
                
                        rgb_total = 0;
                        rgb_average = 0;

                        row             = d_row_pointers[y];
                        ptr             = &(row[x*3]);
                        
                        printf("Pixel  %d - %d, Rgb values: %d - %d - %d \n", x, y, ptr[0], ptr[1], ptr[2]); 
                        rgb_total      += ptr[0] + ptr[1] + ptr[2];
                        
                        // Calculando el promedios RGB
                        rgb_average = rgb_total / 3;
                        // printf("Average: %d \n", (int)rgb_average);
                        
                        ptr[0]  = (int)rgb_average;
                        ptr[1]  = (int)rgb_average;
                        ptr[2]  = (int)rgb_average;
                        
                        // printf("Changed to  %d - %d - %d \n",ptr[0], ptr[1], ptr[2]); 
                        // printf("Pixel  %d - %d done\n",x,y);  
                }

        }
        // printf("Para la imagen de resolución: %d x %d - ", *width, *height);
        
}

但我无法让它工作,即使内核似乎正在接收结构,当我访问数据时它全为零,当它应该是 RGB 值时。我非常感谢有关将此数据传递给内核的正确方法的任何帮助。谢谢!

PD:可以找到完整代码here

我很确定是这一行引起了问题:

cudaMemcpy(d_row_pointers[y], &row_pointers[y], 
    png_get_rowbytes(png_ptr,info_ptr), cudaMemcpyHostToDevice);

您无法通过 cpu 代码访问 d_row_pointers[y] 处的数据。 cudaMalloc 需要一个指向 cpu 上的指针的指针,并将这样对待参数。

您需要小心处理 CUDA 中的多维数组。大多数人选择使用平面阵列,即使是图像。

这就是我的建议:将数据复制到平面(一维)缓冲区,然后将其复制到设备。这样就更难犯错了。

啊,还有一件事:您不需要为内核显式指定 cudaMalloc 和 cudaMemcpy 的宽度和高度参数。您可以按值传递它们,就好像它是一个普通函数一样。您只需要数组的那些函数。