首先来看一下彩色图和灰度图的特点。
在计算机中使用最多的 RGB 彩色空间,分别对应红、绿、蓝三种颜色;通过调配三个分量的比例来组成各种颜色。一般可以使用 1 、 2 、 4 、 8、 16 、 24 、 32 位来存储这三颜色,不过现在一个分量最大是用 8 位来表示,最大值是 255 ,对于 32 位的颜色,高 8 位是用来表示通明度的。彩色图一般指 16 位以上的图。灰度图有一个特殊之处就是组成颜色的三个分量相等;而一般灰度图是 8 位以下。
在彩色电视机系统中,通常使用一种叫 YUV 的色彩空间,其中 Y 表示亮度信号;也就是这个 YUV 空间解决了彩色电视机和黑白电视机的兼容问题。
对于人眼来说,亮度信号是最敏感的,如果将彩色图像转换为灰度图像,仅仅需要转换保存亮度信号就可以。
从 RGB 到 YUV 空间的 Y 转换公式为:
Y = 0.299R+0.587G+0.114B
在 WINDOWS 中,表示 16 位以上的图和以下的图有点不同; 16 位以下的图使用一个调色板来表示选择具体的颜色,调色板的每个单元是 4 个字节,其中一个透明度;而具体的像素值存储的是索引,分别是 1 、 2 、 4 、 8 位。 16 位以上的图直接使用像素表示颜色。
那么如何将彩色图转换为灰度图呢?
灰度图中有调色板,首先需要确定调色板的具体颜色取值。我们前面提到了,灰度图的三个分量相等。
当转换为 8 位的时候,调色板中有 256 个颜色,每个正好从 0 到 255 个,三个分量都相等。
当转换为 4 位的时候,调色板中 16 个颜色,等间隔平分 255 个颜色值,三个分量都相等。
当转换为 2 位的时候,调色板中 4 个颜色,等间隔平分 255 个颜色,三个分量相等。
当转换为 1 位的时候,调色板中两个颜色,是 0 和 255 ,表示黑和白。
将彩色转换为灰度时候,按照公式计算出对应的值,该值实际上是亮度的级别;亮度从 0 到 255 ;由于不同的位有不同的亮度级别,所以 Y 的具体取值如下:
Y = Y/ (1<<(8- 转换的位数 ));
最后一点需要注意,得到 Y 值存放方式是不同的;分别用对应的位数来存储对应的 Y 值。
这里是代码片段:
计算调色板和 Y 的值代码。
2
3 int width,
4
5 int height,
6
7 DWORD & dwGraySize,
8
9 int nToBit)
10
11 {
12
13 DWORD nRowLen = TS_4BYTESALIGN(width * nToBit);
14
15 DWORD nColorTableSize = (( 1 << nToBit) * sizeof (RGBQUAD));
16
17 DWORD nColorNum = 1 << nToBit;
18
19 dwGraySize = nRowLen * height + nColorTableSize;
20
21 LPBYTE lpNewImgBuf = NULL;
22
23 BYTE r,g,b;
24
25 float y;
26
27
28
29 lpNewImgBuf = new BYTE[dwGraySize];
30
31 LPBYTE lpPixels = (LPBYTE)(lpNewImgBuf + nColorTableSize);
32
33 LPRGBQUAD lpvColorTable = (LPRGBQUAD)lpNewImgBuf;
34
35 memset(lpNewImgBuf, 0 ,dwGraySize);
36
37
38
39 for ( int i = 0 ;i < nColorNum;i ++ )
40
41 {
42
43 if (nToBit == 8 )
44
45 {
46
47 ( * (lpvColorTable)).rgbBlue = (BYTE)i;
48
49 ( * (lpvColorTable)).rgbGreen = (BYTE)i;
50
51 ( * (lpvColorTable)).rgbRed = (BYTE)i;
52
53 }
54
55 else if (nToBit == 4 )
56
57 {
58
59 ( * (lpvColorTable)).rgbBlue = (BYTE)(i << ( 8 - nToBit)) + i;
60
61 ( * (lpvColorTable)).rgbGreen = (BYTE)(i << ( 8 - nToBit)) + i;
62
63 ( * (lpvColorTable)).rgbRed = (BYTE)(i << ( 8 - nToBit)) + i;
64
65 }
66
67 else if (nToBit == 2 )
68
69 {
70
71 ( * (lpvColorTable)).rgbBlue = (BYTE)( 255 / 3 ) * i;
72
73 ( * (lpvColorTable)).rgbGreen = (BYTE)( 255 / 3 ) * i;
74
75 ( * (lpvColorTable)).rgbRed = (BYTE)( 255 / 3 ) * i;
76
77 }
78
79 else if (nToBit == 1 )
80
81 {
82
83 ( * (lpvColorTable)).rgbBlue = (BYTE) 255 * i;
84
85 ( * (lpvColorTable)).rgbGreen = (BYTE) 255 * i;
86
87 ( * (lpvColorTable)).rgbRed = (BYTE) 255 * i;
88
89 }
90
91
92
93 ( * (lpvColorTable)).rgbReserved = 0 ;
94
95 lpvColorTable ++ ;
96
97 }
98
99
100
101 LPBYTE lpOldImage = lpByte;
102
103 LPBYTE lpTempPixel = lpPixels;
104
105 int loops = 0 ;
106
107 int nStop = 0 ;
108
109 for ( long h = 0 ;h < height;h ++ )
110
111 {
112
113 for ( long w = 0 ;w < width;w ++ )
114
115 {
116
117 b = (unsigned char )( * lpOldImage ++ );
118
119 g = (unsigned char )( * lpOldImage ++ );
120
121 r = (unsigned char )( * lpOldImage ++ );
122
123
124
125 y = ( float )(r * 0.299 + g * 0.587 + b * 0.114 ) ;
126
127 BYTE bVal = (BYTE)y >> ( 8 - nToBit);
128
129 SetPixelValueByBits(lpTempPixel,nToBit,loops,(BYTE)bVal);
130
131 // ErrorDiffuse(lpPixels,nToBit,loops,((int)y) - (bVal<<(8-nToBit)),
132
133 // w,h,nRowLen,dwGraySize-nColorTableSize);
134
135 }
136
137 }
138
139
140
141 return lpNewImgBuf;
142
143 }
144
145
下面是设置像素值的代码:
2
3 {
4
5 switch (nBits)
6
7 {
8
9 case 8 :
10
11 * (lpByte ++ ) = value;
12
13 break ;
14
15 case 4 :
16
17 {
18
19 if (loops)
20
21 {
22
23 loops = 0 ;
24
25 BYTE bVal = ( * lpByte) & 0xF0 ;
26
27 value &= 0x0F ;
28
29 bVal = (bVal >> 4 ) + value;
30
31 if (bVal > 0x0F ) bVal = 0x0F ;
32
33 ( * lpByte) <<= 4 ;
34
35 ( * lpByte) += bVal;
36
37 lpByte ++ ;
38
39 }
40
41 else
42
43 {
44
45 value &= 0x0F ;
46
47 ( * lpByte) += value;
48
49 if (( * lpByte) > 0x0F ) ( * lpByte) = 0x0F ;
50
51 loops = 1 ;
52
53 }
54
55 }
56
57 break ;
58
59 case 2 :
60
61 {
62
63 value &= 0x03 ;
64
65 ( * lpByte) += value;
66
67 if (loops != 3 )
68
69 {
70
71 ( * lpByte) <<= 2 ;
72
73 loops ++ ;
74
75 }
76
77 else
78
79 {
80
81 loops = 0 ;
82
83 lpByte ++ ;
84
85 }
86
87 }
88
89 break ;
90
91 case 1 :
92
93 {
94
95 value &= 0x01 ;
96
97 ( * lpByte) += value;
98
99 if (loops != 7 )
100
101 {
102
103 ( * lpByte) <<= 1 ;
104
105 loops ++ ;
106
107 }
108
109 else
110
111 {
112
113 loops = 0 ;
114
115 lpByte ++ ;
116
117 }
118
119 }
120
121 break ;
122
123 }
124
125 }
126
有一点需要说明的:
在计算 Y 值的时候,使用的整数除法,这是有误差的,为了消除误差,需要采用误差扩散的算法,也就是将该误差值向其邻近的像素点扩散,当然按照一定的比例来分配;例如:整除之后,余数是 5 ,采用 3/2/3 的策略,就是,右边像素和正下面的像素各占 3/8 ,而右下角的像素占 2/8 。在这方面我发现 ACDSEE 做的很好,其图像的渐进做的很好。
转:http://www.cppblog.com/windcsn/archive/2006/07/27/Grayscale.html