在图像处理中,我们经常需要将真彩色图像转换为黑白图像。黑白图像即为灰度图,即只有纯黑,纯白两种颜色。
计算机中的图像大致可以分为两类:位图(Bitmap)和矢量图(Metafile)。位图可以视为一个二维的网格,整个图像就是由很多个点组成的,点的个数等于位图的宽乘以高。每个点被称为一个像素点,每个像素点由确定的颜色,当很多个像素合在一起时就形成了一幅完整的图像。
我们通常使用的图像大部分是位图,因为位图可以完美的表示图像的细节,能较好的还原图像的原景。但位图也有缺点:第一是体积比较大,第二是位图在放大时,不可避免的会出现“锯齿”现象,这也是由位图的本质特点决定的。所以在现实中,我们还需要使用到另一种图像格式:矢量图。同位图的原理不同,矢量图是利用数学公式通过圆,线段等绘制出来的,所以不管如何放大都不会出现变形,但矢量图不能描述非常复杂的图像。所以矢量图都是用来描述图形图案。
在位图中,通常是用RGB三色方式来表示颜色的(位数很少时要使用调色板)所以每个像素采用不同的位数,就可以表示出不同数量的颜色。当我们使用24位色时(3个字节),我们可以得到2的24次方(1600多万种颜色)。现在计算机使用最多的是24位色,但是在GDI+中还有32位色,多出来的一个通道描述Alpha,即透明分量。
在我们保存灰度图时,灰度图中的颜色数量一共只有256种(1个字节),所以转换后的图像我们通常保存为8位格式而不是24位格式,这样比较节省空间。而8位图像是使用调色板方式来保存颜色的。而不是直接保存颜色值。调色板中可以保存256种颜色,所以可以正好把256种灰度颜色保存到调色板中。
代码如下:
// 通过指针将图像转换为灰度图
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
// 提供一个8位图像的容器
Bitmap bit = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format8bppIndexed);
BitmapData bmpData1 = bit.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
// 字节初始值为0
byte temp = 0;
unsafe
{
// 获取其指针首地址位
byte* ptr = (byte*)bmpData.Scan0;
byte* ptr1 = (byte*)bmpData1.Scan0;
for (int i = 0; i < bmpData.Height; i++)
{
for (int j = 0; j < bmpData.Width; j++)
{
temp = (byte)(0.299 * ptr[2] + 0.587 * ptr[1] + 0.114 * ptr[0]);
// R=G=B
ptr[0] = ptr[1] = ptr[2]=temp;
// 给灰度图像赋值
ptr1[i * bmpData1.Stride + j] = temp;
// 指向下一个像素
ptr += 3;
}
// 指向下一行
ptr += bmpData.Stride - bmpData.Width * 3;
}
// 解锁该位图图像
bmp.UnlockBits(bmpData);
bit.UnlockBits(bmpData1);
// 将灰度图像保存为8位图像
ColorPalette palette = bit.Palette;
for (int i = 0; i < palette.Entries.Length; i++)
{
palette.Entries[i] = Color.FromArgb(i, i, i);
}
bit.Palette = palette;
bit.Save("1.bmp", ImageFormat.Bmp);
// 释放资源
bmp.Dispose();
bit.Dispose();