BMP文件分析及用python读取

一、BMP文件分析

1. 什么是BMP(位图)?

常见的图像文件格式有:BMP、JPG(JPE,JPEG)、GIF等。
BMP图像文件(Bitmap-File)格式是Windows采用的图像文件存储格式,在Windows环境下运行的所有图像处理软件都支持这种格式。Windows 3.0以后的BMP文件都是指设备无关位图(DIB,device-independent bitmap)。BMP位图文件默认的文件扩展名是.BMP,有时它也会以.DIB或.RLE作扩展名。BMP格式的图片,没有使用任何压缩算法,这种方式在以前使用的比较多,现在用的就比较少了,不过为了学习图像处理算法,所以先以该种格式的文件开头。

2.BMP文件的结构

BMP文件由四部分组成,这四部分共同形成一个图像文件,缺一不可

位图文件头(bitmap-file header)

该部分一共有14个字节。可以提供文件的格式、大小等信息

		typedef struct tagBITMAPFILEHEADER 
		{  
		UINT16 bfType;    // 说明位图类型  2字节
		DWORD bfSize;  // 说明位图大小  4字节
		UINT16 bfReserved1;  // 保留字,必须为0  2字节
		UINT16 bfReserved2;  // 保留字,必须为0   2字节
		DWORD bfOffBits; // 从文件头到实际的图像数据的偏移量是多少  4字节
		} BITMAPFILEHEADER;  //一共16个字节

位图信息头(bitmap-informationheader)

		typedef struct tagBITMAPINFOHEADER
		 {
		DWORD biSize;  // 说明该结构一共需要的字节数 2字节
		LONG biWidth;  // 说明图片的宽度,以像素为单位 4字节
		LONG biHeight; // 说明图片的高度,以像素为单位 4字节
		WORD biPlanes; //颜色板,总是设为1  2个字节
		WORD biBitCount;  //说明每个比特占多少bit位,可以通过这个字段知道图片类型  2个字节
		DWORD biCompression;  // 说明使用的压缩算法 2个字节 (BMP无压缩算法)
		DWORD biSizeImage;  //说明图像大小   2个字节
		LONG biXPelsPerMeter;  //水平分辨率 4字节  单位:像素/米
		LONG biYPelsPerMeter;  //垂直分辨率4字节
		DWORD biClrUsed;  //说明位图使用的颜色索引数 4字节
		DWORD biClrImportant; //4字节
		} BITMAPINFOHEADER; // 一共40个字节

作为真彩色位图,我们主要关心的是biWidth和biHeight这两个数值,两个数值告诉我们图像的尺寸。biSize,biPlanes,biBitCount这几个数值是固定的。想偷懒的话,其它的数值可以一律用0来填充。

颜色表(color table)

紧跟在位图信息头后面的就是数据就是颜色表了,颜色表是形如一个二维数组,4个字节为一行,这四个字节分别代表了R、G、B ,Alpha(透明度通道)的分量。通过位图数据,我们就可以对这像素进行着色。注意,如果位图是24位真彩色图像,那么就没有颜色表。

位图点阵数据(bits data)

在颜色表后面的就是像素点数据了,如果bmp是伪彩色图的话,那么每个像素只占8位,即一个字节。如果bmp是24位真彩色图像的话,那么每个像素占24位,即3个字节,3个字节分别为B、G、R颜色分量。

另外,我们需要注意的是,bmp位图,其像素的排列方式是从下到上,从左到右。也就是说,我们读取位图数据的第一个字节是左下角的像素值。

我还没需要注意的是,Windows系统中有“补零”的习惯!即要求位图的每一行像素所占字节数必须被4整除。若不能倍4整除,则在该位图每一行的十六进制码末尾“补”1至3个字节的“00”。 下面举一个例子

下面这幅图片是256色伪彩色图像,大小为501*502。

现在我们来计算一下理论大小

	像素数量=501 * 502=251502
	像素所占字节=像素数量=251502个字节
	文件信息头=14个字节
	位图信息头=40个字节
	颜色表=256种颜色*4=1024个字节
	
	理论大小=251502+14+40+1024=252580个字节

但是实际上,真实大小=254086个字节

这中间相差了,差值=254086-252580=1506个字节

这相差的1506个字节哪里来的呢? 因为前面说过,Windows系统中有“补零”的习惯(这是因为windows习惯4个字节扫描一次),就是每行的像素宽度必须是4的倍数,如果不能被4整除,则添加若干个0像素,知道每行像素能被4整除为止。

所以这副图片的宽度为502,不能被4整除,我们要每行添加3个像素才行(也就是3个字节),所以最后的差值=3*502=1506个字节

二、用python来读取bmp文件

字节转换

在读取之前,我们需要先了解一下,在python中读取二进制文件时,怎么进行字节转化(类似于java里的readint,readfloat等等)。我们可以使用python中的struct模块。在这个模块中,有两个重要的函数:pack()unpack()

打包函数:pack(fmt, v1, v2, v3, ...) 这个函数可以把各种类型的数据打包成字节

解包函数:unpack(fmt, buffer) 这个函数可以把二进制数据转换成各种数据类型

通过上面的这两个函数,就可以实现类似于 readInt、readFloat 的效果

代码实现

首先,要用到三个包,numpy是用来装像素数据和颜色表的,struct是用来进行字节转换的,matplotlib我们则用来显示最终的图片的
首先
下面部分的代码,逐字节的进行读取,暂时未进行转换,是纯字节数据。我们读取了一副图像里最关键的几个信息,忽略了几个无用的信息
在这里插入图片描述
下面的这一部分代码就是将上面读取的字节数据转化为指定类型,通过fmt符来转换成我们想要的数据类型。 在这里值得一提的是,windows里的数据是小端模式,也就是低位字节在前,高位字节在后。比如说 存储在计算机里的"3c 05" 这两个字节,实际上是"053c"
在这里插入图片描述
读取完信息头之后,我们就可以来读取颜色表了。需要注意的是,bmp位图读取颜色表的时候,读取的颜色分量的顺序不是R、G、B,而是B、G、R!这个一定要注意。因为我们是用matplotlib来显示图像的,所以我们还增加了一个255的分量(实际上没什么用)
在这里插入图片描述
然后就是来读取位图数据了,读取位图数据的时候,我们一定要注意,数据的排列方式是从左到右,从下到上!还有一个while循环,是用来判断行像素是否为4的倍数,如果不是我们还要将填充的用字节给读取扔掉
在这里插入图片描述

最后再把图像显示出来,即可
在这里插入图片描述
最终效果图
在这里插入图片描述

import  numpy as np
import struct
import matplotlib.pyplot as plt

def main():

    '先将位图打开'
    f=open('1_256.bmp','rb') #打开对应的文件
    '下面部分用来读取BMP位图的基础信息'
    f_type=str(f.read(2)) #这个就可以用来读取 文件类型 需要读取2个字节
    file_size_byte=f.read(4)# 这个可以用来读取文件的大小 需要读取4个字节
    f.seek(f.tell()+4) # 跳过中间无用的四个字节
    file_ofset_byte=f.read(4) # 读取位图数据的偏移量
    f.seek(f.tell()+4) # 跳过无用的两个字节
    file_wide_byte=f.read(4) #读取宽度字节
    file_height_byte=f.read(4) #读取高度字节
    f.seek(f.tell()+2) ## 跳过中间无用的两个字节
    file_bitcount_byte=f.read(4) #得到每个像素占位大小


    #下面就是将读取的字节转换成指定的类型
    f_size,=struct.unpack('l',file_size_byte)
    f_ofset,=struct.unpack('l',file_ofset_byte)
    f_wide,=struct.unpack('l',file_wide_byte)
    f_height,=struct.unpack('l',file_height_byte)
    f_bitcount,=struct.unpack('i',file_bitcount_byte)
    print("类型:",f_type,"大小:",f_size,"位图数据偏移量:",f_ofset,"宽度:",f_wide,"高度:",f_height,"位图:",f_bitcount)


    '然后来读取颜色表'
    color_table=np.empty(shape=[256,4],dtype=int)
    f.seek(54) #跳过文件信息头和位图信息头
    for i in range(0,256):
        b=struct.unpack('B',f.read(1))[0];
        g = struct.unpack('B', f.read(1))[0];
        r = struct.unpack('B', f.read(1))[0];
        alpha = struct.unpack('B', f.read(1))[0];
        color_table[i][0]=r
        color_table[i][1]=g
        color_table[i][2]=b
        color_table[i][3]=255

    '下面部分用来读取BMP位图数据区域,将数据存入numpy数组'
    #首先对文件指针进行偏移
    f.seek(f_ofset)
    #因为图像是8位伪彩色图像,所以一个像素点占一个字节,即8位
    img=np.empty(shape=[f_height,f_wide,4],dtype=int)
    cout = 0
    for y in range(0, f_height):
        for x in range(0,f_wide):
            cout=cout+1
            index=struct.unpack('B',f.read(1))[0]
            img[f_height-y-1,x]=color_table[index]
        while cout %4 !=0:
            f.read(1)
            cout=cout+1
    plt.imshow(img)
    plt.show()
    f.close()
if __name__ == '__main__':
    main()

后续会继续写一些图像处理的算法,并且将其集成一个小小的框架

发布了2 篇原创文章 · 获赞 0 · 访问量 109

猜你喜欢

转载自blog.csdn.net/qq_43409114/article/details/104538619