python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > python读取图像文件

python实现读取图像文件并获取到像素数组的4种方法详解

作者:深蓝海拓

这篇文章主要为大家详细介绍了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个字节,文件头数据的结构:

文件头后面所有数据都是图像的像素字节。

将图像文件保存为我的格式的代码:

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读取图像文件的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文