[email protected] 2008.04.22
tag: 误差扩散,真彩色到高彩色转换
摘要: 在图像的颜色转换过程中,由于颜色值域的不同,转换过程中可能会产生误差;
误差扩散算法通过将误差传递到周围像素而减轻其造成的视觉误差。
正文:
代码使用C++,编译器:VC2005
测试平台:(CPU:AMD64x2 4200+(2.37G); 内存:DDR2 677(双通道); 编译器:VC2005)
A:程序将把一张真彩色图片转换成高彩色图片作为例子,颜色和图片的数据定义:
typedef unsigned
char
TUInt8;
//
[0..255]
typedef unsigned
short
TUInt16;
typedef unsigned
long
TUInt32;
struct
TARGB32
//
32 bit color
{
TUInt8 b,g,r,a;
//
a is alpha
};
struct
TPicRegion
//
一块颜色数据区的描述,便于参数传递
{
TARGB32
*
pdata;
//
颜色数据首地址
long
byte_width;
//
一行数据的物理宽度(字节宽度);注意: abs(byte_width)有可能大于等于width*sizeof(TARGB32);
unsigned
long
width;
//
像素宽度
unsigned
long
height;
//
像素高度
};
//
那么访问一个点的函数可以写为:
inline TARGB32
&
Pixels(
const
TPicRegion
&
pic,
const
long
x,
const
long
y)
{
return
( (TARGB32
*
)((TUInt8
*
)pic.pdata
+
pic.byte_width
*
y) )[x];
}
//高彩色颜色和图片数据定义 (
struct
TRGB16_555
//
16bit 5:5:5 high color
{
TUInt16 b:
5
;
TUInt16 g:
5
;
TUInt16 r:
5
;
TUInt16 x:
1
;
};
struct
TPicRegion_RGB16_555
//
一块颜色数据区的描述,便于参数传递
{
TRGB16_555
*
pdata;
//
颜色数据首地址
long
byte_width;
//
一行数据的物理宽度(字节宽度)
unsigned
long
width;
//
像素宽度
unsigned
long
height;
//
像素高度
};
inline TRGB16_555
&
Pixels(
const
TPicRegion_RGB16_555
&
pic,
const
long
x,
const
long
y)
{
return
( (TRGB16_555
*
)((TUInt8
*
)pic.pdata
+
pic.byte_width
*
y) )[x];
}
例子中使用的16bit高彩色的RGB颜色编码为555; 常见的编码方式还有565和655,某些程序
里面可能还会使用4:4:4:4 (4比特Alpha通道); (提示:利用宏或泛型的方式可以用一个函数
实现同时支持这些格式)
B:真彩色图片直接转换成高彩色图片的简单实现
inline TRGB16_555 ToColor16(
const
TARGB32
&
color){
TRGB16_555 result;
result.r
=
color.r
>>
3
;
result.g
=
color.g
>>
3
;
result.b
=
color.b
>>
3
;
return
result;
}
void
CvsPic32To16_0(
const
TPicRegion_RGB16_555
&
dst,
const
TPicRegion
&
src){
for
(
long
y
=
0
;y
<
src.height;
++
y){
for
(
long
x
=
0
;x
<
src.width;
++
x){
Pixels(dst,x,y)
=
ToColor16(Pixels(src,x,y));
}
}
}
来看一下函数效果
源图片(800x600):
转换后图片:
可以看到,颜色位数的降低,很多区域都产生了失真的色块
速度测试:
//////////////////////////////////////////////////////////////
//CvsPic32To16_0 204.5 FPS
//////////////////////////////////////////////////////////////
C:对直接转换函数的简单速度优化(功能一样)
inline TUInt16 ToColor16_1(
const
TARGB32
&
color){
return
((color.r
>>
3
)
<<
10
)
|
((color.g
>>
3
)
<<
5
)
|
(color.b
>>
3
);
}
void
CvsPic32To16_1(
const
TPicRegion_RGB16_555
&
dst,
const
TPicRegion
&
src){
TUInt16
*
pDst
=
(TUInt16
*
)dst.pdata;
const
TARGB32
*
pSrc
=
src.pdata;
const
long
width
=
src.width;
for
(
long
y
=
0
;y
<
src.height;
++
y){
for
(
long
x
=
0
;x
<
width;
++
x){
pDst[x]
=
ToColor16_1(pSrc[x]);
}
(TUInt8
*&
)pDst
+=
dst.byte_width;
(TUInt8
*&
)pSrc
+=
src.byte_width;
}
}
速度测试:
//////////////////////////////////////////////////////////////
//CvsPic32To16_1 507.9 FPS
//////////////////////////////////////////////////////////////
(当然,该函数还可以继续优化的,比如使用MMX、SSE等指令,可以得到更快的速度;)
D:误差扩散的颜色转换函数实现
转换过程中,将产生的转换误差,按一定的系数向右和向下传递(这样写代码比较容易);
我使用的误差传递系数为:
* 2
1 1 0 /4
其他一些常见的误差传递模板(也可以自己设定合适的模板系数系数),可以尝试一下其转换效果
* 3
0 3 2 /8
* 7
3 5 1 /16
* 8 4
2 4 8 4 2
1 2 4 2 1 /42
我使用了一个较为简单的模板,为质量、速度、额外空间占用做了折中;
简单的实现:
struct
TErrorColor_0{
float
dR;
float
dG;
float
dB;
};
inline
long
getBestRGB16_555Color_0(
const
float
wantColor){
float
result
=
wantColor
*
(
31.0
/
255
);
if
(result
<=
0
)
return
0
;
else
if
(result
>=
31
)
return
31
;
else
return
(
long
)result;
}
void
CvsPic32To16_ErrorDiffuse_Line_0(TUInt16
*
pDst,
const
TARGB32
*
pSrc,
long
width,TErrorColor_0
*
PHLineErr){
TErrorColor_0 HErr;
HErr.dR
=
0
; HErr.dG
=
0
; HErr.dB
=
0
;
PHLineErr[
-
1
].dB
=
0
; PHLineErr[
-
1
].dG
=
0
; PHLineErr[
-
1
].dR
=
0
;
for
(
long
x
=
0
;x
<
width;
++
x)
{
//
cB,cG,cR为应该显示的颜色
float
cB
=
(pSrc[x].b
+
HErr.dB
*
2
+
PHLineErr[x].dB
+
PHLineErr[x
-
1
].dB);
float
cG
=
(pSrc[x].g
+
HErr.dG
*
2
+
PHLineErr[x].dG
+
PHLineErr[x
-
1
].dG);
float
cR
=
(pSrc[x].r
+
HErr.dR
*
2
+
PHLineErr[x].dR
+
PHLineErr[x
-
1
].dR);
//
rB,rG,rR为转换后的颜色(也就是实际显示颜色)
long
rB
=
getBestRGB16_555Color_0(cB);
long
rG
=
getBestRGB16_555Color_0(cG);
long
rR
=
getBestRGB16_555Color_0(cR);
pDst[x]
=
rB
|
(rG
<<
5
)
|
(rR
<<
10
);
//
计算两个颜色之间的差异的1/4
HErr.dB
=
(cB
-
(rB
*
(
255.0
/
31
)))
*
(
1.0
/
4
);
HErr.dG
=
(cG
-
(rG
*
(
255.0
/
31
)))
*
(
1.0
/
4
);
HErr.dR
=
(cR
-
(rR
*
(
255.0
/
31
)))
*
(
1.0
/
4
);
PHLineErr[x
-
1
].dB
+=
HErr.dB;
PHLineErr[x
-
1
].dG
+=
HErr.dG;
PHLineErr[x
-
1
].dR
+=
HErr.dR;
PHLineErr[x]
=
HErr;
}
}
void
CvsPic32To16_ErrorDiffuse_0(
const
TPicRegion_RGB16_555
&
dst,
const
TPicRegion
&
src){
TUInt16
*
pDst
=
(TUInt16
*
)dst.pdata;
const
TARGB32
*
pSrc
=
src.pdata;
const
long
width
=
src.width;
TErrorColor_0
*
_HLineErr
=
new
TErrorColor_0[width
+
2
];
for
(
long
x
=
0
;x
<
width
+
2
;
++
x){
_HLineErr[x].dR
=
0
;
_HLineErr[x].dG
=
0
;
_HLineErr[x].dB
=
0
;
}
TErrorColor_0
*
HLineErr
=&
_HLineErr[
1
];
for
(
long
y
=
0
;y
<
src.height;
++
y){
CvsPic32To16_ErrorDiffuse_Line_0(pDst,pSrc,width,HLineErr);
(TUInt8
*&
)pDst
+=
dst.byte_width;
(TUInt8
*&
)pSrc
+=
src.byte_width;
}
delete[]_HLineErr;
}
函数效果:
和上面的直接转换效果对比,色深一样但质量明显好了很多:)
(可以放大该图片来看看,对颜色误差的传递会有一个更好的认识)
速度测试:
//////////////////////////////////////////////////////////////
//CvsPic32To16_ErrorDiffuse_0 33.6 FPS
//////////////////////////////////////////////////////////////