在影像處理中,我們經常需要將真彩色映像轉換為黑白映像。嚴格的講應該是灰階圖,因為真正的黑白映像是二色,即只有純黑,純白二色。開始之前,我們先簡單補充一下電腦中映像的表示原理。電腦中的映像大致可以分成兩類:位元影像(Bitmap)和向量圖(Metafile)。 位元影像可以視為一個二維的網格,整個映像就是由很多個點組成的,點的個數等於位元影像的寬乘以高。每個點被稱為一個像素點,每個像素點有確定的顏色,當很多個像 素合在一起時就形成了一幅完整的映像。我們通常使用的映像大部分都是位元影像,如數位相機拍攝的照片,都是位元影像。因為位元影像可以完美的表示映像的細節,能較好的 還原映像的原景。但位元影像也有缺點:第一是體積比較大,所以人們開發了很多壓縮映像格式來儲存位元影像映像,目前應用最廣的是JPEG格式,在WEB上得到了廣泛應用,另外還有GIF,PNG等 等。第二是位元影像在放大時,不可避免的會出現“鋸齒”現象,這也由位元影像的本質特點決定的。所以在現實中,我們還需要使用到另一種映像格式:向量圖。同位元影像不 同,向量圖同位元影像的原理不同,向量圖是利用數學公式通過圓,線段等繪製出來的,所以不管如何放大都不會出現變形,但向量圖不能描述非常複雜的映像。所以矢 量圖都是用來描述圖形圖案,各種CAD軟體等等都是使用向量格式來儲存檔案的。
在講解色彩轉換之前,我們要先對位元影像的顏色表示方式做一瞭解。位元影像中通常是用RGB三色方式來表示顏色的(位元很少時要使用調色盤) 。所以每個像素採用不同的位元,就可以表示出不同數量的顏色。如所示:
|
每像素的位元 |
一個像素可分配到的顏色數量 |
|
1 |
2^1 = 2 |
|
2 |
2^2 = 4 |
|
4 |
2^4 = 16 |
|
8 |
2^8 = 256 |
|
16 |
2^16 = 65,536 |
|
24 |
2^24 = 16,777,216 |
從中我們可以看出,當使用24位色(3個位元組)時,我們可以得到1600多萬種顏色,這已經非常豐富了
,應該已接近人眼所能分辨的顏色了。現在電腦中使用最多的就是24位色,別外在GDI+中還有一種32位色,多出來的一個通道用來描述Alpha,即透明分量。
24位色中3個位元組分別用來描述R,G,B三種顏色分量,我們看到這其中是沒有亮度分量的,這是因為在RGB表示方式中,亮度也是直接可以從顏色分量中得到的,每一顏色分量值的範圍都是從0到255, 某一顏色分量的值越大,就表示這一分量的亮度值越高,所以255表示最亮,0表示最暗。那麼一個真彩色像素點轉換為灰階圖時它的亮度值應該是多少呢,首先我們想到的平均值,即將R+G+B/3。但現實中我們使用的卻是如下的公式:
Y = 0.299R+0.587G+0.114B
這個公式通常都被稱為心理學灰階公式。這裡面我們看到綠色分量所佔比重最大。因為科學家發現使用上述公式進行轉換時所得到的灰階圖最接近人眼對灰階圖的感覺。
因為灰階圖中顏色數量一共只有256種(1個位元組),所以轉換後的映像我們通常儲存為8位格式而不是24位格式,這樣比較節省空間的。而8位元影像像是使用調色盤方式來儲存顏色的。而不是直接儲存顏色值。調色盤中可以儲存256顏色,所以可以正好可以將256種灰階顏色儲存到調色版中。代碼如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
namespace ConsoleApplication2
{
class Program
{
unsafe static void Main(string[] args)
{
Bitmap img = (Bitmap)Image.FromFile(@"E:\My Documents\My Pictures\cherry_blossom_1002.jpg");
Bitmap bit = new Bitmap(img.Width, img.Height, PixelFormat.Format8bppIndexed);
BitmapData data = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
byte* bp = (byte*)data.Scan0.ToPointer();
BitmapData data2 = bit.LockBits(new Rectangle(0, 0, bit.Width, bit.Height), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
byte* bp2 = (byte*)data2.Scan0.ToPointer();
for (int i = 0; i != data.Height; i++)
{
for (int j = 0; j != data.Width; j++)
{
//0.3R+0.59G+0.11B
float value = 0.11F * bp[i * data.Stride + j * 3] + 0.59F * bp[i * data.Stride + j * 3 + 1] + 0.3F * bp[i * data.Stride + j * 3 + 2];
bp2[i * data2.Stride + j] = (byte)value;
}
}
img.UnlockBits(data);
bit.UnlockBits(data2);
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(@"E:\TEMP\bb.jpeg", ImageFormat.Jpeg);
img.Dispose();
bit.Dispose();
}
}
}
代碼中我使用了指標直接操作位元影像資料,同樣的操作要比使用GetPixel, SetPixel快非常多。我們要感謝微軟在C#中保留了這一強有力的工具。