在图像处理程序开发中,常会遇到将一幅彩色图像转换成灰度图像的情况,笔者在最近的一个项目中便遇到了这点。经过一翻努力最终解决,想想有必要分享一下,于是便写下此文。在本文中,将向各位读者介绍两种实现这一变换的方法,这也是笔者先后使用的两种方法。本文的例子使用C#语言编写,使用的集成开发环境是Visual Studio 2005。
第一种,直接调用GetPixel/SetPixel方法。
我们都知道,图像在计算机中的存在形式是位图,也即一个矩形点阵,每一个点被称为一个像素。在这种方法中,我们通过GDI+中提供的GetPixel方法来读取像素的颜色,并加以计算,然后再使用SetPixel方法将计算后的颜色值应用到相应的像素上去,这样便可以得到灰度图像。
上边提到的“计算”便是指得到灰度图像的计算,其公式是:
r = (像素点的红色分量 + 像素点的绿色分量 + 像素点的蓝色分量) / 3
最后得到的r便是需要应用到原像素点的值。具体的编程实现也非常的简单,只需要遍历位图的每一个像素点,然后使用SetPixel方法将上边计算得到的值应用回去即可。主要代码如下所示:
Color currentColor;
int r;
Bitmap currentBitmap = new Bitmap(picBox.Image);
Graphics g = Graphics.FromImage(currentBitmap);
for (int w = 0; w < currentBitmap.Width; w++)
{
for (int h = 0; h < currentBitmap.Height; h++)
{
currentColor = currentBitmap.GetPixel(w, h);
r = (currentColor.R + currentColor.G + currentColor.B) / 3;
currentBitmap.SetPixel(w, h, Color.FromArgb(r, r, r));
}
}
g.DrawImage(currentBitmap, 0, 0);
picBox.Image = currentBitmap;
g.Dispose();
以上代码非常简单,不需要做太多的解释。需要注意的是,在使用SetPixel方法的时候,其三色分量值均为我们公式计算得到的结果r。
第二种,使用ColorMatrix 类
如果读者亲自测试过第一种方式,就会发现通过循环遍历位图所有像素,并使用SetPixel方法来修改每个像素的各颜色分量是非常耗时的。而现在介绍的第二种方法则是一种更好的实现方式――使用ColorMatrix类。
在介绍具体的实现之前,有必要先向读者介绍一下相关的背景知识。在GDI+中,颜色使用32位来保存,红色、绿色、蓝色和透明度分别占8位,因此每个分量可以有28=256(0~255)种取值。这样一来,一个颜色信息就可以用一个向量 (Red,Green,Blue,Alpha) 来表示,例如不透明的红色可以表示成为(255,0,0,255)。向量中的Alpha值用来表示颜色的透明度,0 表示完全透明,255 表示完全不透明。到这里读者朋友可能应该想到了,我们只需要按照一定的规则改变这些向量里各个分量的值,便可以得到各种各样的颜色变换效果,所以我们获得灰度图这个要求也就能够实现了。
现在关键问题便是按照什么规则来改变各分量值。在上边介绍的第一种方式中我们提到了计算灰度图像的公式,其实它还有另外一个表示方式,如下:
r = 像素点的红色分量×0.299 + 像素点的绿色分量×0.587 + 像素点的蓝色分量×0.114
这一公式便是我们的规则。我们只需要对每一个颜色向量做上边的变化即可。这里的变换就需要用到ColorMatrix类,此类定义在System.Drawing.Imaging名字空间中,它定义了一个5×5的数组,用来记录将和颜色向量进行乘法计算的值。ColorMatrix对象配合ImageAttributes类一起使用,实际上GDI+里的颜色变换就是通过ImageAttributes对象的SetColorMatrix 进行的。
第二种的主要实现代码如下:
Bitmap currentBitmap = new Bitmap(picBox.Image);
Graphics g = Graphics.FromImage(currentBitmap);
ImageAttributes ia = new ImageAttributes();
float[][] colorMatrix = {
new float[] { 0.299f , 0.299f , 0.299f , 0, 0},
new float[] { 0.587f , 0.587f , 0.587f , 0, 0},
new float[] { 0.114f , 0.114f , 0.114f , 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}};
ColorMatrix cm = new ColorMatrix(colorMatrix);
ia.SetColorMatrix(cm, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(currentBitmap, new Rectangle(0, 0, currentBitmap.Width, currentBitmap.Height), 0, 0, currentBitmap.Width, currentBitmap.Height, GraphicsUnit.Pixel, ia);
picBox.Image = currentBitmap;
g.Dispose();
细心的读者可能会问,明明颜色是由4个分量组成的,那么与之相乘的矩阵也应该是一个4×4的矩阵啊,为什么这里定义的颜色变换矩阵却是5×5的呢?这个问题可以参考MSDN上的一篇文章(《使用颜色矩阵对单色进行变换》)便可得解。
请读者朋友们输入以上代码并编译执行。大家会发现其变换速度极快,几乎是瞬间完成。其实,只要知道了矩阵各行、列必要的参数值,那么使用ColorMatrix来实现颜色变换是非常方便的。诸如让某色透明这样的功能用这种方式实现起来也是非常方便高效的。
上边简要地介绍了两种获得灰度图像的方法。在实际开发中,使用第二种方式远远优于第一种,其在运算速度方面的优势,是第一种远远没法比的。