(C ++)(Visual Studio)将RGB更改为灰度

时间:2017-08-10 04:36:33

标签: c++ image-processing

我正在访问图像:

pDoc = GetDocument();

int iBitPerPixel = pDoc->_bmp->bitsperpixel;    // used to see if grayscale(8 bits) or RGB (24 bits)
int iWidth = pDoc->_bmp->width;
int iHeight = pDoc->_bmp->height;
BYTE *pImg = pDoc->_bmp->point;     // pointer used to point at pixels in the image
int Wp = iWidth;
const int area = iWidth * iHeight;
int r;          // red pixel value
int g;          // green pixel value
int b;          // blue pixel value
int gray;       // gray pixel value

BYTE *pImgGS = pImg;                    // grayscale image pixel array

并尝试将rgb图像更改为灰色,如下所示:

    // convert RGB values to grayscale at each pixel, then put in grayscale array
    for (int i = 0; i<iHeight; i++)
        for (int j = 0; j<iWidth; j++)
        {
            r = pImg[i*iWidth * 3 + j * 3 + 2];
            g = pImg[i*iWidth * 3 + j * 3 + 1];
            b = pImg[i*Wp + j * 3];

            r * 0.299;
            g * 0.587;
            b * 0.144;

            gray = std::round(r + g + b);

            pImgGS[i*Wp + j] = gray;
        }

最后,这是我尝试绘制图像的方式:

//draw the picture as grayscale
for (int i = 0; i < iHeight; i++) {
    for (int j = 0; j < iWidth; j++) {
        // this should set every corresponding grayscale picture to the current picture as grayscale
        pImg[i*Wp + j] = pImgGS[i*Wp + j];
    }
}
}

原图: enter image description here 我得到的结果是这样的: enter image description here

2 个答案:

答案 0 :(得分:0)

首先检查图像类型是否为每像素24位。 其次,将内存分配给pImgGS;

BYTE* pImgGS = (BTYE*)malloc(sizeof(BYTE)*iWidth *iHeight); 

请参阅this文章,了解如何保存bmp数据。 bmp图像被颠倒保存。此外,第一个54字节的信息是BITMAPFILEHEADER。 因此,您应该按照以下方式访问值,

double r,g,b;
unsigned char gray;
for (int i = 0; i<iHeight; i++)
{
     for (int j = 0; j<iWidth; j++)
    {
        r = (double)pImg[(i*iWidth + j)*3 + 2];
        g = (double)pImg[(i*iWidth + j)*3 + 1];
        b = (double)pImg[(i*iWidth + j)*3 + 0];

         r= r * 0.299;
         g= g * 0.587;
         b= b * 0.144;

         gray = floor((r + g + b + 0.5));

         pImgGS[(iHeight-i-1)*iWidth + j] = gray;
     }
}

如果存在填充,则首先以不同方式确定填充和访问。请参考this了解音高和填充。

double r,g,b;
unsigned char gray;
long index=0;
for (int i = 0; i<iHeight; i++)
{
     for (int j = 0; j<iWidth; j++)
    {
        r = (double)pImg[index+ (j)*3 + 2];
        g = (double)pImg[index+ (j)*3 + 1];
        b = (double)pImg[index+ (j)*3 + 0];

         r= r * 0.299;
         g= g * 0.587;
         b= b * 0.144;

         gray = floor((r + g + b + 0.5));

         pImgGS[(iHeight-i-1)*iWidth + j] = gray;
     }
index =index +pitch;
}

在绘制图像时, 当pImg为24bpp时,您需要将灰色值复制三次到每个R,G,B通道。如果您最终想要以bmp格式保存灰度图像,那么您还必须颠倒写入bmp数据,或者您可以在此处跳过转换为灰色的步骤:

pImgGS[(iHeight-i-1)*iWidth + j] = gray;

答案 1 :(得分:0)

TL; DR:

制定一条共同的道路。以明确定义的方式将所有内容转换为32位,并且不使用图像尺寸或坐标。将YCbCr转换(=灰度值计算)重构为一个单独的函数,这更容易阅读并以完全相同的速度运行。

冗长的东西

首先,你似乎对步幅和抵消感到困惑。你看到的人工制品是因为当你应该写出三个值时,你意外地写出了一个值(总共只有三分之一的数据)。
人们可以很容易地对此感到困惑,但这里发生的事情是因为你做了一些你不需要做的无用的东西。您从左到右,从上到下迭代坐标,并精心计算每个位置的数据中的正确字节偏移。
但是,您正在执行全屏效果,因此真正想要的会遍历整个图像。谁在乎宽度和高度?您知道数据的开头,并且您知道长度。完整blob上的一个循环将执行相同的操作,但速度更快,代码更少,并且错误的机会更少。

接下来,24位位图作为文件很常见,但它们对于内存中表示来说相当不寻常,因为格式很难访问并且不适合硬件。绘制这样的位图将需要来自驱动程序或图形硬件的大量工作(它将起作用,但它将无法正常工作)。因此,32位深度通常是更好,更快,更舒适的选择。访问程序更加“自然” 您可以简单地将24位转换为32位。迭代完整的位图数据,并为每个3字节元组读取写出一个完整的32位字。 Windows位图忽略A通道(最高位字节),因此只需将其保留为零或其他任何内容。

此外,没有像<8>灰度位图这样的东西。这根本就不存在。虽然存在看起来像灰度位图的位图,但它们实际上是调色板的8位位图,其中(无意中)bmiColors成员包含所有灰度值。

因此,除非您能保证您只处理自己创建的图像,否则您不能仅仅依赖于值5和73分别对应于5/255和73/255灰度强度。 可能是的情况,但通常是错误的假设 为了安全起见,在正确性方面,您必须通过在调色板中查找索引(位图的灰度值实际上是索引)将8位灰度位图转换为实际颜色。否则,可以加载灰度图像,其中调色板是相反的(因此5表示250和250表示5),或者根本不是灰度的位图。

所以...你想要转换24位,你想要将8位位图转换为32位深度。这意味着你在开始时做了所有令人讨厌的假设的东西,其余的是一个相同的共同路径。那是件好事。
您将在屏幕上显示的内容是总是一个32位位图,其中最顶层的字节被忽略,而较低的三位都是相同的值,导致看起来像灰色的阴影。这很简单,也很简单。

请注意,如果您执行BT.601样式的YCbCr转换(如使用常量0.299,0.587和0.144所示),并且您的8位灰度图像是敏感的(这是您必须知道的,没有办法从文件中告诉!),然后为了100%正确,你需要在从调色板8位转换为RGB时进行逆变换。否则,您的最终结果将看起来像几乎正确,但不完全。如果您的8位灰度是线性的,即在不使用上述常量的情况下创建(再次,您必须知道,您无法从图像中得知),您需要按原样复制所有内容(此处,正在执行转换会使它看起来几乎 - 但不是非常正确。)

关于RGB到灰度转换,您不需要额外的灰度位图来保存之后不再需要的值。您可以从加载的位图中读取三个颜色值,计算Y,然后直接构建32位ARGB字,然后将其写入最终位图。这节省了一个完全无用的往返内存,这是不必要的。

这样的事情:

uint32_t* out = (uint32_t*) output_bitmap_data;

for(int i = 0; i < inputSize; i+= 3)
{
    uint8_t Y = calc_greyscale(in[0], in[1], in[2]);
    *out++ = (Y<<16) | (Y<<8) | Y;
}

或者,您也可以执行from-to-to-32转换,然后在那里进行to-greyscale转换。反过来,这会引入一个额外的内存往返,但代码变得非常容易,总体上更容易。