使用 EasyBMP 提取每个通道的像素数据并展平像素数据 - 将提取的数据写回不同的图像

Using EasyBMP to extract pixel data per channel and flattening the pixel data - writing back extracted data results in different image

这是一个新手问题,但我正在努力从 BMP 对象中提取像素,提供了 EasyBMP 库,其中每个像素由以下结构表示:

typedef struct RGBApixel {
    ebmpBYTE Blue;
    ebmpBYTE Green;
    ebmpBYTE Red;
    ebmpBYTE Alpha;
} RGBApixel;

#define unsigned char ebmpBYTE

BMP 中的像素由指向指针数组的指针表示:

 RGBApixel** Pixels;

所以基本上是一个二维数组(也可以通过在整个库的源代码中使用 [i][j] 来访问像素)。

我想从 BMP 中得到 3 个一维数组(基本上是展平的二维数组)- 每个颜色通道(R、G 和 B)一个,不包括 alpha:

BMP bmp_file;
bmp_file.ReadFromFile("test.bmp"); // 640x480 bitmap image

unsigned int dimensions = bmp_file.TellWidth() * bmp_file.TellHeight();
auto pixels_host_red_channel = new unsigned char[dimensions];
auto pixels_host_green_channel = new unsigned char[dimensions];
auto pixels_host_blue_channel = new unsigned char[dimensions];

for (unsigned int col = 0; col < bmp_file.TellWidth(); col++)
{
    for (unsigned int row = 0; row < bmp_file.TellHeight(); row++)
    {
        *(pixels_host_red_channel + col * bmp_file.TellHeight() + row) = bmp_file.GetPixel(col, row).Red;
        *(pixels_host_green_channel + col * bmp_file.TellHeight() + row) = bmp_file.GetPixel(col, row).Green;
        *(pixels_host_blue_channel + col * bmp_file.TellHeight() + row) = bmp_file.GetPixel(col, row).Blue;
        *(pixels_host_grayscale + col * bmp_file.TellHeight() + row) = 0;
    }
}

原因是我想使用 CUDA 和(这里是 CUDA 初学者)在每个通道的基础上做一些操作,到目前为止最简单的方法(不触及库的源代码)就是做我上面发布的。

在继续之前,我尝试简单地将像素数据写回 BMP 对象并将其写入文件:

unsigned char alpha = bmp_file.GetPixel(0, 0).Alpha; // My image has the same alpha across all pixels so for the image it works just fine

BMP bmp_original(bmp_file);
for (unsigned int col = 0; col < bmp_original.TellWidth(); col++)
{
    for (unsigned int row = 0; row < bmp_original.TellHeight(); row++)
    {
        unsigned char red = *(pixels_host_red_channel + col * bmp_original.TellHeight() + row);
        unsigned char green = *(pixels_host_green_channel + col * bmp_original.TellHeight() + row);
        unsigned char blue = *(pixels_host_blue_channel + col * bmp_original.TellHeight() + row);

        RGBApixel pixel_original { red, green, blue, alpha };
        bmp_original.SetPixel(col, row, pixel_original);
    }
}

上面的代码导致

原来的样子是这样的:

这里是完整代码(库除外):

#include "EasyBMP.h"

int main(int argc, char* argv[])
{
    // Load BMP file into CPU memory
    BMP bmp_file;
    bmp_file.ReadFromFile("test.bmp");

    unsigned int dimensions = bmp_file.TellWidth() * bmp_file.TellHeight();
    unsigned int channel_size = sizeof(unsigned int) * dimensions;
    // EasyBMP does not offer direct access to the raw pixel data array
    // but instead maps all 4 channels (R, G, B and A) to a structure
    // Here we allocate CPU memory for each channel
    auto pixels_host_red_channel = new unsigned char[dimensions];
    auto pixels_host_green_channel = new unsigned char[dimensions];
    auto pixels_host_blue_channel = new unsigned char[dimensions];
    auto pixels_host_grayscale = new unsigned char[dimensions];

    // and copy the respective R, G or B value to the given array
    for (unsigned int col = 0; col < bmp_file.TellWidth(); col++)
    {
        for (unsigned int row = 0; row < bmp_file.TellHeight(); row++)
        {
            *(pixels_host_red_channel + col * bmp_file.TellHeight() + row) = bmp_file.GetPixel(col, row).Red;
            *(pixels_host_green_channel + col * bmp_file.TellHeight() + row) = bmp_file.GetPixel(col, row).Green;
            *(pixels_host_blue_channel + col * bmp_file.TellHeight() + row) = bmp_file.GetPixel(col, row).Blue;
            *(pixels_host_grayscale + col * bmp_file.TellHeight() + row) = 0;
        }
    }
    //printf("col:row = %d:%d\n\n", col, row);

    // Following line is not really useful except for displaying info
    // about the alpha channel (for the test image it is all equal, 
    // which is generally not the case). In case alpha channel needs
    // to be handled in some way, use the same steps as for the other
    // channels to ensure covering cases where alpha is not the same
    // across all pixels
    unsigned char alpha = bmp_file.GetPixel(0, 0).Alpha;

    BMP bmp_copy(bmp_original);
    for (unsigned int col = 0; col < bmp_copy.TellWidth(); col++)
    {
        for (unsigned int row = 0; row < bmp_copy.TellHeight(); row++)
        {
            unsigned char red = *(pixels_host_red_channel + col * bmp_copy.TellHeight() + row);
            unsigned char green = *(pixels_host_green_channel + col * bmp_copy.TellHeight() + row);
            unsigned char blue = *(pixels_host_blue_channel + col * bmp_copy.TellHeight() + row);

            RGBApixel pixel_original { red, green, blue, alpha };
            bmp_copy.SetPixel(col, row, pixel_original);
        }
    }
    // Write original file to a new file (for verification it's the same)
    bmp_copy.WriteToFile("test_copy.bmp");

    delete pixel_host_red_channel;
    delete pixel_host_green_channel;
    delete pixel_host_blue_channel;

    return 0;
}

我解决了这个问题。有两个问题:

  • 迭代顺序(行优先 vs 列优先):

    for (unsigned int row = 0; row < bmp_file.TellHeight(); row++)
    {
        for (unsigned int col = 0; col < bmp_file.TellWidth(); col++)
        {
            *(pixels_host_red_channel + row * bmp_file.TellWidth() + col) = bmp_file.GetPixel(col, row).Red;
            *(pixels_host_green_channel + row * bmp_file.TellWidth() + col) = bmp_file.GetPixel(col, row).Green;
            *(pixels_host_blue_channel + row * bmp_file.TellWidth() + col) = bmp_file.GetPixel(col, row).Blue;
            *(pixels_host_grayscale + row * bmp_file.TellWidth() + col) = 0;
        }
    }
    
  • 值在 RGBApixel 中的存储方式 - BGRA 而不是 RGBA:

    for (unsigned int row = 0; row < bmp_file.TellHeight(); row++)
    {
        for (unsigned int col = 0; col < bmp_file.TellWidth(); col++)
        {
            unsigned char red = *(pixels_host_red_channel + row * bmp_file.TellWidth() + col);
            unsigned char green = *(pixels_host_green_channel + row * bmp_file.TellWidth() + col);
            unsigned char blue = *(pixels_host_blue_channel + row * bmp_file.TellWidth() + col);
    
            RGBApixel pixel_original {blue, green, red, alpha};
            bmp_original.SetPixel(col, row, pixel_original);
        }
    }
    

第二个只是表明初始化结构然后用分别寻址每个数据字段的值填充它比只使用短初始化要好得多{ ... }