預(yù)覽:

一、BMP文件格式詳解(BMP file format)
BMP文件格式,又稱為Bitmap(位圖)或是DIB(Device-Independent Device,設(shè)備無(wú)關(guān)位圖),是Windows系統(tǒng)中廣泛使用的圖像文件格式
下面以Notepad++為分析工具,結(jié)合Windows的位圖數(shù)據(jù)結(jié)構(gòu)對(duì)BMP文件格式進(jìn)行一個(gè)深度的剖析。
BMP文件的數(shù)據(jù)按照從文件頭開(kāi)始的先后順序分為四個(gè)部分:
bmp文件頭(bmp file header):提供文件的格式、大小等信息
位圖信息頭(bitmap information):提供圖像數(shù)據(jù)的尺寸、位平面數(shù)、壓縮方式、顏色索引等信息
調(diào)色板(color palette): 可選,如使用索引來(lái)表示圖像,調(diào)色板就是索引與其對(duì)應(yīng)的顏色的映射表
?位圖數(shù)據(jù)(bitmap data):就是圖像數(shù)據(jù)啦_
下面結(jié)合Windows結(jié)構(gòu)體的定義,通過(guò)一個(gè)表來(lái)分析這四個(gè)部分。

我們一般見(jiàn)到的圖像以24位圖像為主,即R、G、B三種顏色各用8個(gè)bit來(lái)表示,這樣的圖像我們稱為真彩色,這種情況下是不需要調(diào)色板的,也就是所位圖信息頭后面緊跟的就是位圖數(shù)據(jù)了。因此,我們常常見(jiàn)到有這樣一種說(shuō)法:位圖文件從文件頭開(kāi)始偏移54個(gè)字節(jié)就是位圖數(shù)據(jù)了,這其實(shí)說(shuō)的是24或32位圖的情況。這也就解釋了我們按照這種程序?qū)懗鰜?lái)的程序?yàn)槭裁磳?duì)某些位圖文件沒(méi)用了。
BMP文件頭數(shù)據(jù)結(jié)構(gòu)

位圖信息頭數(shù)據(jù)結(jié)構(gòu)

位圖數(shù)據(jù)
每個(gè)像素占一個(gè)字節(jié),取得這個(gè)字節(jié)后,以該字節(jié)為索引查詢相應(yīng)的顏色,并顯示到相應(yīng)的顯示設(shè)備上就可以了。
注意:由于位圖信息頭中的圖像高度是正數(shù),所以位圖數(shù)據(jù)在文件中的排列順序是從左下角到右上角,以行為主序排列的。

也即我們見(jiàn)到的第一個(gè)像素60是圖像最左下角的數(shù)據(jù),第二個(gè)人像素60為圖像最后一行第二列的數(shù)據(jù),…一直到最后一行的最后一列數(shù)據(jù),后面緊接的是倒數(shù)第二行的第一列的數(shù)據(jù),依此類推。
- 如果圖像是24位或是32位數(shù)據(jù)的位圖的話,位圖數(shù)據(jù)區(qū)就不是索引而是實(shí)際的像素值了。下面說(shuō)明一下,此時(shí)位圖數(shù)據(jù)區(qū)的每個(gè)像素的RGB顏色陣列排布:
- 24位RGB按照BGR的順序來(lái)存儲(chǔ)每個(gè)像素的各顏色通道的值,一個(gè)像素的所有顏色分量值都存完后才存下一個(gè)下一個(gè)像素,不進(jìn)行交織存儲(chǔ)。
- 32位數(shù)據(jù)按照BGRA的順序存儲(chǔ),其余與24位位圖的方式一樣。
像素的排布規(guī)則與前述一致。
對(duì)齊規(guī)則
講完了像素的排列規(guī)則以及各像素的顏色分量的排列規(guī)則,最后我們談?wù)剶?shù)據(jù)的對(duì)齊規(guī)則。我們知道Windows默認(rèn)的掃描的最小單位是4字節(jié),如果數(shù)據(jù)對(duì)齊滿足這個(gè)值的話對(duì)于數(shù)據(jù)的獲取速度等都是有很大的增益的。因此,BMP圖像順應(yīng)了這個(gè)要求,要求每行的數(shù)據(jù)的長(zhǎng)度必須是4的倍數(shù),如果不夠需要進(jìn)行比特填充(以0填充),這樣可以達(dá)到按行的快速存取。這時(shí),位圖數(shù)據(jù)區(qū)的大小就未必是圖片寬×每像素字節(jié)數(shù)×圖片高能表示的了,因?yàn)槊啃锌赡苓€需要進(jìn)行比特填充。
填充后的每行的字節(jié)數(shù)為:

在程序中,我們可以表示為:
int iLineByteCnt = (((m_iImageWidth * m_iBitsPerPixel) + 31) >> 5) << 2;
這樣,位圖數(shù)據(jù)區(qū)的大小為:
m_iImageDataSize = iLineByteCnt * m_iImageHeight;
我們?cè)趻呙柰暌恍袛?shù)據(jù)后,也可能接下來(lái)的數(shù)據(jù)并不是下一行的數(shù)據(jù),可能需要跳過(guò)一段填充數(shù)據(jù):
skip = 4 - ((m_iImageWidth * m_iBitsPerPixel)>>3) & 3;
二、Ruby實(shí)現(xiàn)BMP圖片的解析
根據(jù)上面的BMP圖片格式的介紹,我可以寫(xiě)出以下Ruby代碼。
@file = File.open(file, "rb+")
@bitMapFileHeader = @file.read(14).unpack('a2LS2L')
@type=@bitMapFileHeader[0] #文件類型,BM:BMP圖片
if @type!="BM"
puts "不是BMP圖片"
exit
end
@size=@bitMapFileHeader[1] #文件大小
@offBits=@bitMapFileHeader[4] #圖像數(shù)據(jù)的偏移字節(jié)
@bitMapInfoHeader = @file.read(40).unpack('L3S2L6')
@infoSize=@bitMapInfoHeader[0] #圖片信息字段大小
@width=@bitMapInfoHeader[1] #圖片寬度
@height=@bitMapInfoHeader[2] #圖片高度
@planes=@bitMapInfoHeader[3] #平面數(shù)
@bitCountPerPixel=@bitMapInfoHeader[4] #圖片位數(shù)
@compression=@bitMapInfoHeader[5]
@imageDataSize=@bitMapInfoHeader[6] #圖片數(shù)據(jù)段大小
@xPelsPerMeter=@bitMapInfoHeader[7]
@yPelsPerMeter=@bitMapInfoHeader[8]
@ClrUsed=@bitMapInfoHeader[9]
@ClrImportant=@bitMapInfoHeader[10]
@skipByteALine = 4 - ((@width * @bitCountPerPixel)>>3) & 3
if @bitCountPerPixel == 24
iLineByteCnt = (((@width * @bitCountPerPixel) + 31) >> 5) << 2
@file.seek @offBits
@imageDataArray= @file.read(@imageDataSize).unpack("C*")
end
file對(duì)象中的read(length)是從文件指針開(kāi)始讀出length個(gè)字節(jié)的數(shù)據(jù),數(shù)據(jù)類型是字符串,通過(guò)unpack函數(shù),我們可以通過(guò)傳入unpack參數(shù)解析出bmp圖片的數(shù)據(jù)結(jié)構(gòu)。
以@file.read(14).unpack('a2LS2L')為例,根據(jù)上面的BMP文件數(shù)據(jù)結(jié)構(gòu)的分析,read(14)是讀出bmp文件的前14個(gè)字節(jié)的文件頭,參數(shù)'a2LS2L'可以將14個(gè)字節(jié)的數(shù)據(jù)解析為 兩個(gè)字符(1*2字節(jié))、一個(gè)Long型(1*8字節(jié))、兩個(gè)Short型(2*2字節(jié)),分別取出bmp圖片的文件類型(“BM”)、文件大小、兩個(gè)保留字段、 圖像數(shù)據(jù)偏移量(@imageDataSize) 。

再?gòu)奈募凶x入@imageDataSize個(gè)字節(jié)的數(shù)據(jù),解析為字符數(shù)組,里面除了一些因?qū)R規(guī)則以外的數(shù)據(jù)都是圖片的數(shù)據(jù);
@imageDataArray= @file.read(@imageDataSize).unpack("C*")
如果是24位BMP圖片,那么每個(gè)像素占據(jù)三個(gè)字節(jié),分別為RGB中的B、G、R值。
以一個(gè)height=2,width=2的圖片為例,其圖片數(shù)據(jù)部分轉(zhuǎn)成單字節(jié)數(shù)組如下:
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 124 | 254 | 258 | 123 | 212 | 21 | 221 | 56 | 107 | 204 | 251 | 100 | 52 | 100 | 111 | 59 |
其中每一行的無(wú)效數(shù)據(jù):skipByteALine = 4 - ((2 * 24)>>3) & 3=2
為了更形象地表示圖片像素與RGB數(shù)據(jù)的對(duì)應(yīng)關(guān)系,在此我以二維矩陣的方式展示上面的一維數(shù)組:

由于對(duì)齊原則,第一行的7 、8,第二行的15、16元素將被丟棄。
基于以上考慮,可以通過(guò)以下Ruby代碼獲取指定像素位置的RGB值
#圖片(i,j)位置的RGB,二維坐標(biāo)到一維坐標(biāo)的映射,同時(shí)考慮到一個(gè)像素的位數(shù)以及skip量
def getRGB(i, j)
linearIndex = (@width*i+j)*(@bitCountPerPixel>>3)+i*@skipByteALine
RGB.new(@imageDataArray[linearIndex+2], @imageDataArray[linearIndex+1], @imageDataArray[linearIndex])
end
通過(guò)以下Ruby代碼設(shè)置指定像素位置的RGB值
def setRGB(i, j, rgb)
linearIndex = (@width*i+j)*(@bitCountPerPixel>>3)+i*@skipByteALine @imageDataArray[linearIndex+2] = rgb.r
@imageDataArray[linearIndex+1] = rgb.g
@imageDataArray[linearIndex] = rgb.b
end
三、Ruby處理圖片
通過(guò)上面介紹的setRGB和getRGB方法來(lái)修改bmp的圖片數(shù)據(jù)字節(jié)數(shù)組,我們就可以對(duì)bmp的指定像素進(jìn)行操作了,下面介紹將圖片灰度化:
#灰度化圖片,取RGB三色平均值
#灰度化圖片
#取RGB三色平均值
def self.grey(bmp)
for i in 0 .. bmp.height - 1
for j in 0 .. bmp.width - 1
rgb = bmp.getRGB(i, j)
grey = rgb.getGreyLevel
bmp.setRGB(i, j, RGB.new(grey, grey, grey))
end
end
end
上面的代碼我取得所有像素的RGB,然后求出R、G、B值的平均值,RGB值三值相同時(shí)像素呈現(xiàn)為灰色
上面的操作只會(huì)修改圖像數(shù)據(jù)字節(jié)數(shù)組,修改完畢需要保存到磁盤(pán),保存方法如下:
def save(file)
@saveFile = File.open(file, "wb")
@saveFile.write(@bitMapFileHeader.pack('a2LS2L'))
@saveFile.write(@bitMapInfoHeader.pack('L3S2L6'))
@saveFile.write(@imageDataArray.pack('C*'))
@file.close @saveFile.close
end
原圖:

處理效果:

下篇文章我將介紹二值化、浮雕濾鏡、底片濾鏡等圖像處理算法
預(yù)覽:
Paste_Image.png
項(xiàng)目主頁(yè)
參考文章
http://blog.csdn.net/hzqnju/article/details/5927825
http://www.itdecent.cn/p/30fbaab6d0a6
http://blog.csdn.net/hxker/article/details/50013303
http://blog.csdn.net/o_sun_o/article/details/8351037