首先来看一下彩色图和灰度图的特点。
在计算机中使用最多的
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
的值代码。
1
LPBYTE CColorDeepChange::ConvertTo8Gray(LPBYTE lpByte,
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
下面是设置像素值的代码:
1
void
CColorDeepChange::SetPixelValueByBits(LPBYTE
&
lpByte,
int
nBits,
int
&
loops,BYTE value)
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
做的很好,其图像的渐进做的很好。
源码下载:ImageConvert.zip