介绍
由于要开始学习图像方面的知识,读写图片是难免的。对图片的结构有一定的了解对理解图片存储还是很有帮助的。由于实验的代码是用 python 写的,因此读取文件就直接使用 python 了,虽然用 C 来读写文件更有效率,但我个人感觉 python 也没有很慢。下面来看一下bmp 文件结构和读取 bmp 文件的过程。
bmp 文件结构
- bmp文件头(bmp file header):提供文件的格式、大小等信息
- 位图信息头(bitmap information):提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息
- 调色板(color palette):可选,如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表
- 位图数据(bitmap data):就是图像数据
用表格形式可以表示为:
首先来看文件头的信息(14字节):
然后是位图信息图(40字节):
一般情况下,到这里我们就可以得到这个图片的基本信息了。由于调色板是不确定的,而且现在的bmp图片一般都没有调色板信息(因为24位),所以忽略第三个。
所以,思路还是很简单的。先读取 54 字节的头文件,利用读取的图片宽和高信息,对后面的数据进行读取。
注意:由于是以二进制形式进行读取的,因此,需要注意存储方式。bmp 文件的存储方式是小端方式,读取的时候也要使用小端法进行解析
Python 代码
# -*- coding: UTF-8 -*-
from struct import unpack
# 读取并存储 bmp 文件
class ReadBMPFile :
def __init__(self, filePath) :
file = open(filePath, "rb")
# 读取 bmp 文件的文件头 14 字节
self.bfType = unpack("<h", file.read(2))[0] # 0x4d42 对应BM 表示这是Windows支持的位图格式
self.bfSize = unpack("<i", file.read(4))[0] # 位图文件大小
self.bfReserved1 = unpack("<h", file.read(2))[0] # 保留字段 必须设为 0
self.bfReserved2 = unpack("<h", file.read(2))[0] # 保留字段 必须设为 0
self.bfOffBits = unpack("<i", file.read(4))[0] # 偏移量 从文件头到位图数据需偏移多少字节(位图信息头、调色板长度等不是固定的,这时就需要这个参数了)
# 读取 bmp 文件的位图信息头 40 字节
self.biSize = unpack("<i", file.read(4))[0] # 所需要的字节数
self.biWidth = unpack("<i", file.read(4))[0] # 图像的宽度 单位 像素
self.biHeight = unpack("<i", file.read(4))[0] # 图像的高度 单位 像素
self.biPlanes = unpack("<h", file.read(2))[0] # 说明颜色平面数 总设为 1
self.biBitCount = unpack("<h", file.read(2))[0] # 说明比特数
self.biCompression = unpack("<i", file.read(4))[0] # 图像压缩的数据类型
self.biSizeImage = unpack("<i", file.read(4))[0] # 图像大小
self.biXPelsPerMeter = unpack("<i", file.read(4))[0]# 水平分辨率
self.biYPelsPerMeter = unpack("<i", file.read(4))[0]# 垂直分辨率
self.biClrUsed = unpack("<i", file.read(4))[0] # 实际使用的彩色表中的颜色索引数
self.biClrImportant = unpack("<i", file.read(4))[0] # 对图像显示有重要影响的颜色索引的数目
self.bmp_data = []
if self.biBitCount != 24 :
print("输入的图片比特值为 :" + str(self.biBitCount) + "\t 与程序不匹配")
for height in range(self.biHeight) :
bmp_data_row = []
# 四字节填充位检测
count = 0
for width in range(self.biWidth) :
bmp_data_row.append([unpack("<B", file.read(1))[0], unpack("<B", file.read(1))[0], unpack("<B", file.read(1))[0]])
count = count + 3
# bmp 四字节对齐原则
while count % 4 != 0 :
file.read(1)
count = count + 1
self.bmp_data.append(bmp_data_row)
self.bmp_data.reverse()
file.close()
# R, G, B 三个通道
self.R = []
self.G = []
self.B = []
for row in range(self.biHeight) :
R_row = []
G_row = []
B_row = []
for col in range(self.biWidth) :
B_row.append(self.bmp_data[row][col][0])
G_row.append(self.bmp_data[row][col][1])
R_row.append(self.bmp_data[row][col][2])
self.B.append(B_row)
self.G.append(G_row)
self.R.append(R_row)
这里,读取完文件后,我又将 R、G、B 三个通道的数值分出来了。主要是为了方便后续的处理,毕竟读取文件的目的是为了后面对图像的处理。
这是一个类,外部代码可以这样调用:
import numpy as np
import sys
from ReadBMPFile import ReadBMPFile
import cv2
# 命令行传入的文件路径
filePath = sys.argv[1]
# 读取 BMP 文件
bmpFile = ReadBMPFile(filePath)
# R, G, B 三个通道 [0, 255]
R = bmpFile.R
G = bmpFile.G
B = bmpFile.B
# 显示图像
b = np.array(B, dtype = np.uint8)
g = np.array(G, dtype = np.uint8)
r = np.array(R, dtype = np.uint8)
merged = cv2.merge([b, g, r]) #合并R、G、B分量 默认顺序为 B、G、R
cv2.imshow("Merged",merged)