python實(shí)現(xiàn)讀取圖像文件并獲取到像素?cái)?shù)組的4種方法詳解
場(chǎng)景需求:讀取一個(gè)bmp圖像文件,并把像素?cái)?shù)據(jù)轉(zhuǎn)換為numpy數(shù)組,這是一個(gè)非常司空見(jiàn)慣的操作。本次測(cè)試的目的是使用不同的讀取方式,找到其中效率最高的。
素材:demo.bmp,4624*3742像素,RGB格式。
方法1:使用opencv直接讀取,就可以獲取到像素的數(shù)組
import cv2
from PyQt5.QtCore import QElapsedTimer
# 創(chuàng)建定時(shí)器,統(tǒng)計(jì)用時(shí)
timer = QElapsedTimer()
timer.start()
img = cv2.imread("demo.bmp")
print(f"讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: {timer.elapsed()} ms")
print(img.shape)計(jì)時(shí)結(jié)果:
讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: 54 ms
(3472, 4624, 3)
方法2:使用pillow讀取,再轉(zhuǎn)換為像素?cái)?shù)組
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文件的時(shí)間: {timer.elapsed()} ms")
img_array = np.array(img)
print(f"讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: {timer.elapsed()} ms")
print(img_array.shape)計(jì)時(shí)結(jié)果:
讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: 14 ms
讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: 117 ms
(3472, 4624, 3)
方法3:使用qt
讀取為QImage對(duì)象,再轉(zhuǎn)換為像素?cái)?shù)組
import numpy as np
from PyQt5.QtCore import QElapsedTimer
from PyQt5.QtGui import QImage
timer = QElapsedTimer()
timer.start()
# 轉(zhuǎn)換為BGR888格式
qimage = QImage('demo.bmp').convertToFormat(QImage.Format_BGR888)
w, h = qimage.width(), qimage.height() # 4624 3472
print("讀取bmp文件的耗時(shí)", 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文件并轉(zhuǎn)換為數(shù)組的時(shí)間: {timer.elapsed()} ms")
print(img_array.shape)計(jì)時(shí)結(jié)果:
讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: 110 ms
(3472, 4624, 3)
不創(chuàng)建QImage對(duì)象,只讀取像素?cái)?shù)據(jù)并轉(zhuǎn)換為數(shù)組
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文件并轉(zhuǎn)換為數(shù)組的時(shí)間: {timer.elapsed()} ms")
print(img_array.shape)計(jì)時(shí)結(jié)果:
讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: 109 ms
(3472, 4624, 3)
方法4:使用python的原生文件打開(kāi)方法
一次性全部讀出數(shù)據(jù)后截取數(shù)據(jù)切片:
import numpy as np
from PyQt5.QtCore import QElapsedTimer
timer = QElapsedTimer()
timer.start()
with open("demo.bmp", "rb") as f:
read_bytes = f.read()
# 解析像素區(qū)的偏移量:第10-13字節(jié),小端序,4字節(jié)整數(shù)
offset = int.from_bytes(read_bytes[10:14], byteorder='little')
# 解析寬度:第18-21字節(jié),小端序,4字節(jié)整數(shù)
w = int.from_bytes(read_bytes[18:22], byteorder='little')
# 解析高度:第22-25字節(jié),小端序,4字節(jié)整數(shù)
h = int.from_bytes(read_bytes[22:26], byteorder='little')
# 截取bmp圖片數(shù)據(jù)
image_bytes = read_bytes[offset:]
img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, 3)) # 將bayer格式字節(jié)流轉(zhuǎn)換為numpy數(shù)組
img_array = np.ascontiguousarray(np.flipud(img_array)) # 垂直翻轉(zhuǎn)(bmp圖像像素是從左下角開(kāi)始存儲(chǔ)的)
print(f"讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: {timer.elapsed()} ms")
print(img_array.shape)計(jì)時(shí)結(jié)果:
讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: 53 ms
(3472, 4624, 3)
分兩次讀取,先讀取頭文件再讀取數(shù)據(jù):
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)
# 解析像素區(qū)的偏移量:第10-13字節(jié),小端序,4字節(jié)整數(shù)
offset = int.from_bytes(header[10:14], byteorder='little')
# 解析寬度:第18-21字節(jié),小端序,4字節(jié)整數(shù)
w = int.from_bytes(header[18:22], byteorder='little')
# 解析高度:第22-25字節(jié),小端序,4字節(jié)整數(shù)
h = int.from_bytes(header[22:26], byteorder='little')
image_size = file_size - offset
# 繼續(xù)讀取bmp圖片數(shù)據(jù)
f.seek(offset)
image_bytes = f.read()
# print(f"讀取bmp文件的時(shí)間: {timer.elapsed()} ms")
img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, 3)) # 將bayer格式字節(jié)流轉(zhuǎn)換為numpy數(shù)組
img_array = np.ascontiguousarray(np.flipud(img_array)) # 垂直翻轉(zhuǎn)(bmp圖像像素是從左下角開(kāi)始存儲(chǔ)的)
print(f"讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: {timer.elapsed()} ms")
print(img_array.shape)用時(shí)接近:
讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: 51 ms
(3472, 4624, 3)
前面這兩個(gè)方法的用時(shí)與opencv非常接近。
分兩次讀取,先讀取頭文件,在讀取數(shù)據(jù)時(shí)指定長(zhǎng)度(上面方法的改進(jìn)):
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)
# 解析像素區(qū)的偏移量:第10-13字節(jié),小端序,4字節(jié)整數(shù)
offset = int.from_bytes(header[10:14], byteorder='little')
# 解析寬度:第18-21字節(jié),小端序,4字節(jié)整數(shù)
w = int.from_bytes(header[18:22], byteorder='little')
# 解析高度:第22-25字節(jié),小端序,4字節(jié)整數(shù)
h = int.from_bytes(header[22:26], byteorder='little')
image_size = w * h * 3
# 繼續(xù)讀取bmp圖片數(shù)據(jù)
f.seek(offset)
image_bytes = f.read(image_size)
img_array = np.frombuffer(image_bytes, dtype=np.uint8).reshape((h, w, 3)) # 將bayer格式字節(jié)流轉(zhuǎn)換為numpy數(shù)組
img_array = np.ascontiguousarray(np.flipud(img_array)) # 垂直翻轉(zhuǎn)(bmp圖像像素是從左下角開(kāi)始存儲(chǔ)的)
print(f"讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: {timer.elapsed()} ms")
print(img_array.shape)計(jì)時(shí)結(jié)果:
讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: 36 ms
(3472, 4624, 3)
與之前的代碼相比,唯一改進(jìn)的地方:image_bytes = f.read(image_size),在讀取像素?cái)?shù)據(jù)的時(shí)候指定了數(shù)據(jù)長(zhǎng)度,用時(shí)就有了明顯的減少。
階段總結(jié)
| 讀取方法 | 用時(shí)(ms) | 備注 |
| 使用opencv讀取 | 53 | 與python的原生打開(kāi)方法接近 |
| 使用pillow讀取 | 117 | 最慢 |
| 使用qt讀取 | 110 | 次慢 |
| python的原生打開(kāi)方法打開(kāi)圖像的像素字節(jié) | 53 | 與opencv接近 |
| python的原生打開(kāi)方法打開(kāi)圖像的像素字節(jié),指定讀取長(zhǎng)度 | 36 | 最快 |
所以,使用python的原生打開(kāi)方法打開(kāi)圖像的像素字節(jié),并指定長(zhǎng)度來(lái)讀取,是最快的方法。但是這個(gè)方法的局限性在于,只對(duì)bmp文件最適用,因?yàn)閎mp文件是逐字節(jié)存放像素的原始數(shù)據(jù)。
兩個(gè)實(shí)用代碼demo
一個(gè)兼容性強(qiáng)的也比較快的代碼demo:
上面的代碼都是針對(duì)bmp格式,下面的代碼可以打開(kāi)多種圖像文件。
import cv2
import numpy as np
from PyQt5.QtCore import QElapsedTimer
# 創(chuàng)建定時(shí)器,統(tǒng)計(jì)用時(shí)
timer = QElapsedTimer()
timer.start()
with open("demo.bmp", "rb") as f:
image_bytes = f.read() # 獲取二進(jìn)制數(shù)據(jù)
# 將二進(jìn)制數(shù)據(jù)轉(zhuǎn)為numpy數(shù)組,再用imdecode解碼
img_array = np.frombuffer(image_bytes, dtype=np.uint8)
img_array = cv2.imdecode(img_array, cv2.IMREAD_COLOR) # 解碼參數(shù)同imread,也可以進(jìn)行別的顏色設(shè)置
print(f"讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: {timer.elapsed()} ms")
print(img_array.shape)結(jié)果如下:
讀取bmp文件并轉(zhuǎn)換為數(shù)組的時(shí)間: 50 ms
(3472, 4624, 3)
該段代碼使用python原生的文件打開(kāi)方法獲取二進(jìn)制數(shù)據(jù),并使用opencv進(jìn)行解碼后獲得像素陣列。讀取速度與使用opencv直接讀取接近,并且兼容所有opencv支持的圖像格式,優(yōu)勢(shì)是可以同時(shí)獲取到二進(jìn)制數(shù)據(jù)的字節(jié)流,這個(gè)字節(jié)流可用來(lái)網(wǎng)絡(luò)傳輸或保存為本地二進(jìn)制文件。
一套我自己用的基于二進(jìn)制數(shù)據(jù)字節(jié)流的極快的讀寫(xiě)方法:
首先,保存文件為我的格式,文件分為兩部分:文件頭(head)和文件二進(jìn)制字節(jié)(image_bytes)兩部分。
文件頭共13個(gè)字節(jié),文件頭數(shù)據(jù)的結(jié)構(gòu):
- [0:2]:圖像高度
- [2:4]:圖像寬度
- [4:6]:圖像色彩通道數(shù)
- [6:10]:圖像像素的字節(jié)數(shù)
- [10:13]:像素格式(“RGB"、"BAY")
文件頭后面所有數(shù)據(jù)都是圖像的像素字節(jié)。
將圖像文件保存為我的格式的代碼:
import cv2
import numpy as np
img = cv2.imread("demo.bmp") # 讀取一個(gè)圖像文件
h, w, c = img.shape # 高寬
l = w * h *3 # 像素字節(jié)數(shù)
t = "RGB" # 圖像類型(RGB)
# 把hwclt轉(zhuǎn)換為字節(jié)(高寬等圖像參數(shù))
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數(shù)組
rgb_bytes = img.astype(np.uint8)
# image_bytes是圖像的字節(jié)流
image_bytes = b''.join([h_bytes, w_bytes, c_bytes, l_bytes, t_bytes, rgb_bytes]) # 拼接字節(jié)流,hwclt在前,像素在后
with open("demo2.raw", "wb") as f:
f.write(image_bytes)讀取我的格式的文件并轉(zhuǎn)換為數(shù)列:
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文件并轉(zhuǎn)換為數(shù)組的時(shí)間: {timer.elapsed()} ms")
cv2.imshow("img", img_array)
cv2.waitKey(0)速度展示:
讀取raw文件并轉(zhuǎn)換為數(shù)組的時(shí)間: 19 ms
上面代碼中的image_bytes像素字節(jié)流,除了可以從本地讀取,還可以是來(lái)自網(wǎng)絡(luò)或者相機(jī)。
上面的方法,唯一缺點(diǎn)就是保存為本地文件時(shí),文件比較大(與bmp格式相同),為了減小文件體積,可將RGB格式的彩色 圖像文件保存為bayer格式,bayer格式的文件體積只有bmp文件的1/3。
將圖像文件保存為bayer格式二進(jìn)制文件的代碼:
使用pillow:
import cv2
import numpy as np
from PIL import Image
img = Image.open("demo.jpg") # 讀取一個(gè)彩色RGB圖像
w, h = img.size # 寬高
c = 3 # 通道數(shù)
l = w * h # 像素字節(jié)數(shù)
t = "BAY" # 圖像類型(Bayer)
# 把hwclt轉(zhuǎn)換為字節(jié)(高寬等圖像參數(shù))
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數(shù)組
rgb_array = np.array(img.convert('RGB'))
###########創(chuàng)建Bayer-rg格式數(shù)組 (RGGB排列)#################
bayer_array = np.zeros((h, w), dtype=np.uint8)
# 按照Bayer-rg (RGGB) 模式填充數(shù)據(jù)
# 偶數(shù)行偶數(shù)列 (0,0), (0,2)... - R通道
bayer_array[::2, ::2] = rgb_array[::2, ::2, 0]
# 偶數(shù)行奇數(shù)列 (0,1), (0,3)... - G通道
bayer_array[::2, 1::2] = rgb_array[::2, 1::2, 1]
# 奇數(shù)行偶數(shù)列 (1,0), (1,2)... - G通道
bayer_array[1::2, ::2] = rgb_array[1::2, ::2, 1]
# 奇數(shù)行奇數(shù)列 (1,1), (1,3)... - B通道
bayer_array[1::2, 1::2] = rgb_array[1::2, 1::2, 2]
##########################################################
# 將Bayer數(shù)組轉(zhuǎn)換為字節(jié)流并保存
bayer_bytes = bayer_array.tobytes()
image_bytes = b''.join([h_bytes, w_bytes, c_bytes, l_bytes, t_bytes, bayer_bytes]) # 拼接字節(jié)流,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") # 讀取一個(gè)圖像文件
h, w, _ = img.shape # 高寬
c = 3 # 通道數(shù)
l = w * h # bayer格式像素字節(jié)數(shù)
t = "BAY" # 圖像類型(Bayer)
# 把hwclt轉(zhuǎn)換為字節(jié)(高寬等圖像參數(shù))
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數(shù)組
rgb_array = img.astype(np.uint8)
rgb_array = cv2.cvtColor(rgb_array, cv2.COLOR_BGR2RGB)
###########創(chuàng)建Bayer-rg格式數(shù)組 (RGGB排列)#################
bayer_array = np.zeros((h, w), dtype=np.uint8)
# 按照Bayer-rg (RGGB) 模式填充數(shù)據(jù)
# 偶數(shù)行偶數(shù)列 (0,0), (0,2)... - R通道
bayer_array[::2, ::2] = rgb_array[::2, ::2, 0]
# 偶數(shù)行奇數(shù)列 (0,1), (0,3)... - G通道
bayer_array[::2, 1::2] = rgb_array[::2, 1::2, 1]
# 奇數(shù)行偶數(shù)列 (1,0), (1,2)... - G通道
bayer_array[1::2, ::2] = rgb_array[1::2, ::2, 1]
# 奇數(shù)行奇數(shù)列 (1,1), (1,3)... - B通道
bayer_array[1::2, 1::2] = rgb_array[1::2, 1::2, 2]
##########################################################
# 將Bayer數(shù)組轉(zhuǎn)換為字節(jié)流并保存
bayer_bytes = bayer_array.tobytes()
image_bytes = b''.join([h_bytes, w_bytes, c_bytes, l_bytes, t_bytes, bayer_bytes]) # 拼接字節(jié)流,hwclt在前,像素在后
with open("demo3.raw", "wb") as f:
f.write(image_bytes)讀取RGB或bayer格式二進(jìn)制文件的代碼:
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文件并轉(zhuǎn)換為數(shù)組的時(shí)間: {timer.elapsed()} ms")
cv2.imshow("img", img_array)
cv2.waitKey(0)讀取速度展示:
圖像格式: BAY
(3472, 4624, 3)
讀取raw文件并轉(zhuǎn)換為數(shù)組的時(shí)間: 15 ms
總結(jié)
1、將文件保存為bayer格式的二進(jìn)制文件,文件由文件頭和像素?cái)?shù)據(jù)兩部分組成;
2、使用python原生的open(“image”, "rb")方法打開(kāi)文件,先打開(kāi)文件頭,再指定長(zhǎng)度后打開(kāi)像素字節(jié)流;
3、結(jié)合以上兩點(diǎn),可獲得兼顧到文件體積、存取速度的方法。
以上就是python實(shí)現(xiàn)讀取圖像文件并獲取到像素?cái)?shù)組的4種方法詳解的詳細(xì)內(nèi)容,更多關(guān)于python讀取圖像文件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Flask教程之重定向與錯(cuò)誤處理實(shí)例分析
這篇文章主要介紹了Flask教程之重定向與錯(cuò)誤處理,結(jié)合實(shí)例形式分析了flask框架重定向、狀態(tài)碼判斷及錯(cuò)誤處理相關(guān)操作技巧,需要的朋友可以參考下2019-08-08
Python itertools庫(kù)中product函數(shù)使用實(shí)例探究
這篇文章主要為大家介紹了Python itertools庫(kù)中product函數(shù)使用實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
Python實(shí)現(xiàn)在PowerPoint演示文稿中添加漏斗圖
漏斗圖是一種常用的數(shù)據(jù)可視化工具,本文將介紹如何使用?Python?和?Spire.Presentation?for?Python?庫(kù)在?PowerPoint?演示文稿中創(chuàng)建專業(yè)的漏斗圖,文中的示例代碼講解詳細(xì),有需要的小伙伴可以了解下2026-06-06
Python中文分詞實(shí)現(xiàn)方法(安裝pymmseg)
這篇文章主要介紹了Python中文分詞實(shí)現(xiàn)方法,通過(guò)安裝pymmseg來(lái)實(shí)現(xiàn)分詞功能,涉及pymmseg的下載、解壓、安裝及使用技巧,需要的朋友可以參考下2016-06-06
關(guān)于Python中Flask全局異常處理流程詳解
Flask是一個(gè)基于Python的Web框架,它提供了全局異常處理的機(jī)制來(lái)捕獲和處理應(yīng)用程序中的異常,本文將詳細(xì)介紹Flask的全局異常處理,并提供相應(yīng)的代碼示例,需要的朋友可以參考下2023-06-06

