将图像转换为灰度

66
有没有办法将图像转换为每像素16位灰度格式,而不是将r、g和b组件的每个值都设置为亮度值。 我目前有一个从文件中读取的bmp图像。
Bitmap c = new Bitmap("filename");
我想要一个名为d的Bitmap,它是c的灰度版本。我看到有一个构造函数包括System.Drawing.Imaging.PixelFormat,但我不知道如何使用它。我对图像处理和相关的C#库是新手,但对C#本身有一定的经验。
任何帮助、在线资源的参考、提示或建议都将不胜感激。
编辑:d是c的灰度版本。
7个回答

88

我想要一个灰度的 Bitmap d。 我看到了一个包括 System.Drawing.Imaging.PixelFormat 的构造函数, 但我不知道如何使用它。

以下是实现方法:

Bitmap grayScaleBP = new 
         System.Drawing.Bitmap(2, 2, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale);

编辑: 转换为灰度

             Bitmap c = new Bitmap("fromFile");
             Bitmap d;
             int x, y;

             // Loop through the images pixels to reset color.
             for (x = 0; x < c.Width; x++)
             {
                 for (y = 0; y < c.Height; y++)
                 {
                     Color pixelColor = c.GetPixel(x, y);
                     Color newColor = Color.FromArgb(pixelColor.R, 0, 0);
                     c.SetPixel(x, y, newColor); // Now greyscale
                 }
             }
            d = c;   // d is grayscale version of c  

更快的版本来自switchonthecode,请点击链接获取完整分析:

public static Bitmap MakeGrayscale3(Bitmap original)
{
   //create a blank bitmap the same size as original
   Bitmap newBitmap = new Bitmap(original.Width, original.Height);

   //get a graphics object from the new image
   using(Graphics g = Graphics.FromImage(newBitmap)){

       //create the grayscale ColorMatrix
       ColorMatrix colorMatrix = new ColorMatrix(
          new float[][] 
          {
             new float[] {.3f, .3f, .3f, 0, 0},
             new float[] {.59f, .59f, .59f, 0, 0},
             new float[] {.11f, .11f, .11f, 0, 0},
             new float[] {0, 0, 0, 1, 0},
             new float[] {0, 0, 0, 0, 1}
          });

       //create some image attributes
       using(ImageAttributes attributes = new ImageAttributes()){

           //set the color matrix attribute
           attributes.SetColorMatrix(colorMatrix);

           //draw the original image on the new image
           //using the grayscale color matrix
           g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height),
                       0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes);
       }
   }
   return newBitmap;
}

抱歉,我在问题中应该说明我想要原始位图的灰度版本。我已经编辑了帖子以反映这一点。 - 0fnt
11
这种方法(逐像素处理)速度太慢。请查看这篇文章:http://www.switchonthecode.com/tutorials/csharp-tutorial-convert-a-color-image-to-grayscale - Luis
1
如何更改此方法以仅考虑RGB值而不考虑透明度?谢谢。 - Martina
我进行了第一次编辑,将所有的白色变成了红色,并且仍然是真正的24位彩色...我改变了代码使用绿色和蓝色,之后生成的图像似乎与原始图像相同。 - Bryan
2
原始文章的博客似乎已不可用。矩阵的链接或解释是什么? - amhed
请查看WayBack Machine - Dour High Arch

38
Bitmap d = new Bitmap(c.Width, c.Height);

for (int i = 0; i < c.Width; i++)
{
    for (int x = 0; x < c.Height; x++)
    {
        Color oc = c.GetPixel(i, x);
        int grayScale = (int)((oc.R * 0.3) + (oc.G * 0.59) + (oc.B * 0.11));
        Color nc = Color.FromArgb(oc.A, grayScale, grayScale, grayScale);
        d.SetPixel(i, x, nc);
    }
}

这种方法还可以保留 alpha 通道。
享受吧。


3
我知道这是一个旧回答,但您能否详细说明在R、G、B值上乘以的常数?我看到它们加起来等于“1.0”,但有什么解释吗? - Lars Kristensen
现在,我自己使用过之后,必须补充一点,那就是在小的禁用图标或任何需要注意区别的情况下使用它是一个坏主意。灰度结果与彩色结果非常接近...很难区分。 - Vercas
这个解决方案似乎很慢。第一个“for循环”应该遍历高度,而第二个循环(内部循环)应该迭代宽度(列)。图像按行存储在磁盘空间中。当您从一行迭代到另一行时,您会从一个磁盘空间跳转到另一个磁盘空间。当迭代宽度时,您只需转到磁盘上的下一个值即可。您可以查看opencv主题链接,您总是会看到应该在行(外部)和列(内部循环)中进行迭代。 - Christian Gold
这些循环变量名到处都是... "x" 实际上是 y 坐标,整个图像按列处理,因为高度循环在宽度循环内部。 - Nyerguds

34

ToolStripRenderer类中有一个静态方法,名为CreateDisabledImage。 它的使用非常简单:

Bitmap c = new Bitmap("filename");
Image d = ToolStripRenderer.CreateDisabledImage(c);

它使用的矩阵与被接受的答案略有不同,并且另外将其乘以透明度值为0.7,因此效果略有不同于只是灰度化,但如果您想使您的图像变成灰色,这是最简单和最好的解决方案。


3
谢谢你。这与WinForms中禁用控件内的图像外观相匹配,保持了一致性。 - Jimmy
这些图片有点蓝色调,看起来不太好看。 - Elmue

8
下面的代码是最简单的解决方案:
Bitmap bt = new Bitmap("imageFilePath");

for (int y = 0; y < bt.Height; y++)
{
    for (int x = 0; x < bt.Width; x++)
    {
        Color c = bt.GetPixel(x, y);

        int r = c.R;
        int g = c.G;
        int b = c.B;
        int avg = (r + g + b) / 3;
        bt.SetPixel(x, y, Color.FromArgb(avg,avg,avg));
    }   
}

bt.Save("d:\\out.bmp");

这里使用RGB值的算术平均值,虽然这可以工作,但并不完全产生正确的结果。其他答案使用加权值(R=0.3,G=0.59,B=0.11)的原因是目标灰度值是颜色的亮度(由我们的眼睛感知的亮度),而颜色的RGB分量没有以这种方式平衡。不幸的是,我们眼中的棒状细胞和锥状细胞并没有进化成方便地与RGB值相适应的形式,因此在进行转换时我们必须进行补偿。 - Phil Rogers

7

以上示例均未创建8位(8bpp)位图图像。一些软件,如图像处理,仅支持8bpp。不幸的是,MS .NET库没有解决方案。PixelFormat.Format8bppIndexed格式看起来很有前途,但经过多次尝试,我无法使其工作。

要创建真正的8位位图文件,您需要创建正确的标头。最终,我找到了Grayscale库用于创建8位位图(BMP)文件的解决方案。代码非常简单:

Image image = Image.FromFile("c:/path/to/image.jpg");
GrayBMP_File.CreateGrayBitmapFile(image, "c:/path/to/8bpp/image.bmp");

这个项目的代码并不漂亮,但是它可以工作,只有一个小问题需要简单修复。作者在代码中将图像分辨率硬编码为10x10。图像处理程序不喜欢这个设置。解决方法是打开GrayBMP_File.cs文件(是的,名字很奇怪),并用下面的代码替换第50和51行。该示例将分辨率设置为200x200,但您应该将其更改为正确的数字。

int resX = 200;
int resY = 200;
// horizontal resolution
Copy_to_Index(DIB_header, BitConverter.GetBytes(resX * 100), 24);
// vertical resolution 
Copy_to_Index(DIB_header, BitConverter.GetBytes(resY * 100), 28);

我想测试一下并点赞,但现在时间比较紧。不过还是谢谢你的回答。 - 0fnt
8bpp的技巧在于需要编辑支持图像的原始内部数组。您可以通过调用image.LockBits()来获取它,然后使用Marshal.Copy复制数据。请谷歌搜索以获取完整的过程。请注意,图像的步幅(从数据中读取的行)通常比实际宽度长,因为它会四舍五入到最接近4个字节的倍数。 - Nyerguds

6

3
上文链接中的第三个例子非常高效,直接解决了我的问题。 - ford
3
@RichardEverett:确实。网络档案馆来救了!已修复。 - jeffreypriebe

-1

我认为在这种情况下这是一个好方法:

    /// <summary>
    /// Change the RGB color to the Grayscale version
    /// </summary>
    /// <param name="color">The source color</param>
    /// <param name="volume">Gray scale volume between -255 - 255</param>
    /// <returns></returns>
    public virtual Color Grayscale(Color color, short volume = 0)
    {
        if (volume == 0) return color;
        var r = color.R;
        var g = color.G;
        var b = color.B;
        var mean = (r + g + b) / 3F;
        var n = volume / 255F;
        var o = 1 - n;
        return Color.FromArgb(color.A, Convert.ToInt32(r * o + mean * n), Convert.ToInt32(g * o + mean * n), Convert.ToInt32(b * o + mean * n));
    }

    public virtual Image Grayscale(Image source, short volume = 0)
    {
        if (volume == 0) return source;
        Bitmap bmp = new Bitmap(source);
        for (int x = 0; x < bmp.Width; x++)
            for (int y = 0; y < bmp.Height; y++)
            {
                Color c = bmp.GetPixel(x, y);
                if (c.A > 0)
                    bmp.SetPixel(x, y, Grayscale(c,volume));
            }
        return bmp;
    }

享受...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接