常见的图像文件格式有:BMP、JPG(JPE,JPEG)、GIF等。
BMP图像文件(Bitmap-File)格式是Windows采用的图像文件存储格式,在Windows环境下运行的所有图像处理软件都支持这种格式。Windows 3.0以后的BMP文件都是指设备无关位图(DIB,device-independent bitmap)。BMP位图文件默认的文件扩展名是.BMP,有时它也会以.DIB或.RLE作扩展名。BMP格式的图片,没有使用任何压缩算法,这种方式在以前使用的比较多,现在用的就比较少了,不过为了学习图像处理算法,所以先以该种格式的文件开头。
BMP文件由四部分组成,这四部分共同形成一个图像文件,缺一不可
该部分一共有14个字节。可以提供文件的格式、大小等信息
typedef struct tagBITMAPFILEHEADER
{
UINT16 bfType; // 说明位图类型 2字节
DWORD bfSize; // 说明位图大小 4字节
UINT16 bfReserved1; // 保留字,必须为0 2字节
UINT16 bfReserved2; // 保留字,必须为0 2字节
DWORD bfOffBits; // 从文件头到实际的图像数据的偏移量是多少 4字节
} BITMAPFILEHEADER; //一共16个字节
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来填充。
紧跟在位图信息头后面的就是数据就是颜色表了,颜色表是形如一个二维数组,4个字节为一行,这四个字节分别代表了R、G、B ,Alpha(透明度通道)的分量。通过位图数据,我们就可以对这像素进行着色。注意,如果位图是24位真彩色图像,那么就没有颜色表。
在颜色表后面的就是像素点数据了,如果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-252580=1506个字节
这相差的1506个字节哪里来的呢? 因为前面说过,Windows系统中有“补零”的习惯(这是因为windows习惯4个字节扫描一次),就是每行的像素宽度必须是4的倍数,如果不能被4整除,则添加若干个0像素,知道每行像素能被4整除为止。
所以这副图片的宽度为502,不能被4整除,我们要每行添加3个像素才行(也就是3个字节),所以最后的差值=3*502=1506个字节
在读取之前,我们需要先了解一下,在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()
后续会继续写一些图像处理的算法,并且将其集成一个小小的框架