彩色BMP转换成灰度图的原理
| 圖像處理中,大部分的處理方法都需要事先把彩色圖轉換成灰度圖才能進行相關的計算、識別。 彩色圖轉換灰度圖的原理如下: 我們知道彩色位圖是由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色圖轉換成灰度圖示例代碼:
?
[cpp]?view plaincopy?
?
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色,哪個表示更合適.
?
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 技術交流、商務合作請直接聯系博主
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 掃碼或搜索:猿說編程
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 猿說編程
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 微信公眾號?掃一掃關注
總結
以上是生活随笔為你收集整理的彩色BMP转换成灰度图的原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BugkuCTF-Reverse题mob
- 下一篇: Python issubclass 函数