现在的位置: 首页 > 综合 > 正文

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

2013年10月04日 ⁄ 综合 ⁄ 共 5587字 ⁄ 字号 评论关闭

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

HouSisong@GMail.com

   2008.04.22

 

(2010.01.05 文章由2篇变成3篇,对误差扩散的速度和质量作进一步探讨!
  代码也有一些更新,容纳到我的图像处理建议框架内,并提供源代码下载!
  测试环境也有了变动;由AMD64x2 4200+(2.37G)DDR2 677(双通道) 升级为i7-920 DDR3 1333(三通道) )

(2008.12.01 修正一处bug,颜色误差多累加了一次; 该错误由QueQuan发现,表示感谢! )

tag: 误差扩散,真彩色到高彩色转换,色阶,减色,半色调

 
摘要: 在图像的颜色转换过程中,由于颜色值域的不同,转换过程中可能会产生误差; 误差扩散算法通过将误差传递到周围像素而减轻其造成的视觉误差。
上篇:简单实现; 中篇:简单的速度优化; 下篇: 更快的速度或更好的效果

 

(测试源代码下载: http://cid-10fa89dec380323f.skydrive.live.com/self.aspx/.Public/ErrorDiffuse.zip
)

   
正文:
  代码使用C++,编译器:VC2005
  测试平台:(CPU:i7-920(3.44G); 内存:DDR3 1333(三通道); 编译器:VC2005)


  
A:程序将把一张真彩色图片转换成高彩色图片作为例子,颜色和图片的数据定义:

 

//高彩色颜色和图片数据定义 (

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  Color32 &  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  TPixels32Ref &  src){
     for  ( long  y = 0 ;y < src.height; ++ y){
         for  ( long  x = 0 ;x < src.width; ++ x){
            Pixels(dst,x,y) = ToColor16(src.pixels(x,y));
        }
    }
}

来看一下函数效果

   源图片(800x600):

  

   转换后图片:

 

 

 

可以看到,颜色位数的降低,很多区域都产生了失真的色块

 

 

 

 

 

 

速度测试:
//////////////////////////////////////////////////////////////
//CvsPic32To16_0                  386.95  FPS 
//////////////////////////////////////////////////////////////

C:对直接转换函数的简单速度优化(功能一样)

inline UInt16 ToColor16_1( const  Color32 &  color){
     return  ((color.r >> 3 ) << 10 ) | ((color.g >> 3 ) << 5 ) | (color.b >> 3 );
}
void  CvsPic32To16_1( const  TPicRegion_RGB16_555 &  dst, const  TPixels32Ref &  src){
    UInt16 *  pDst = (UInt16 * )dst.pdata;
     const  Color32 *  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]);
        }
        (UInt8 *& )pDst += dst.byte_width;
        (UInt8 *& )pSrc += src.byte_width;
    }
}


速度测试:
//////////////////////////////////////////////////////////////
//CvsPic32To16_1                 1221.00  FPS 
//////////////////////////////////////////////////////////////
  (当然,该函数还可以继续优化的,比如使用MMX、SSE等指令,可以得到更快的速度;)

D:误差扩散的颜色转换函数实现
   转换过程中,将产生的转换误差,按一定的系数向右和向下传递(这样写代码比较容易); 
我使用的误差传递系数为:
  * 2
1 1 0    /4

(

该模板的解释:
 

算法需要整幅图片按照从左到右,从上到下的顺序依次对每个像素运用该模板
*代表当前转换处理的像素 假设这个点转换颜色后,颜色误差为D
那么,将这个误差传递到周围的像素  将D*2/4的颜色量加到右边的像素 将D*1/4的颜色量加到左下的像素和下面的像素
)

其他一些常见的误差传递模板(也可以自己设定合适的模板系数系数),可以尝试一下其转换效果
  
 

* 3
0 3 2   /8
  
 

* 7
3 5 1   /16
  
   

* 8 4
2 4 8 4 2
1 2 4 2 1  /42

 

我使用了一个较为简单的模板,为质量、速度、额外空间占用做了折中;
简单的实现:

/////////////////////////////////////////////////////////////////////
//误差传递系数为:
//  * 2
//1 1 0    /4

     struct  TErrorColor_f{
         float  dR;
         float  dG;
         float  dB;
    };

    inline  long  getBestRGB16_555Color_f( const   float  wantColor){
         float  result = wantColor * ( 31.0f / 255 );
         if  (result <= 0 )
             return   0 ;
         else   if  (result >= 31 )
             return   31 ;
         else
             return  ( long )result;
    }

     void  CvsPic32To16_ErrorDiffuse_Line_0(UInt16 *  pDst, const  Color32 *  pSrc, long  width,TErrorColor_f *  PHLineErr){
        TErrorColor_f 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 );
             float  cG = (pSrc[x].g + HErr.dG * 2 + PHLineErr[x].dG );
             float  cR = (pSrc[x].r + HErr.dR * 2 + PHLineErr[x].dR );
             // rB,rG,rR为转换后的颜色(也就是实际显示颜色)
             long  rB = getBestRGB16_555Color_f(cB);
             long  rG = getBestRGB16_555Color_f(cG);
             long  rR = getBestRGB16_555Color_f(cR);
            pDst[x] =  rB | (rG << 5 ) | (rR << 10 );
             // 计算两个颜色之间的差异的1/4
            HErr.dB = (cB - (rB * ( 255.0f / 31 ))) * ( 1.0f / 4 );
            HErr.dG = (cG - (rG * ( 255.0f / 31 ))) * ( 1.0f / 4 );
            HErr.dR = (cR - (rR * ( 255.0f / 31 ))) * ( 1.0f / 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  TPixels32Ref &  src){
    UInt16 *  pDst = (UInt16 * )dst.pdata;
     const  Color32 *  pSrc = src.pdata;
     const   long  width = src.width;

    TErrorColor_f *  _HLineErr = new  TErrorColor_f[width + 2 ];
     for  ( long  x = 0 ;x < width + 2 ; ++ x){
        _HLineErr[x].dR = 0 ;
        _HLineErr[x].dG = 0 ;
        _HLineErr[x].dB = 0 ;
    }
    TErrorColor_f *  HLineErr =& _HLineErr[ 1 ];

     for  ( long  y = 0 ;y < src.height; ++ y){
        CvsPic32To16_ErrorDiffuse_Line_0(pDst,pSrc,width,HLineErr);
        (UInt8 *& )pDst += dst.byte_width;
        (UInt8 *& )pSrc += src.byte_width;
    }

    delete[]_HLineErr;
}

 

函数效果:
 
 

 

  和上面的直接转换效果对比,色深一样但质量明显好了很多:)

(可以放大该图片来看看,对颜色误差的传递会有一个更好的认识)

速度测试:
//////////////////////////////////////////////////////////////
//CvsPic32To16_ErrorDiffuse_0      97.13  FPS
//////////////////////////////////////////////////////////////
  
(文章的

中篇开始简单的优化其速度)

 

 

抱歉!评论已关闭.