圖形圖像處理-之-誤差擴散 上篇

                        圖形圖像處理-之-誤差擴散 上篇
                     
[email protected]    2008.04.22

 

(2010.01.05 文章由2篇變成3篇,對誤差擴散的速度和質量作進一步探討!
  代碼也有一些更新,容納到我的圖像處理建議框架內,並提供源代碼下載!
  測試環境也有了變動;由AMD64x2 4200+(2.37G)DDR2 677(雙通道) 升級爲i7-920 DDR3 1333(三通道) )

(2008.12.01 修正一處bug,顏色誤差多累加了一次; 該錯誤由QueQuan發現,表示感謝! )


tag: 誤差擴散,真彩色到高彩色轉換,色階,減色,半色調

 
摘要: 在圖像的顏色轉換過程中,由於顏色值域的不同,轉換過程中可能會產生誤差; 誤差擴散算法通過將誤差傳遞到周圍像素而減輕其造成的視覺誤差。
上篇:簡單實現; 中篇:簡單的速度優化; 下篇: 更快的速度或更好的效果

 

(測試源代碼下載: https://github.com/sisong/demoForHssBlog )

   
正文:
  代碼使用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
//////////////////////////////////////////////////////////////
  
(文章的
中篇開始簡單的優化其速度)

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章