如何將真彩色圖轉換為各種灰度圖

pamxy發表於2013-03-23

轉自:http://www.cppblog.com/windcsn/archive/2006/07/27/Grayscale.html

首先來看一下彩色圖和灰度圖的特點。

在計算機中使用最多的 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


相關文章