彩色BMP转换成灰度图的原理
| 圖像處理中,大部分的處理方法都需要事先把彩色圖轉(zhuǎn)換成灰度圖才能進行相關(guān)的計算、識別。 彩色圖轉(zhuǎn)換灰度圖的原理如下: 我們知道彩色位圖是由R/G/B三個分量組成,其文件存儲格式為 BITMAPFILEHEADER+BITMAPINFOHEADER,緊跟后面的可能是: 如果是24位真彩圖,則每個點是由三個字節(jié)分別表示R/G/B,所以這里直接跟著圖像的色彩信息; 如果是8位(256色),4位(16色),1位(單色)圖,則緊跟后面的是調(diào)色板數(shù)據(jù),一個RGBQUAD類型的數(shù)組,其長度由BITMAPINFOHEADER.biClrUsed來決定。 然后后面緊跟的才是圖像數(shù)據(jù)(24位圖是真實的圖像數(shù)據(jù),其他的則是調(diào)色板的索引數(shù)據(jù))。 灰度圖是指只含亮度信息,不含色彩信息的圖象,就象我們平時看到的黑白照片:亮度由暗到明,變化是連續(xù)的。因此,要表示灰度圖,就需要把亮度值進行 量化。通常劃分成0到255共256個級別,其中0最暗(全黑),255最亮(全白)。在表示顏色的方法中,除了RGB外,還有一種叫YUV的表示方法, 應用也很多。電視信號中用的就是一種類似于YUV的顏色表示方法。在這種表示方法中,Y分量的物理含義就是亮度,Y分量包含了灰度圖的所有信息,只用Y分 量就能完全能夠表示出一幅灰度圖來。 從 RGB 到 YUV 空間的 Y 轉(zhuǎn)換公式為:? Y = 0.299R+0.587G+0.114B? 在 WINDOWS 中,表示 16 位以上的圖和以下的圖有點不同; 16 位以下的圖使用一個調(diào)色板來表示選擇具體的顏色,調(diào)色板的每個單元是 4 個字節(jié),其中一個透明度;而具體的像素值存儲的是索引,分別是 1 、 2 、 4 、 8 位。 16 位以上的圖直接使用像素表示顏色。? 那么如何將彩色圖轉(zhuǎn)換為灰度圖呢?? 灰度圖中有調(diào)色板,首先需要確定調(diào)色板的具體顏色取值。我們前面提到了,灰度圖的三個分量相等。? 當轉(zhuǎn)換為 8 位的時候,調(diào)色板中有 256 個顏色,每個正好從 0 到 255 個,三個分量都相等。? 當轉(zhuǎn)換為 4 位的時候,調(diào)色板中 16 個顏色,等間隔平分 255 個顏色值,三個分量都相等。? 當轉(zhuǎn)換為 2 位的時候,調(diào)色板中 4 個顏色,等間隔平分 255 個顏色,三個分量相等。? 當轉(zhuǎn)換為 1 位的時候,調(diào)色板中兩個顏色,是 0 和 255 ,表示黑和白。? 將彩色轉(zhuǎn)換為灰度時候,按照公式計算出對應的值,該值實際上是亮度的級別;亮度從 0 到 255 ;由于不同的位有不同的亮度級別,所以 Y 的具體取值如下:? ? ?? ? Y = Y/ (1<<(8- 轉(zhuǎn)換的位數(shù) ));? 所以,我們要轉(zhuǎn)化成灰度圖,并且存儲成一幅可以看到的圖像,需要做如下轉(zhuǎn)換: 16位以上的圖像不帶調(diào)色板,只需要把圖像數(shù)據(jù)按每個點的位數(shù)都轉(zhuǎn)換成相同的灰度值即可 16位以下的圖像,則需要修改調(diào)色板的數(shù)值,并且按照每個點所占位數(shù)修改灰度值索引即可。 |
?
以下是256色圖轉(zhuǎn)換成灰度圖示例代碼:
?
[cpp]?view plaincopy?
?
24位彩色圖轉(zhuǎn)換成4位灰度圖
首先要聲明的是,這個4位(16)色圖比較特殊,不是彩色的16色圖,而已一個用4位16色,模擬的灰度圖
什么是灰度圖?
灰度圖是指只含亮度信息不含彩色信息的圖象,就像我們平時看到的亮度由暗到明的黑白照片,亮度變化是連續(xù)的。因此,要表示
灰度圖,就需要把亮度值進行亮化。通常分成0-255共256個級別,0最暗(全黑),255最亮(全白)。
BMP格式的文件中并沒有灰度圖這個概念,但是可以很容易的用BMP文件來表示灰度圖。一般的方法是用256色的調(diào)色板,這個調(diào)色板
每一項的RGB值都是相同的,即從(0,0,0),(1,1,1)一直到(255,255,255),(0,0,0)表示全黑(255,255,255)表示全白
1.BMP位圖的格式
BMP文件的結(jié)構(gòu)分為4部分,本文假定讀者都已經(jīng)了解BMP位圖的格式(幾乎所有教VC的書上多媒體部分都有講,再google一下也很容
易就查得到,這里主要介紹其中的調(diào)色板,和圖象數(shù)據(jù)部分。
對于非真彩的位圖,都有一個調(diào)色板,調(diào)色板的格式如下
typedef struct tagRGBQUAD{
?BYTE rgbBlue;//藍色的分量
?BYTE rgbGreen;//綠色的分量
?BYTE rgbRed;//紅色的分量
?BYTE rgbReserved;//保留值不用管它為0就好
}RGBQUAD;
一般的調(diào)色版是一個,由上面的結(jié)構(gòu)體組成的結(jié)構(gòu)體數(shù)組,存儲具體的顏色信息,而位圖中,圖象數(shù)據(jù)部分存儲的只是調(diào)色板的下標
。這樣做就可以大大的節(jié)省空間。
例如:
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數(shù)組就是一個1位2色黑白圖的調(diào)色板,
在位圖數(shù)據(jù)部分只需要用1位的長度存儲0表示黑,1表示白就可以了,1字節(jié)可以表示8個像素的信息,比用3字節(jié)直接表示R,G,B節(jié)
省了24倍的存儲空間
而真彩圖則不然,比如24位圖,那么他就需要一個數(shù)組大小為2的24次方的調(diào)色板,而調(diào)色板的下標也需要3個字節(jié)才儲存,這樣還
不如直接就R,G,B這三個分量來直接表示每一個像素的色值。使用調(diào)色板技術(shù)還浪費了一個256*256*256*3字節(jié)大的調(diào)色板空間.
而這里要用4位表示一個灰度圖,那么它的調(diào)色板只有16項,每一項的RGB值同通常由256色構(gòu)成的灰度圖的調(diào)色板一樣的道理
這里這樣建立這個調(diào)色板
?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.轉(zhuǎn)換算法
現(xiàn)在的圖象是24位真彩的,表示它的數(shù)據(jù)部分,3字節(jié)表示一個像素,這三個字節(jié)分別表示RGB。
我們現(xiàn)在要做的是求每一像素點的RGB值的平均值,然后用16色調(diào)色板中最接近這個顏色亮度的值來表示它。
而4位的圖象是1個字節(jié)表示2個像素,在這里需要特殊注意
具體算法實現(xiàn)代碼如下,pBuffer是儲存圖象數(shù)據(jù)的數(shù)組
? USHORT R,G,B;
?
? // 第一個像素
? R = pBuffer[dwIndex++];
? G = pBuffer[dwIndex++];
? B = pBuffer[dwIndex++];
? int maxcolor = (R+G+B)/3;
? maxcolor /= 17;//計算在16色調(diào)色板中的下標
?
? //第二個像素
? R = pBuffer[dwIndex++];
? G = pBuffer[dwIndex++];
? B = pBuffer[dwIndex++];
? int maxcolor2 = (R+G+B)/3;
? maxcolor2 /= 17;
??
? pNew[dwOldIndex++] = ( maxcolor<<4 )| maxcolor2;//合成一個字節(jié)表示兩個像素
3.實現(xiàn)代碼
完整的實現(xiàn)代碼如下
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;
?// 創(chuàng)建新文件
?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;
?}
?// 計算圖像數(shù)據(jù)大小
?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; //計算轉(zhuǎn)換后新圖象大小
?TRACE("New Size: %d bytes/n", dwNewSize);
?
?// 讀取原始數(shù)據(jù)
?UCHAR *pBuffer = NULL;
?pBuffer = new UCHAR[dwOldSize]; // 申請原始數(shù)據(jù)空間
?if(pBuffer == NULL)
?{
? CloseHandle(hNewFile);
? CloseHandle(hFile);
? DeleteFile(lpszDestFile);
? return FALSE;
?}
?// 讀取數(shù)據(jù)
?ReadFile(hFile, pBuffer, dwOldSize, &dwByteWritten, NULL);
?UCHAR *pNew = new UCHAR[dwNewSize];
?UCHAR? color = 0;
?DWORD dwIndex = 0, dwOldIndex = 0;
?while( dwIndex < dwOldSize )//一字節(jié)表示兩個像素
?{
? 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;//合成一個字節(jié)表示兩個像素
?}
?
?// 完工, 把結(jié)果保存到新文件中
?// 修改屬性
?bmHdr.bfSize = sizeof(bmHdr)+sizeof(bmInfo)+sizeof(RGBQUAD)*16+dwNewSize;
?bmHdr.bfOffBits = bmHdr.bfSize - dwNewSize;
?bmInfo.biBitCount = 4;
?bmInfo.biSizeImage = dwNewSize;
?// 創(chuàng)建調(diào)色板
?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);
?// 調(diào)色板
?WriteFile(hNewFile, pa, sizeof(RGBQUAD)*16, &dwByteWritten, NULL);
?// 文件數(shù)據(jù)
?WriteFile(hNewFile, pNew, dwNewSize, &dwByteWritten, NULL);
?delete []pBuffer;
?delete []pNew;
?// 關(guān)閉文件句柄
?CloseHandle(hNewFile);
?CloseHandle(hFile);
?return TRUE;
}
4.疑問
既然可以由24位真菜圖轉(zhuǎn)換為4位灰度圖,那么一定有一個合適的方法把它轉(zhuǎn)換成4位彩色圖,而具體的區(qū)別就是
調(diào)色板不同(調(diào)色板都要表示哪些顏色),再有最重要的是原來的顏色用現(xiàn)有的16色,哪個表示更合適.
?
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 技術(shù)交流、商務合作請直接聯(lián)系博主
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 掃碼或搜索:猿說編程
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 猿說編程
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 微信公眾號?掃一掃關(guān)注
總結(jié)
以上是生活随笔為你收集整理的彩色BMP转换成灰度图的原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BugkuCTF-Reverse题mob
- 下一篇: BugkuCTF-Reverse题mob