python实现读取图像文件并获取到像素数组的4种方法详解
作者:深蓝海拓
场景需求:读取一个bmp图像文件,并把像素数据转换为numpy数组,这是一个非常司空见惯的操作。本次测试的目的是使用不同的读取方式,找到其中效率最高的。
素材:demo.bmp,4624*3742像素,RGB格式。
方法1:使用opencv直接读取,就可以获取到像素的数组
import cv2
from PyQt5.QtCore import QElapsedTimer
# 创建定时器,统计用时
timer = QElapsedTimer()
timer.start()
img = cv2.imread("demo.bmp")
print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms")
print(img.shape)计时结果:
读取bmp文件并转换为数组的时间: 54 ms
(3472, 4624, 3)
方法2:使用pillow读取,再转换为像素数组
import cv2
import numpy as np
from PIL import Image
from PyQt5.QtCore import QElapsedTimer
timer = QElapsedTimer()
timer.start()
img = Image.open('demo.bmp')
print(f"读取bmp文件的时间: {timer.elapsed()} ms")
img_array = np.array(img)
print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms")
print(img_array.shape)计时结果:
读取bmp文件并转换为数组的时间: 14 ms
读取bmp文件并转换为数组的时间: 117 ms
(3472, 4624, 3)
方法3:使用qt
读取为QImage对象,再转换为像素数组
import numpy as np
from PyQt5.QtCore import QElapsedTimer
from PyQt5.QtGui import QImage
timer = QElapsedTimer()
timer.start()
# 转换为BGR888格式
qimage = QImage('demo.bmp').convertToFormat(QImage.Format_BGR888)
w, h = qimage.width(), qimage.height() # 4624 3472
print("读取bmp文件的耗时", timer.elapsed(), " ms")
bits = qimage.bits()
bits.setsize(qimage.byteCount())
img_array = np.frombuffer(bits, dtype=np.uint8).reshape(h, w, 3)
print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms")
print(img_array.shape)计时结果:
读取bmp文件并转换为数组的时间: 110 ms
(3472, 4624, 3)
不创建QImage对象,只读取像素数据并转换为数组
import numpy as np
from PyQt5.QtCore import QElapsedTimer
from PyQt5.QtGui import QImage, QImageReader
timer = QElapsedTimer()
timer.start()
reader = QImageReader('demo.bmp')
reader.setAutoTransform(False)
img_bytes = reader.read().convertToFormat(QImage.Format_RGB888)
h =img_bytes.height()
w = img_bytes.width()
bits = img_bytes.bits()
bits.setsize(img_bytes.byteCount())
img_array = np.frombuffer(bits, dtype=np.uint8).reshape(h, w, 3)
print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms")
print(img_array.shape)计时结果:
读取bmp文件并转换为数组的时间: 109 ms
(3472, 4624, 3)
方法4:使用python的原生文件打开方法
一次性全部读出数据后截取数据切片:
import numpy as np
from PyQt5.QtCore import QElapsedTimer
timer = QElapsedTimer()
timer.start()
with open("demo.bmp", "rb") as f:
read_bytes = f.read()
# 解析像素区的偏移量:第10-13字节,小端序,4字节整数
offset = int.from_bytes(read_bytes[10:14], byteorder='little')
# 解析宽度:第18-21字节,小端序,4字节整数
w = int.from_bytes(read_bytes[18:22], byteorder='little')
# 解析高度:第22-25字节,小端序,4字节整数
h = int.from_bytes(read_bytes[22:26], byteorder='little')
# 截取bmp图片数据
image_bytes = read_bytes[offset:]
img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, 3)) # 将bayer格式字节流转换为numpy数组
img_array = np.ascontiguousarray(np.flipud(img_array)) # 垂直翻转(bmp图像像素是从左下角开始存储的)
print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms")
print(img_array.shape)计时结果:
读取bmp文件并转换为数组的时间: 53 ms
(3472, 4624, 3)
分两次读取,先读取头文件再读取数据:
import numpy as np
from PyQt5.QtCore import QElapsedTimer
from os.path import getsize
timer = QElapsedTimer()
timer.start()
file_size = getsize("demo.bmp") # 获取文件大小
with open("demo.bmp", "rb") as f:
# 读取BMP文件头
header = f.read(26)
# 解析像素区的偏移量:第10-13字节,小端序,4字节整数
offset = int.from_bytes(header[10:14], byteorder='little')
# 解析宽度:第18-21字节,小端序,4字节整数
w = int.from_bytes(header[18:22], byteorder='little')
# 解析高度:第22-25字节,小端序,4字节整数
h = int.from_bytes(header[22:26], byteorder='little')
image_size = file_size - offset
# 继续读取bmp图片数据
f.seek(offset)
image_bytes = f.read()
# print(f"读取bmp文件的时间: {timer.elapsed()} ms")
img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, 3)) # 将bayer格式字节流转换为numpy数组
img_array = np.ascontiguousarray(np.flipud(img_array)) # 垂直翻转(bmp图像像素是从左下角开始存储的)
print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms")
print(img_array.shape)用时接近:
读取bmp文件并转换为数组的时间: 51 ms
(3472, 4624, 3)
前面这两个方法的用时与opencv非常接近。
分两次读取,先读取头文件,在读取数据时指定长度(上面方法的改进):
import numpy as np
from PyQt5.QtCore import QElapsedTimer
timer = QElapsedTimer()
timer.start()
with open("demo.bmp", "rb") as f:
# 读取BMP文件头
header = f.read(26)
# 解析像素区的偏移量:第10-13字节,小端序,4字节整数
offset = int.from_bytes(header[10:14], byteorder='little')
# 解析宽度:第18-21字节,小端序,4字节整数
w = int.from_bytes(header[18:22], byteorder='little')
# 解析高度:第22-25字节,小端序,4字节整数
h = int.from_bytes(header[22:26], byteorder='little')
image_size = w * h * 3
# 继续读取bmp图片数据
f.seek(offset)
image_bytes = f.read(image_size)
img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, 3)) # 将bayer格式字节流转换为numpy数组
img_array = np.ascontiguousarray(np.flipud(img_array)) # 垂直翻转(bmp图像像素是从左下角开始存储的)
print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms")
print(img_array.shape)计时结果:
读取bmp文件并转换为数组的时间: 36 ms
(3472, 4624, 3)
与之前的代码相比,唯一改进的地方:image_bytes = f.read(image_size),在读取像素数据的时候指定了数据长度,用时就有了明显的减少。
阶段总结
| 读取方法 | 用时(ms) | 备注 |
| 使用opencv读取 | 53 | 与python的原生打开方法接近 |
| 使用pillow读取 | 117 | 最慢 |
| 使用qt读取 | 110 | 次慢 |
| python的原生打开方法打开图像的像素字节 | 53 | 与opencv接近 |
| python的原生打开方法打开图像的像素字节,指定读取长度 | 36 | 最快 |
所以,使用python的原生打开方法打开图像的像素字节,并指定长度来读取,是最快的方法。但是这个方法的局限性在于,只对bmp文件最适用,因为bmp文件是逐字节存放像素的原始数据。
两个实用代码demo
一个兼容性强的也比较快的代码demo:
上面的代码都是针对bmp格式,下面的代码可以打开多种图像文件。
import cv2
import numpy as np
from PyQt5.QtCore import QElapsedTimer
# 创建定时器,统计用时
timer = QElapsedTimer()
timer.start()
with open("demo.bmp", "rb") as f:
image_bytes = f.read() # 获取二进制数据
# 将二进制数据转为numpy数组,再用imdecode解码
img_array = np.frombuffer(image_bytes, dtype=np.uint8)
img_array = cv2.imdecode(img_array, cv2.IMREAD_COLOR) # 解码参数同imread,也可以进行别的颜色设置
print(f"读取bmp文件并转换为数组的时间: {timer.elapsed()} ms")
print(img_array.shape)结果如下:
读取bmp文件并转换为数组的时间: 50 ms
(3472, 4624, 3)
该段代码使用python原生的文件打开方法获取二进制数据,并使用opencv进行解码后获得像素阵列。读取速度与使用opencv直接读取接近,并且兼容所有opencv支持的图像格式,优势是可以同时获取到二进制数据的字节流,这个字节流可用来网络传输或保存为本地二进制文件。
一套我自己用的基于二进制数据字节流的极快的读写方法:
首先,保存文件为我的格式,文件分为两部分:文件头(head)和文件二进制字节(image_bytes)两部分。
文件头共13个字节,文件头数据的结构:
- [0:2]:图像高度
- [2:4]:图像宽度
- [4:6]:图像色彩通道数
- [6:10]:图像像素的字节数
- [10:13]:像素格式(“RGB"、"BAY")
文件头后面所有数据都是图像的像素字节。
将图像文件保存为我的格式的代码:
import cv2
import numpy as np
img = cv2.imread("demo.bmp") # 读取一个图像文件
h, w, c = img.shape # 高宽
l = w * h *3 # 像素字节数
t = "RGB" # 图像类型(RGB)
# 把hwclt转换为字节(高宽等图像参数)
h_bytes = h.to_bytes(2, byteorder='little')
w_bytes = w.to_bytes(2, byteorder='little')
c_bytes = c.to_bytes(2, byteorder='little')
l_bytes = l.to_bytes(4, byteorder='little', signed=False)
t_bytes = t.encode('utf-8')
# 获取图像的RGB数组
rgb_bytes = img.astype(np.uint8)
# image_bytes是图像的字节流
image_bytes = b''.join([h_bytes, w_bytes, c_bytes, l_bytes, t_bytes, rgb_bytes]) # 拼接字节流,hwclt在前,像素在后
with open("demo2.raw", "wb") as f:
f.write(image_bytes)读取我的格式的文件并转换为数列:
import numpy as np
from PyQt5.QtCore import QElapsedTimer
timer = QElapsedTimer()
timer.start()
with open("demo2.raw", "rb") as f:
head = f.read(13)
h = int.from_bytes(head[:2], byteorder='little')
w = int.from_bytes(head[2:4], byteorder='little')
c = int.from_bytes(head[4:6], byteorder='little')
l = int.from_bytes(head[6:10], byteorder='little')
t = head[10:13].decode('utf-8')
image_bytes = f.read(l)
if t == "RGB":
img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, c))
# elif t == "BAY":
# img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w))
# img_array = cv2.cvtColor(img_array, cv2.COLOR_BAYER_RG2RGB)
print("图像格式:", t)
print(f"读取raw文件并转换为数组的时间: {timer.elapsed()} ms")
cv2.imshow("img", img_array)
cv2.waitKey(0)速度展示:
读取raw文件并转换为数组的时间: 19 ms
上面代码中的image_bytes像素字节流,除了可以从本地读取,还可以是来自网络或者相机。
上面的方法,唯一缺点就是保存为本地文件时,文件比较大(与bmp格式相同),为了减小文件体积,可将RGB格式的彩色 图像文件保存为bayer格式,bayer格式的文件体积只有bmp文件的1/3。
将图像文件保存为bayer格式二进制文件的代码:
使用pillow:
import cv2
import numpy as np
from PIL import Image
img = Image.open("demo.jpg") # 读取一个彩色RGB图像
w, h = img.size # 宽高
c = 3 # 通道数
l = w * h # 像素字节数
t = "BAY" # 图像类型(Bayer)
# 把hwclt转换为字节(高宽等图像参数)
h_bytes = h.to_bytes(2, byteorder='little')
w_bytes = w.to_bytes(2, byteorder='little')
c_bytes = c.to_bytes(2, byteorder='little')
l_bytes = l.to_bytes(4, byteorder='little', signed=False)
t_bytes = t.encode('utf-8')
# 获取图像的RGB数组
rgb_array = np.array(img.convert('RGB'))
###########创建Bayer-rg格式数组 (RGGB排列)#################
bayer_array = np.zeros((h, w), dtype=np.uint8)
# 按照Bayer-rg (RGGB) 模式填充数据
# 偶数行偶数列 (0,0), (0,2)... - R通道
bayer_array[::2, ::2] = rgb_array[::2, ::2, 0]
# 偶数行奇数列 (0,1), (0,3)... - G通道
bayer_array[::2, 1::2] = rgb_array[::2, 1::2, 1]
# 奇数行偶数列 (1,0), (1,2)... - G通道
bayer_array[1::2, ::2] = rgb_array[1::2, ::2, 1]
# 奇数行奇数列 (1,1), (1,3)... - B通道
bayer_array[1::2, 1::2] = rgb_array[1::2, 1::2, 2]
##########################################################
# 将Bayer数组转换为字节流并保存
bayer_bytes = bayer_array.tobytes()
image_bytes = b''.join([h_bytes, w_bytes, c_bytes, l_bytes, t_bytes, bayer_bytes]) # 拼接字节流,hwclt在前,像素在后
with open("demo3.raw", "wb") as f:
f.write(image_bytes)使用opencv完成同样功能:
import cv2
import numpy as np
# from PIL import Image
img = cv2.imread("demo.bmp") # 读取一个图像文件
h, w, _ = img.shape # 高宽
c = 3 # 通道数
l = w * h # bayer格式像素字节数
t = "BAY" # 图像类型(Bayer)
# 把hwclt转换为字节(高宽等图像参数)
h_bytes = h.to_bytes(2, byteorder='little')
w_bytes = w.to_bytes(2, byteorder='little')
c_bytes = c.to_bytes(2, byteorder='little')
l_bytes = l.to_bytes(4, byteorder='little', signed=False)
t_bytes = t.encode('utf-8')
# 获取图像的RGB数组
rgb_array = img.astype(np.uint8)
rgb_array = cv2.cvtColor(rgb_array, cv2.COLOR_BGR2RGB)
###########创建Bayer-rg格式数组 (RGGB排列)#################
bayer_array = np.zeros((h, w), dtype=np.uint8)
# 按照Bayer-rg (RGGB) 模式填充数据
# 偶数行偶数列 (0,0), (0,2)... - R通道
bayer_array[::2, ::2] = rgb_array[::2, ::2, 0]
# 偶数行奇数列 (0,1), (0,3)... - G通道
bayer_array[::2, 1::2] = rgb_array[::2, 1::2, 1]
# 奇数行偶数列 (1,0), (1,2)... - G通道
bayer_array[1::2, ::2] = rgb_array[1::2, ::2, 1]
# 奇数行奇数列 (1,1), (1,3)... - B通道
bayer_array[1::2, 1::2] = rgb_array[1::2, 1::2, 2]
##########################################################
# 将Bayer数组转换为字节流并保存
bayer_bytes = bayer_array.tobytes()
image_bytes = b''.join([h_bytes, w_bytes, c_bytes, l_bytes, t_bytes, bayer_bytes]) # 拼接字节流,hwclt在前,像素在后
with open("demo3.raw", "wb") as f:
f.write(image_bytes)读取RGB或bayer格式二进制文件的代码:
import numpy as np
from PyQt5.QtCore import QElapsedTimer
timer = QElapsedTimer()
timer.start()
with open("demo3.raw", "rb") as f:
head = f.read(13)
h = int.from_bytes(head[:2], byteorder='little')
w = int.from_bytes(head[2:4], byteorder='little')
c = int.from_bytes(head[4:6], byteorder='little')
l = int.from_bytes(head[6:10], byteorder='little')
t = head[10:13].decode('utf-8')
image_bytes = f.read(l)
if t == "RGB":
img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, c))
elif t == "BAY":
img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w))
img_array = cv2.cvtColor(img_array, cv2.COLOR_BAYER_RG2RGB)
print("图像格式:", t)
print(f"读取raw文件并转换为数组的时间: {timer.elapsed()} ms")
cv2.imshow("img", img_array)
cv2.waitKey(0)读取速度展示:
图像格式: BAY
(3472, 4624, 3)
读取raw文件并转换为数组的时间: 15 ms
总结
1、将文件保存为bayer格式的二进制文件,文件由文件头和像素数据两部分组成;
2、使用python原生的open(“image”, "rb")方法打开文件,先打开文件头,再指定长度后打开像素字节流;
3、结合以上两点,可获得兼顾到文件体积、存取速度的方法。
以上就是python实现读取图像文件并获取到像素数组的4种方法详解的详细内容,更多关于python读取图像文件的资料请关注脚本之家其它相关文章!
