<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
ImageNet 是一個著名的公共影象資料庫,用於訓練物件分類、檢測和分割等任務的模型,它包含超過 1400 萬張影象。
在 Python 中處理影象資料的時候,例如應用折積神經網路(也稱CNN)等演演算法可以處理大量影象資料集,這裡就需要學習如何用最簡單的方式儲存、讀取資料。
對於影象資料處理應該有有個定量的比較方式,讀取和寫入檔案需要多長時間,以及將使用多少磁碟記憶體。
分別用不同的方式去處理、解決影象的儲存、效能優化的問題。
我們熟知的影象資料集 CIFAR-10,由 60000 個 32x32 畫素的彩色影象組成,這些影象屬於不同的物件類別,例如狗、貓和飛機。相對而言 CIFAR 不是一個非常大的資料集,但如使用完整的 TinyImages 資料集,那麼將需要大約 400GB 的可用磁碟空間。
文中的程式碼應用的資料集下載地址 CIFAR-10 資料集 。
這份資料是使用cPickle進行了序列化和批次儲存。pickle模組可以序列化任何 Python 物件,而無需進行任何額外的程式碼或轉換。但是有一個潛在的嚴重缺點,即在處理大量資料時會帶來安全風險無法評估。
影象載入到 NumPy 陣列中
import numpy as np import pickle from pathlib import Path # 檔案路徑 data_dir = Path("data/cifar-10-batches-py/") # 解碼功能 def unpickle(file): with open(file, "rb") as fo: dict = pickle.load(fo, encoding="bytes") return dict images, labels = [], [] for batch in data_dir.glob("data_batch_*"): batch_data = unpickle(batch) for i, flat_im in enumerate(batch_data[b"data"]): im_channels = [] # 每個影象都是扁平化的,通道按 R, G, B 的順序排列 for j in range(3): im_channels.append( flat_im[j * 1024 : (j + 1) * 1024].reshape((32, 32)) ) # 重建原始影象 images.append(np.dstack((im_channels))) # 儲存標籤 labels.append(batch_data[b"labels"][i]) print("載入 CIFAR-10 訓練集:") print(f" - np.shape(images) {np.shape(images)}") print(f" - np.shape(labels) {np.shape(labels)}")
安裝三方庫 Pillow 用於影象處理 。
pip install Pillow
LMDB 也稱為“閃電資料庫”,代表閃電記憶體對映資料庫,因為速度快並且使用記憶體對映檔案。它是鍵值儲存,而不是關聯式資料庫。
安裝三方庫 lmdb 用於影象處理 。
pip install lmdb
HDF5 代表 Hierarchical Data Format,一種稱為 HDF4 或 HDF5 的檔案格式。起源於美國國家超級計算應用中心,是一種可移植、緊湊的科學資料格式。
安裝三方庫 h5py 用於影象處理 。
pip install h5py
3種不同的方式進行資料讀取操作
from pathlib import Path disk_dir = Path("data/disk/") lmdb_dir = Path("data/lmdb/") hdf5_dir = Path("data/hdf5/")
同時載入的資料可以建立資料夾分開儲存
disk_dir.mkdir(parents=True, exist_ok=True) lmdb_dir.mkdir(parents=True, exist_ok=True) hdf5_dir.mkdir(parents=True, exist_ok=True)
使用 Pillow 完成輸入是一個單一的影象 image,在記憶體中作為一個 NumPy 陣列,並且使用唯一的影象 ID 對其進行命名image_id。
單個影象儲存到磁碟
from PIL import Image import csv def store_single_disk(image, image_id, label): """ 將單個影象作為 .png 檔案儲存在磁碟上。 引數: --------------- image 影象陣列, (32, 32, 3) 格式 image_id 影象的整數唯一 ID label 影象標籤 """ Image.fromarray(image).save(disk_dir / f"{image_id}.png") with open(disk_dir / f"{image_id}.csv", "wt") as csvfile: writer = csv.writer( csvfile, delimiter=" ", quotechar="|", quoting=csv.QUOTE_MINIMAL ) writer.writerow([label])
LMDB 是一個鍵值對儲存系統,其中每個條目都儲存為一個位元組陣列,鍵將是每個影象的唯一識別符號,值將是影象本身。
鍵和值都應該是字串。 常見的用法是將值序列化為字串,然後在讀回時將其反序列化。
用於重建的影象尺寸,某些資料集可能包含不同大小的影象會使用到這個方法。
class CIFAR_Image: def __init__(self, image, label): self.channels = image.shape[2] self.size = image.shape[:2] self.image = image.tobytes() self.label = label def get_image(self): """ 將影象作為 numpy 陣列返回 """ image = np.frombuffer(self.image, dtype=np.uint8) return image.reshape(*self.size, self.channels)
單個影象儲存到 LMDB
import lmdb import pickle def store_single_lmdb(image, image_id, label): """ 將單個影象儲存到 LMDB 引數: --------------- image 影象陣列, (32, 32, 3) 格式 image_id 影象的整數唯一 ID label 影象標籤 """ map_size = image.nbytes * 10 # Create a new LMDB environment env = lmdb.open(str(lmdb_dir / f"single_lmdb"), map_size=map_size) # Start a new write transaction with env.begin(write=True) as txn: # All key-value pairs need to be strings value = CIFAR_Image(image, label) key = f"{image_id:08}" txn.put(key.encode("ascii"), pickle.dumps(value)) env.close()
一個 HDF5 檔案可以包含多個資料集。可以建立兩個資料集,一個用於影象,一個用於後設資料。
import h5py def store_single_hdf5(image, image_id, label): """ 將單個影象儲存到 HDF5 檔案 引數: --------------- image 影象陣列, (32, 32, 3) 格式 image_id 影象的整數唯一 ID label 影象標籤 """ # 建立一個新的 HDF5 檔案 file = h5py.File(hdf5_dir / f"{image_id}.h5", "w") # 在檔案中建立資料集 dataset = file.create_dataset( "image", np.shape(image), h5py.h5t.STD_U8BE, data=image ) meta_set = file.create_dataset( "meta", np.shape(label), h5py.h5t.STD_U8BE, data=label ) file.close()
將儲存單個影象的所有三個函數放入字典中。
_store_single_funcs = dict( disk=store_single_disk, lmdb=store_single_lmdb, hdf5=store_single_hdf5 )
以三種不同的方式儲儲存存 CIFAR 中的第一張影象及其對應的標籤。
from timeit import timeit store_single_timings = dict() for method in ("disk", "lmdb", "hdf5"): t = timeit( "_store_single_funcs[method](image, 0, label)", setup="image=images[0]; label=labels[0]", number=1, globals=globals(), ) store_single_timings[method] = t print(f"儲存方法: {method}, 使用耗時: {t}")
來一個表格看看對比。
儲存方法 | 儲存耗時 | 使用記憶體 |
---|---|---|
Disk | 2.1 ms | 8 K |
LMDB | 1.7 ms | 32 K |
HDF5 | 8.1 ms | 8 K |
同單個影象儲存方法類似,修改程式碼進行多個影象資料的儲存。
將多個影象儲存為.png檔案就可以理解為多次呼叫 store_single_method() 這樣。但這不適用於 LMDB 或 HDF5,因為每個影象都有不同的資料庫檔案。
將一組影象儲存到磁碟
store_many_disk(images, labels): """ 引數: --------------- images 影象陣列 (N, 32, 32, 3) 格式 labels 標籤陣列 (N,1) 格式 """ num_images = len(images) # 一張一張儲存所有圖片 for i, image in enumerate(images): Image.fromarray(image).save(disk_dir / f"{i}.png") # 將所有標籤儲存到 csv 檔案 with open(disk_dir / f"{num_images}.csv", "w") as csvfile: writer = csv.writer( csvfile, delimiter=" ", quotechar="|", quoting=csv.QUOTE_MINIMAL ) for label in labels: writer.writerow([label])
將一組影象儲存到 LMDB
def store_many_lmdb(images, labels): """ 引數: --------------- images 影象陣列 (N, 32, 32, 3) 格式 labels 標籤陣列 (N,1) 格式 """ num_images = len(images) map_size = num_images * images[0].nbytes * 10 # 為所有影象建立一個新的 LMDB 資料庫 env = lmdb.open(str(lmdb_dir / f"{num_images}_lmdb"), map_size=map_size) # 在一個事務中寫入所有影象 with env.begin(write=True) as txn: for i in range(num_images): # 所有鍵值對都必須是字串 value = CIFAR_Image(images[i], labels[i]) key = f"{i:08}" txn.put(key.encode("ascii"), pickle.dumps(value)) env.close()
將一組影象儲存到 HDF5
def store_many_hdf5(images, labels): """ 引數: --------------- images 影象陣列 (N, 32, 32, 3) 格式 labels 標籤陣列 (N,1) 格式 """ num_images = len(images) # 建立一個新的 HDF5 檔案 file = h5py.File(hdf5_dir / f"{num_images}_many.h5", "w") # 在檔案中建立資料集 dataset = file.create_dataset( "images", np.shape(images), h5py.h5t.STD_U8BE, data=images ) meta_set = file.create_dataset( "meta", np.shape(labels), h5py.h5t.STD_U8BE, data=labels ) file.close()
使用 100000 個影象進行測試
cutoffs = [10, 100, 1000, 10000, 100000] images = np.concatenate((images, images), axis=0) labels = np.concatenate((labels, labels), axis=0) # 確保有 100,000 個影象和標籤 print(np.shape(images)) print(np.shape(labels))
建立一個計算方式進行對比
_store_many_funcs = dict( disk=store_many_disk, lmdb=store_many_lmdb, hdf5=store_many_hdf5 ) from timeit import timeit store_many_timings = {"disk": [], "lmdb": [], "hdf5": []} for cutoff in cutoffs: for method in ("disk", "lmdb", "hdf5"): t = timeit( "_store_many_funcs[method](images_, labels_)", setup="images_=images[:cutoff]; labels_=labels[:cutoff]", number=1, globals=globals(), ) store_many_timings[method].append(t) # 列印出方法、截止時間和使用時間 print(f"Method: {method}, Time usage: {t}")
PLOT 顯示具有多個資料集和匹配圖例的單個圖
import matplotlib.pyplot as plt def plot_with_legend( x_range, y_data, legend_labels, x_label, y_label, title, log=False ): """ 引數: -------------- x_range 包含 x 資料的列表 y_data 包含 y 值的列表 legend_labels 字串圖例標籤列表 x_label x 軸標籤 y_label y 軸標籤 """ plt.style.use("seaborn-whitegrid") plt.figure(figsize=(10, 7)) if len(y_data) != len(legend_labels): raise TypeError( "資料集的數量與標籤的數量不匹配" ) all_plots = [] for data, label in zip(y_data, legend_labels): if log: temp, = plt.loglog(x_range, data, label=label) else: temp, = plt.plot(x_range, data, label=label) all_plots.append(temp) plt.title(title) plt.xlabel(x_label) plt.ylabel(y_label) plt.legend(handles=all_plots) plt.show() # Getting the store timings data to display disk_x = store_many_timings["disk"] lmdb_x = store_many_timings["lmdb"] hdf5_x = store_many_timings["hdf5"] plot_with_legend( cutoffs, [disk_x, lmdb_x, hdf5_x], ["PNG files", "LMDB", "HDF5"], "Number of images", "Seconds to store", "Storage time", log=False, ) plot_with_legend( cutoffs, [disk_x, lmdb_x, hdf5_x], ["PNG files", "LMDB", "HDF5"], "Number of images", "Seconds to store", "Log storage time", log=True, )
def read_single_disk(image_id): """ 引數: --------------- image_id 影象的整數唯一 ID 返回結果: --------------- images 影象陣列 (N, 32, 32, 3) 格式 labels 標籤陣列 (N,1) 格式 """ image = np.array(Image.open(disk_dir / f"{image_id}.png")) with open(disk_dir / f"{image_id}.csv", "r") as csvfile: reader = csv.reader( csvfile, delimiter=" ", quotechar="|", quoting=csv.QUOTE_MINIMAL ) label = int(next(reader)[0]) return image, label
def read_single_lmdb(image_id): """ 引數: --------------- image_id 影象的整數唯一 ID 返回結果: --------------- images 影象陣列 (N, 32, 32, 3) 格式 labels 標籤陣列 (N,1) 格式 """ # 開啟 LMDB 環境 env = lmdb.open(str(lmdb_dir / f"single_lmdb"), readonly=True) # 開始一個新的事務 with env.begin() as txn: # 進行編碼 data = txn.get(f"{image_id:08}".encode("ascii")) # 載入的 CIFAR_Image 物件 cifar_image = pickle.loads(data) # 檢索相關位 image = cifar_image.get_image() label = cifar_image.label env.close() return image, label
def read_single_hdf5(image_id): """ 引數: --------------- image_id 影象的整數唯一 ID 返回結果: --------------- images 影象陣列 (N, 32, 32, 3) 格式 labels 標籤陣列 (N,1) 格式 """ # 開啟 HDF5 檔案 file = h5py.File(hdf5_dir / f"{image_id}.h5", "r+") image = np.array(file["/image"]).astype("uint8") label = int(np.array(file["/meta"]).astype("uint8")) return image, label
from timeit import timeit read_single_timings = dict() for method in ("disk", "lmdb", "hdf5"): t = timeit( "_read_single_funcs[method](0)", setup="image=images[0]; label=labels[0]", number=1, globals=globals(), ) read_single_timings[method] = t print(f"讀取方法: {method}, 使用耗時: {t}")
儲存方法 | 儲存耗時 |
---|---|
Disk | 1.7 ms |
LMDB | 4.4 ms |
HDF5 | 2.3 ms |
將多個影象儲存為.png檔案就可以理解為多次呼叫 read_single_method() 這樣。但這不適用於 LMDB 或 HDF5,因為每個影象都有不同的資料庫檔案。
從磁碟中讀取多個都影象
def read_many_disk(num_images): """ 引數: --------------- num_images 要讀取的影象數量 返回結果: --------------- images 影象陣列 (N, 32, 32, 3) 格式 labels 標籤陣列 (N,1) 格式 """ images, labels = [], [] # 迴圈遍歷所有ID,一張一張地讀取每張圖片 for image_id in range(num_images): images.append(np.array(Image.open(disk_dir / f"{image_id}.png"))) with open(disk_dir / f"{num_images}.csv", "r") as csvfile: reader = csv.reader( csvfile, delimiter=" ", quotechar="|", quoting=csv.QUOTE_MINIMAL ) for row in reader: labels.append(int(row[0])) return images, labels
從LMDB中讀取多個都影象
def read_many_lmdb(num_images): """ 引數: --------------- num_images 要讀取的影象數量 返回結果: --------------- images 影象陣列 (N, 32, 32, 3) 格式 labels 標籤陣列 (N,1) 格式 """ images, labels = [], [] env = lmdb.open(str(lmdb_dir / f"{num_images}_lmdb"), readonly=True) # 開始一個新的事務 with env.begin() as txn: # 在一個事務中讀取,也可以拆分成多個事務分別讀取 for image_id in range(num_images): data = txn.get(f"{image_id:08}".encode("ascii")) # CIFAR_Image 物件,作為值儲存 cifar_image = pickle.loads(data) # 檢索相關位 images.append(cifar_image.get_image()) labels.append(cifar_image.label) env.close() return images, labels
從HDF5中讀取多個都影象
def read_many_hdf5(num_images): """ 引數: --------------- num_images 要讀取的影象數量 返回結果: --------------- images 影象陣列 (N, 32, 32, 3) 格式 labels 標籤陣列 (N,1) 格式 """ images, labels = [], [] # 開啟 HDF5 檔案 file = h5py.File(hdf5_dir / f"{num_images}_many.h5", "r+") images = np.array(file["/images"]).astype("uint8") labels = np.array(file["/meta"]).astype("uint8") return images, labels _read_many_funcs = dict( disk=read_many_disk, lmdb=read_many_lmdb, hdf5=read_many_hdf5 )
建立一個計算方式進行對比
from timeit import timeit read_many_timings = {"disk": [], "lmdb": [], "hdf5": []} for cutoff in cutoffs: for method in ("disk", "lmdb", "hdf5"): t = timeit( "_read_many_funcs[method](num_images)", setup="num_images=cutoff", number=1, globals=globals(), ) read_many_timings[method].append(t) # Print out the method, cutoff, and elapsed time print(f"讀取方法: {method}, No. images: {cutoff}, 耗時: {t}")
同一張圖表上檢視讀取和寫入時間
plot_with_legend( cutoffs, [disk_x_r, lmdb_x_r, hdf5_x_r, disk_x, lmdb_x, hdf5_x], [ "Read PNG", "Read LMDB", "Read HDF5", "Write PNG", "Write LMDB", "Write HDF5", ], "Number of images", "Seconds", "Log Store and Read Times", log=False, )
各種儲存方式使用磁碟空間
雖然 HDF5 和 LMDB 都佔用更多的磁碟空間。需要注意的是 LMDB 和 HDF5 磁碟的使用和效能在很大程度上取決於各種因素,包括作業系統,更重要的是儲存的資料大小。
通常對於大的資料集,可以通過並行化來加速操作。 也就是我們經常說的並行處理。
作為.png 檔案儲存到磁碟實際上允許完全並行。只要影象名稱不同就可以從不同的執行緒讀取多個影象,或一次寫入多個檔案。
如果將所有 CIFAR 分成十組,那麼可以為一組中的每個讀取設定十個程序,並且相應的處理時間可以減少到原來的10%左右。
以上就是Python圖片儲存和存取的三種方式詳解的詳細內容,更多關於Python圖片儲存存取的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45