Bmp圖片的結(jié)構(gòu)剖析與代碼處理實(shí)踐[Ruby]

預(yù)覽:

Paste_Image.png

一、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è)部分。

BMP文件數(shù)據(jù)結(jié)構(gòu)

我們一般見(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)

Paste_Image.png

位圖信息頭數(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ù)為:

,其中BPP(Bits Per Pixel)為每像素的比特?cái)?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) 。

Paste_Image.png

再?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ù)組:

Paste_Image.png

由于對(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

原圖:

raw.jpg

處理效果:

out_grey.jpg

下篇文章我將介紹二值化、浮雕濾鏡底片濾鏡等圖像處理算法
預(yù)覽:

Paste_Image.png

項(xiàng)目主頁(yè)

geekeren/RubyImageProcess

參考文章

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容