影像處理中,大部分的處理方法都需要事先把彩色圖轉換成灰階圖才能進行相關的計算、識別。 彩色圖轉換灰階圖的原理如下: 我們知道彩色位元影像是由R/G/B三個分量組成,其檔案儲存體格式為 BITMAPFILEHEADER+BITMAPINFOHEADER,緊跟後面的可能是: 如果是24位真彩圖,則每個點是由三個位元組分別表示R/G/B,所以這裡直接跟著映像的色多媒體訊息息; 如果是8位(256色),4位(16色),1位(單色)圖,則緊跟後面的是調色盤資料,一個RGBQUAD類型的數組,其長度由BITMAPINFOHEADER.biClrUsed來決定。 然後後面緊跟的才是映像資料(24位元影像是真實的映像資料,其他的則是調色盤的索引資料)。 灰階圖是指只含亮度資訊,不含色多媒體訊息息的圖象,就象我們平時看到的黑白照片:亮度由暗到明,變化是連續的。因此,要表示灰階圖,就需要把亮度值進行 量化。通常劃分成0到255共256個層級,其中0最暗(全黑),255最亮(全白)。在表示顏色的方法中,除了RGB外,還有一種叫YUV的表示方法, 應用也很多。電視訊號中用的就是一種類似於YUV的顏色表示方法。在這種表示方法中,Y分量的物理含義就是亮度,Y分量包含了灰階圖的所有資訊,只用Y分 量就能完全能夠表示出一幅灰階圖來。 從 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- 轉換的位元 )); 所以,我們要轉化成灰階圖,並且儲存成一幅可以看到的映像,需要做如下轉換: 16位以上的映像不帶調色盤,只需要把映像資料按每個點的位元都轉換成相同的灰階值即可 16位以下的映像,則需要修改調色盤的數值,並且按照每個點所佔位元修改灰階值索引即可。 |
以下是256色圖轉換成灰階圖範例程式碼:
/****************************************************************<br />* 函數名稱:<br />* Convert256toGray()<br />*<br />* 參數:<br />* HDIB hDIB -映像的控制代碼<br />*<br />* 傳回值:<br />* 無<br />*<br />* 功能:<br />* 將256色位元影像轉化為灰階圖<br />*<br />***************************************************************/</p><p>void Convert256toGray(HDIB hDIB)<br />{<br />LPSTRlpDIB;</p><p>// 由DIB控制代碼得到DIB指標並鎖定DIB<br />lpDIB = (LPSTR) ::GlobalLock((HGLOBAL)hDIB);</p><p>// 指向DIB象素資料區的指標<br />LPSTR lpDIBBits;</p><p>// 指向DIB象素的指標<br />BYTE *lpSrc;</p><p>// 映像寬度<br />LONGlWidth;<br />// 映像高度<br />LONG lHeight;</p><p>// 映像每行的位元組數<br />LONGlLineBytes;</p><p>// 指向BITMAPINFO結構的指標(Win3.0)<br />LPBITMAPINFO lpbmi;</p><p>// 指向BITMAPCOREINFO結構的指標<br />LPBITMAPCOREINFO lpbmc;</p><p>// 擷取指向BITMAPINFO結構的指標(Win3.0)<br />lpbmi = (LPBITMAPINFO)lpDIB;</p><p>// 擷取指向BITMAPCOREINFO結構的指標<br />lpbmc = (LPBITMAPCOREINFO)lpDIB;</p><p>// 灰階映射表<br />BYTE bMap[256];</p><p>// 計算灰階映射表(儲存各個顏色的灰階值),並更新DIB調色盤<br />inti,j;<br />for (i = 0; i < 256; i ++)<br />{<br />// 計算該顏色對應的灰階值<br />bMap[i] = (BYTE)(0.299 * lpbmi->bmiColors[i].rgbRed +</p><p> 0.587 * lpbmi->bmiColors[i].rgbGreen +</p><p> 0.114 * lpbmi->bmiColors[i].rgbBlue + 0.5);<br />// 更新DIB調色盤紅色分量<br />lpbmi->bmiColors[i].rgbRed = i;</p><p>// 更新DIB調色盤綠色分量<br />lpbmi->bmiColors[i].rgbGreen = i;</p><p>// 更新DIB調色盤藍色分量<br />lpbmi->bmiColors[i].rgbBlue = i;</p><p>// 更新DIB調色盤保留位<br />lpbmi->bmiColors[i].rgbReserved = 0;</p><p>}<br />// 找到DIB映像象素起始位置<br />lpDIBBits = ::FindDIBBits(lpDIB);</p><p>// 擷取映像寬度<br />lWidth = ::DIBWidth(lpDIB);</p><p>// 擷取映像高度<br />lHeight = ::DIBHeight(lpDIB);</p><p>// 計算映像每行的位元組數<br />lLineBytes = WIDTHBYTES(lWidth * 8);</p><p>// 更換每個象素的色彩索引(即按照灰階映射表換成灰階值)</p><p>//漸進式掃描<br />for(i = 0; i < lHeight; i++)<br />{</p><p> //逐列掃描<br />for(j = 0; j < lWidth; j++)<br />{<br />// 指向DIB第i行,第j個象素的指標<br />lpSrc = (unsigned char*)lpDIBBits + lLineBytes * (lHeight - 1 - i) + j;</p><p>// 變換<br />*lpSrc = bMap[*lpSrc];<br />}<br />}</p><p>//解除鎖定<br />::GlobalUnlock ((HGLOBAL)hDIB);<br />}
24位彩色圖轉換成4位灰階圖
首先要聲明的是,這個4位(16)色圖比較特殊,不是彩色的16色圖,而已一個用4位16色,類比的灰階圖
什麼是灰階圖?
灰階圖是指只含亮度資訊不含彩色資訊的圖象,就像我們平時看到的亮度由暗到明的黑白照片,亮度變化是連續的。因此,要表示
灰階圖,就需要把亮度值進行亮化。通常分成0-255共256個層級,0最暗(全黑),255最亮(全白)。
BMP格式的檔案中並沒有灰階圖這個概念,但是可以很容易的用BMP檔案來表示灰階圖。一般的方法是用256色的調色盤,這個調色盤
每一項的RGB值都是相同的,即從(0,0,0),(1,1,1)一直到(255,255,255),(0,0,0)表示全黑(255,255,255)表示全白
1.BMP位元影像的格式
BMP檔案的結構分為4部分,本文假定讀者都已經瞭解BMP位元影像的格式(幾乎所有教VC的書上多媒體部分都有講,再google一下也很容
易就查得到,這裡主要介紹其中的調色盤,和圖象資料部分。
對於非真彩的位元影像,都有一個調色盤,調色盤的格式如下
typedef struct tagRGBQUAD{
BYTE rgbBlue;//藍色的分量
BYTE rgbGreen;//綠色的分量
BYTE rgbRed;//紅色的分量
BYTE rgbReserved;//保留值不用管它為0就好
}RGBQUAD;
一般的調色版是一個,由上面的結構體組成的結構體數組,儲存具體的顏色資訊,而位元影像中,圖象資料部分儲存的只是調色盤的下標
。這樣做就可以大大的節省空間的。
例如:
RGBQUAD rgb[2];
rgb[0].rgbBlue = 0;
rgb[0].rgbGreen = 0;
rgb[0].rgbRed = 0;
rgb[0].rgbReserved = 0;
rgb[1].rgbBlue = 255;
rgb[1].rgbGreen = 255;
rgb[1].rgbRed = 255;
rgb[1].rgbReserved = 255;
這個長度為2的RGBQUAD數組就是一個1位2色黑白圖的調色盤,
在位元影像資料部分只需要用1位的長度儲存0表示黑,1表示白就可以了,1位元組可以表示8個像素的資訊,比用3位元組直接表示R,G,B節
省了24倍的儲存空間
而真彩圖則不然,比如24位元影像,那麼他就需要一個數組大小為2的24次方的調色盤,而調色盤的下標也需要3個位元組才儲存,這樣還
不如直接就R,G,B這三個分量來直接表示每一個像素的色值。使用調色盤技術還浪費了一個256*256*256*3位元組大的調色盤空間.
而這裡要用4位表示一個灰階圖,那麼它的調色盤只有16項,每一項的RGB值同通常由256色構成的灰階圖的調色盤一樣的道理
這裡這樣建立這個調色盤
RGBQUAD pa[16];
BYTE c;
for(int i=0;i<16;i++)
{
c= i * 17;
pa[i].rgbRed = c;
pa[i].rgbGreen = c;
pa[i].rgbBlue = c;
pa[i].rgbReserved = 0;
}
2.轉換演算法
現在的圖象是24位真彩的,表示它的資料部分,3位元組表示一個像素,這三個位元組分別表示RGB。
我們現在要做的是求每一像素點的RGB值的平均值,然後用16色調色盤中最接近這個顏色亮度的值來表示它。
而4位的圖象是1個位元組表示2個像素,在這裡需要特殊注意
具體演算法實現代碼如下,pBuffer是儲存圖象資料的數組
USHORT R,G,B;
////////////////////////////////////////////////////////////////
// 第一個像素
R = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
B = pBuffer[dwIndex++];
int maxcolor = (R+G+B)/3;
maxcolor /= 17;//計算在16色調色盤中的下標
//第二個像素
R = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
B = pBuffer[dwIndex++];
int maxcolor2 = (R+G+B)/3;
maxcolor2 /= 17;
pNew[dwOldIndex++] = ( maxcolor<<4 )| maxcolor2;//合成一個位元組表示兩個像素
3.實現代碼
完整的實現代碼如下
BOOL Convert24To4(LPCTSTR lpszSrcFile, LPCTSTR lpszDestFile)//24->4灰階圖
{
BITMAPFILEHEADER bmHdr; // BMP檔案頭
BITMAPINFOHEADER bmInfo; // BMP檔案資訊
HANDLE hFile, hNewFile;
DWORD dwByteWritten = 0;
// 開啟源檔案控制代碼
hFile = CreateFile(lpszSrcFile,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
return FALSE;
// 建立新檔案
hNewFile = CreateFile(lpszDestFile,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hNewFile == INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return FALSE;
}
// 讀取源檔案BMP頭和檔案資訊
ReadFile(hFile, &bmHdr, sizeof(bmHdr), &dwByteWritten, NULL);
ReadFile(hFile, &bmInfo, sizeof(bmInfo), &dwByteWritten, NULL);
TRACE("biSize: %d , biWidth: %d , biHeight: %d , biBitCount: %d , biSizeImage: %d
/n",bmInfo.biSize,bmInfo.biWidth,bmInfo.biHeight,bmInfo.biBitCount,bmInfo.biSizeImage);
TRACE("biX: %d , biY: %d , biClrUsed: %d , biClrImportant: %d
/n",bmInfo.biXPelsPerMeter,bmInfo.biYPelsPerMeter,bmInfo.biClrUsed,bmInfo.biClrImportant);
// 只處理24位未壓縮的映像
if (bmInfo.biBitCount != 24 || bmInfo.biCompression!=0)
{
CloseHandle(hNewFile);
CloseHandle(hFile);
DeleteFile(lpszDestFile);
return FALSE;
}
// 計算映像資料大小
DWORD dwOldSize = bmInfo.biSizeImage;
if(dwOldSize == 0) // 重新計算
{
dwOldSize = bmHdr.bfSize - sizeof(bmHdr) - sizeof(bmInfo);
}
TRACE("Old Width: %d , Old Height: %d ,Old Size: %d bytes/n",bmInfo.biWidth,bmInfo.biHeight,dwOldSize);
long wid = bmInfo.biWidth % 4;
if(wid>0)
{
wid = 4 - wid;
}
wid += bmInfo.biWidth;
DWORD dwNewSize;
dwNewSize = wid * bmInfo.biHeight / 2; //計算轉換後新圖象大小
TRACE("New Size: %d bytes/n", dwNewSize);
// 讀取未經處理資料
UCHAR *pBuffer = NULL;
pBuffer = new UCHAR[dwOldSize]; // 申請未經處理資料空間
if(pBuffer == NULL)
{
CloseHandle(hNewFile);
CloseHandle(hFile);
DeleteFile(lpszDestFile);
return FALSE;
}
// 讀取資料
ReadFile(hFile, pBuffer, dwOldSize, &dwByteWritten, NULL);
UCHAR *pNew = new UCHAR[dwNewSize];
UCHAR color = 0;
DWORD dwIndex = 0, dwOldIndex = 0;
while( dwIndex < dwOldSize )//一位元組表示兩個像素
{
USHORT R,G,B;
////////////////////////////////////////////////////////////////
// 第一個像素
R = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
B = pBuffer[dwIndex++];
int maxcolor = (R+G+B)/3;
maxcolor /= 17;
//第二個像素
R = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
B = pBuffer[dwIndex++];
int maxcolor2 = (R+G+B)/3;
maxcolor2 /= 17;
pNew[dwOldIndex++] = ( maxcolor<<4 )| maxcolor2;//合成一個位元組表示兩個像素
}
////////////////////////////////////////////////////////////////////////////////
// 完工, 把結果儲存到新檔案中
// 修改屬性
bmHdr.bfSize = sizeof(bmHdr)+sizeof(bmInfo)+sizeof(RGBQUAD)*16+dwNewSize;
bmHdr.bfOffBits = bmHdr.bfSize - dwNewSize;
bmInfo.biBitCount = 4;
bmInfo.biSizeImage = dwNewSize;
// 建立調色盤
RGBQUAD pa[16];
UCHAR c;
for(int i=0;i<16;i++)
{
c= i * 17;
pa[i].rgbRed = c;
pa[i].rgbGreen = c;
pa[i].rgbBlue = c;
pa[i].rgbReserved = 0;
}
// BMP頭
WriteFile(hNewFile, &bmHdr, sizeof(bmHdr), &dwByteWritten, NULL);
// 檔案資訊頭
WriteFile(hNewFile, &bmInfo, sizeof(bmInfo), &dwByteWritten, NULL);
// 調色盤
WriteFile(hNewFile, pa, sizeof(RGBQUAD)*16, &dwByteWritten, NULL);
// 檔案資料
WriteFile(hNewFile, pNew, dwNewSize, &dwByteWritten, NULL);
delete []pBuffer;
delete []pNew;
// 關閉檔案控制代碼
CloseHandle(hNewFile);
CloseHandle(hFile);
return TRUE;
}
4.疑問
既然可以由24位真菜圖轉換為4位灰階圖,那麼一定有一個合適的方法把它轉換成4位彩色圖,而具體的區別就是
調色盤不同(調色盤都要表示哪些顏色),再有最重要的是原來的顏色用現有的16色,哪個表示更合適.