图形图像处理-之-误差扩散 上篇

来源:互联网 发布:淘宝京东唯品会 编辑:程序博客网 时间:2024/05/23 16:55
 HouSisong@GMail.com   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
//////////////////////////////////////////////////////////////

原创粉丝点击