Mxnet的數據(圖像和非圖像)讀取方式太太太多了。基於mxnet的、基於gluon的。 主要是Data iterators很多:
必讀api:Iterators - Loading data
API給出的數據迭代器:主要是基於io和基於image的兩大類:
這么多的數據讀取方法,針對圖像而言,Mxnet主要有四種:
Image IO
- 1. 利用mx.image.imdecode來load原始的圖像數據
- 2. 利用
mx.img.ImageIter
來實現,非常靈活,易於定制(做transform),可以同時讀取.rec格式和原始image格式,所以應當主要使用該方法。 - 3. 利用
mx.io.ImageRecordIter實現,基於C++后端,沒那么靈活但是易於擴展到其他語言
- 4. 利用mx.io.DataIter來自己定制
方式1:
class mxnet.gluon.data.vision.datasets.
ImageFolderDataset
(root, flag=1, transform=None)[source]
基於 mxnet.gluon.data.dataset.Dataset的類。即繼承自Dataset類。官網api地址
參數:
root:root文件夾路徑
flag:0/1,如果是0則將輸入圖像轉為灰度1通道,如果是1,則轉為彩圖3通道。
transform:可callable的,就是說類里要有call函數。默認是None。
用法:
用法非常簡單,如果用過pytorch中torchvision的ImageFolder的話,這個基本一樣了。
首先需要將數據做成這種格式:
root文件夾下有多個類別,每個類別是一個文件夾,里面是該類的數據。然后讀取方法:
import mxnet as mx from mxnet import gluon import numpy as np transform = lambda data, label: (data.astype(np.float32)/255, label) # transform train_imgs = gluon.data.vision.ImageFolderDataset(root='/Users/lps/MacProjects/mxnet/root', # root路徑 transform=transform) print(train_imgs.items) #打印出所有圖像信息: (filename, label) 對. print(train_imgs.synsets) # 列出所有類名 synsets[i] 是 label i所對應的類名 data = gluon.data.DataLoader(train_imgs, 32, shuffle=True) # 類似pytorch,dataset需要放到dataloader里進行打包 iter(data).__next__() # 可以打印出一個批量的數據
方式2.
class mxnet.gluon.data.vision.datasets.
ImageListDataset
(root='.', imglist=None, flag=1)[source]
Bases: mxnet.gluon.data.dataset.Dataset。也是dataset類,所以用法和方式1基本一致。
參數:
root就是list文件的路徑
imglist:
如果是一個純txt文件,里面的格式應該是這樣的:
如果是一個 .lst文件,則應該是這樣的格式:
方式3.
class mxnet.io.
NDArrayIter
(data, label=None, batch_size=1, shuffle=False, last_batch_handle='pad', data_name='data', label_name='softmax_label')[source]
基於 Bases: mxnet.io.io.DataIter,即繼承自DataIter類。
參數:易懂。
用法:可以看到這個類已經是個iter了,所以直接讀取就行:
import mxnet as mx import numpy as np data = np.random.rand(100, 3) # 100個數據每個數據3特征 label = np.random.randint(0, 10, (100,)) # 100個標簽 data_iter = mx.io.NDArrayIter(data=data, label=label, batch_size=30) # 創建了類似dataloader的東西,可以迭代 for batch in data_iter: print([batch.data, batch.label, batch.pad], '\n')
方式4.
mxnet.io.
CSVIter
(*args, **kwargs))
提供從csv文件中讀取的接口,同樣也是一個iter。
#lets save `data` into a csv file first and try reading it back np.savetxt('data.csv', data, delimiter=',') # data_shape對應不上會報錯 data_iter = mx.io.CSVIter(data_csv='data.csv', data_shape=(3,), batch_size=30) for batch in data_iter: print([batch.data, batch.pad])
方式5.
mxnet.recordio 模塊 API
RecordIO是MXNet用於數據IO的文件格式,文件后綴為.rec。它緊湊地打包數據,以便從Hadoop HDFS和AWS S3等分布式文件系統進行高效的讀寫。 MXNet提供MXRecordIO 和MXIndexedRecordIO,用於數據的順序訪問和隨機訪問。
注意,.rec文件寫入的必須是整數或者二進制數據。
該模塊主要包括3個類:
IRHeader HEADER的別名
MXRecordIO
(uri, flag) 讀寫記錄數據格式,支持順序讀寫。
MXIndexedRecordIO
(idx_path, uri, flag[, …]) 讀寫記錄數據格式,支持隨機存取。
二進制數據的裝包(mx.recordio.pack)與拆包(mx.recordio.unpack)。 pack 和unpack 用於存儲浮點數(或1維浮點數組)標簽和二進制數據。
圖像數據的裝包與拆包,由於圖片數據在DL中尤為常用,所以單獨給圖片數組設計出接口,這個接口可以接收numpy數組,自動將之轉化為二進制數據存入文件,解壓時逆向操作。MXNet提供pack_img 和unpack_img 來打包/解壓圖像數據,pack_img 打包的記錄可以由mx.io.ImageRecordIter 加載。
該模塊主要包括4個函數:
pack
(header, s) 打包一個字符串到MXImageRecord
pack_img
(header, img[, quality, img_fmt]) 打包一個image到MXImageRecord
unpack
(s) 解包一個MXImageRecord到string
unpack_img
(s[, iscolor]) 解包一個MXImageRecord到圖像
1-1. 第一個類IRHeader
1-2. 第二個類 class mxnet.recordio.
MXRecordIO
(uri, flag)
繼承自object,也是第三個類MXIndexedRecordIO
的父類。
參數:
uri:字符串型,指向recored文件的路徑
flag:字符串型,‘w’為寫,‘r’為讀
方法:
close():關閉 record文件
open():打開record文件
read(): 以字符串形式返回record
reset(): 重制指向第一項的指針
write(): 將字符串緩沖區作為record插入。
用法:
利用MXRecordIO順序寫入:
import mxnet as mx import numpy as np record = mx.recordio.MXRecordIO('tmp.rec', 'w') for i in range(5): record.write(b'record_%d'%i) record.close()
這時會生成一個tmp.rec文件。讀取的時候:
import mxnet as mx import numpy as np record = mx.recordio.MXRecordIO('tmp.rec', 'r') for i in range(5): item = record.read() print(item)
1-3. 第三個類 class mxnet.recordio.
MXIndexedRecordIO
(idx_path, uri, flag, key_type=<class 'int'>)
參數:
idx_path: index文件的路徑
uri:字符串型,指向recored文件的路徑
flag:字符串型,‘w’為寫,‘r’為讀
key_type: keys的數據類型
方法:
close():關閉 record文件
open():打開record文件
read_idx(): 給定idx返回record
seek(): 設置當前讀取指針位置
tell():返回寫入頭的當前位置。
write_idx(): 在給定索引處插入record。
用法:
MXIndexedRecordIO 支持隨機或索引訪問數據。 我們將創建一個索引記錄文件和一個相應的索引文件,如下所示:
import mxnet as mx import numpy as np record = mx.recordio.MXIndexedRecordIO('tmp.idx', 'tmp.rec', 'w') for i in range(5): record.write_idx(i, b'record_%d'%i) record.close()
這時會生成兩個文件:tmp.idx和tmp.rec,讀取他們:
record = mx.recordio.MXIndexedRecordIO('tmp.idx', 'tmp.rec', 'r') record.read_idx(3) # 利用鍵來讀
2-1 第一個和第三個函數:
mxnet.recordio.
pack
(header, s)[source]
mxnet.recordio.
unpack
(s)[source]
參數:
header:IRHeader類型,image record的header。header.label可以是數字或數組。參閱IRHeader中的更多詳細信息。
s:字符串,要被打包的raw image string
返回 s:得到的打包好的string
用法:
# pack data = b'data' label1 = 1.0 header1 = mx.recordio.IRHeader(flag=0, label=label1, id=1, id2=0) s1 = mx.recordio.pack(header1, data) label2 = [1.0, 2.0, 3.0] header2 = mx.recordio.IRHeader(flag=3, label=label2, id=2, id2=0) s2 = mx.recordio.pack(header2, data) # unpack print(mx.recordio.unpack(s1)) print(mx.recordio.unpack(s2))
(HEADER(flag=0, label=1.0, id=1, id2=0), b'data') (HEADER(flag=3, label=array([ 1., 2., 3.], dtype=float32), id=2, id2=0), b'data')
2-2 第二個和第四個函數:
mxnet.recordio.
pack_img
(header, img, quality=95, img_fmt='.jpg')[source]
mxnet.recordio.
unpack_img
(s, iscolor=-1)[source]
用法:
data = np.ones((3,3,1), dtype=np.uint8) label = 1.0 header = mx.recordio.IRHeader(flag=0, label=label, id=0, id2=0) s = mx.recordio.pack_img(header, data, quality=100, img_fmt='.jpg') # unpack_img print(mx.recordio.unpack_img(s))
(HEADER(flag=0, label=1.0, id=0, id2=0),
array([[1, 1, 1],
[1, 1, 1],
[1, 1, 1]], dtype=uint8))
方式6 使用im2rec進行打包
MXNet框架用於做圖像相關的項目時,讀取圖像主要有兩種方式:
- 第一種是讀.rec格式的文件,類似Caffe框架中LMDB,優點是.rec文件比較穩定,移植到別的電腦上也能復現,缺點是占空間(.rec文件的大小基本上和圖像的存儲大小差不多),而且增刪數據不大靈活。需要idx搭配使用,下面腳本會一並生成。
- 第二種是.lst和圖像結合的方式,首先在前面生成.rec文件的過程中也會生成.lst文件,這個.lst文件就是圖像路徑和標簽的對應列表,也就是說通過維護這個列表來控制你訓練集和測試集的變化,優點是靈活且不占空間,缺點是如果圖像格式不符合要求的話容易出錯而且如果列表中的某些圖像路徑對應的圖像文件夾中圖像被刪除,就尋找不到,另外如果你不是從固態硬盤上讀取圖像的話,速度會很慢。
1.生成.lst
數據准備:
# .
# └── MacProjects
# ├── mxnet
# ├── im2rec.py
# └── images
# ├── cat
# └── dog
那么運行下面的命令就可以生成.lst文件:
python3 im2rec.py --list --recursive --train-ratio 0.9 mxrec/dog_cat_cls images
--list 說明要產生lst文件
--recursive 遍歷所有子文件夾,炳輝給每個子文件夾一個編號
--train_ratio 確定訓練集和測試集的比例
mxrec/dog_cat_cls 指的是文件命名前綴,存下來的文件會在mxrec文件夾:兩個文件:dog_cat_cls_train.lst、dog_cat_cls_val.lst
images:要遍歷的文件夾名字
2.生成.rec
python3 im2rec.py mxrec/dog_cat_cls images --resize 16 --num-thread 4
得到lst文件后就可以根據該文件以及圖像,生成rec和idx文件
參數同上,--num-thread 表示線程數。 --resize就是將圖像resize后保存
此時得到的文件共六個,train/val 以及各自有.lst、.idx、.rec文件。
3. 數據讀取。得到rec文件后,有兩種方式來讀取:MXNet的圖像數據導入模塊主要有mxnet.io.ImageRecordIter和mxnet.image.ImageIter兩個類,前者主要用來讀取.rec格式的數據,后者既可以讀.rec格式文件,也可以讀原圖像數據。
3.1 mxnet.io.
ImageRecordIter
(*args, **kwargs)
參數:及其多,建議直接看api
返回類型:MXDataIter
import mxnet as mx import matplotlib.pyplot as plt import numpy as np data_iter = mx.io.ImageRecordIter( path_imgrec="/Users/bytedance/MacProjects/mxrec/dog_cat_cls_val.rec", # the target record file data_shape=(3, 16, 16), # output data shape. An 227x227 region will be cropped from the original image. batch_size=4, # number of samples per batch resize=16 # resize the shorter edge to 256 before cropping # ... you can add more augumentation options as defined in ImageRecordIter. ) data_iter.reset() # Reset the iterator to the begin of the data. batch = data_iter.next() data = batch.data[0] for i in range(4): plt.subplot(1,4,i+1) plt.imshow(data[i].asnumpy().astype(np.uint8).transpose((1,2,0))) plt.show()
data_iter.getdata() # 得到批量數據
data_iter.getindex # 得到批量的index
data_iter.getlabel() # 得到批量標簽
3.2 class mxnet.image.
ImageIter
(batch_size, data_shape, label_width=1, path_imgrec=None, path_imglist=None, path_root=None, path_imgidx=None, shuffle=False, part_index=0, num_parts=1, aug_list=None, imglist=None, data_name='data', label_name='softmax_label', dtype='float32', last_batch_handle='pad', **kwargs)[source]
import mxnet as mx import matplotlib.pyplot as plt import numpy as np # ImageIter 是一個靈活的界面,支持以RecordIO和Raw格式加載圖像 data_iter = mx.image.ImageIter(batch_size=4, data_shape=(3, 227, 227), path_imgrec="/Users/bytedance/MacProjects/mxrec/dog_cat_cls_val.rec", # 需要rec和idx path_imgidx="/Users/bytedance/MacProjects/mxrec/dog_cat_cls_val.idx") data_iter.reset() batch = data_iter.next() data = batch.data[0] for i in range(4): plt.subplot(1, 4, i + 1) plt.imshow(data[i].asnumpy().astype(np.uint8).transpose((1, 2, 0))) plt.show()
mxnet.image.ImageIter是一個非常重要的類。在MXNet中,當你要讀入圖像數據時,可以用im2rec.py生成lst和rec文件,然后用mxnet.io.ImageRecordIter類來讀取rec文件或者用這個mxnet.image.ImageIter類來讀取rec文件,但是這個函數和前者相比還能直接讀取圖像文件,這樣就可以不用生成占內存的rec文件了,只需要原圖像文件和lst文件即可。另外,在mxnet.io.ImageRecordIter中對於數據的預處理操作都是固定的,不好修改,但是mxnet.image.ImageIter卻可以非常靈活地添加各種預處理操作。
使用.lst和圖像時,示意如下:
import mxnet as mx import matplotlib.pyplot as plt import numpy as np # ImageIter 是一個靈活的界面,支持以RecordIO和Raw格式加載圖像 data_iter = mx.image.ImageIter( batch_size = 3, data_shape = (3,16,16), label_width = 1, path_imglist = "/Users/bytedance/MacProjects/mxrec/dog_cat_cls_val.lst", # 需要lst和images path_root = '/Users/bytedance/MacProjects/images', part_index = 0, shuffle = True, data_name = 'data', label_name = 'softmax_label', aug_list = mx.image.CreateAugmenter((3,16,16),resize=16,rand_crop=True,rand_mirror=True,mean=True)) data_iter.reset() batch = data_iter.next() data = batch.data[0] for i in range(4): plt.subplot(1, 4, i + 1) plt.imshow(data[i].asnumpy().astype(np.uint8).transpose((1, 2, 0))) plt.show()
這里的path_imglist參數和path_root參數是這個類特有的,分別表示.lst文件和圖像的路
只是一個列表文件,大大節省了存儲空間,也方便以后對數據的增刪改變,因為只要重新生成.lst文件即可,而不需要花時間生成占空間的.rec文件。
如果aug_list這個參數沒有賦值(默認是None),那么就不對圖像做預處理;如果這個參數有值,那么就調用CreateAugmenter()函數生成預處理列表。
CreateAugmenter:

1 def CreateAugmenter(data_shape, resize=0, rand_crop=False, rand_resize=False, rand_mirror=False,mean=None, 2 std=None, brightness=0, contrast=0, saturation=0, 3 pca_noise=0, inter_method=2): 4 """Creates an augmenter list.""" 5 auglist = [] 6 7 8 # resize這個參數很重要,一般都要做resize,如果你的resize參數設置為224,你的原圖像是350*300,那么最后resize的大小就是 9 # (350*300/224)*224。這里ResizeAug()函數調用resize_short()函數,resize_short()函數調用OpenCV的imresize()函數完成resize 10 # ,interp參數為2表示采用雙三次插值做resize,可以參考:http://docs.opencv.org/master/da/d54/group__imgproc__transform.html。 11 if resize > 0: 12 auglist.append(ResizeAug(resize, inter_method)) 13 14 crop_size = (data_shape[2], data_shape[1]) 15 16 # 如果rand_resize參數是true,那么會調用RandomSizedCropAug()函數,輸入是size,min_area,retio,interp, 17 # 這個函數既做resize又做crop,因此這邊才會寫成if elif的語句。RandomSizedCropAug()函數調用random_size_crop()函數, 18 # 這個函數會先生成隨機的坐標點和長寬值,然后調用fixed_crop()函數做crop。 19 #這里還有一個語句是assert rand_crop,python的assert語句是用來聲明其布爾值必須為真,如果表達式為假,就會觸發異常。 20 # 也就是說要調用RandomSizedCropAug()函數的前提是rand_crop是True。 21 if rand_resize: 22 assert rand_crop 23 auglist.append(RandomSizedCropAug(crop_size, 0.3, (3.0 / 4.0, 4.0 / 3.0), inter_method)) 24 25 #如果rand_crop參數是true,表示隨機裁剪,randomCropAug()函數的輸入之一是crop_size, 26 # 這個crop_size就是CreateAugmenter()函數的輸入data_shape的圖像大小,然后randomCropAug()函數調用random_crop()函數, 27 # random_crop()函數會先生成新的長寬值和坐標點,然后以此調用fixed_crop()函數做crop, 28 # 最后返回crop后的圖像和坐標即長寬值,因為生成中心坐標點的時候是隨機的,所以還是random crop。 29 elif rand_crop: 30 auglist.append(RandomCropAug(crop_size, inter_method)) 31 32 # 如果前面兩個if條件都不滿足,就調用CenterCropAug()函數做crop,這個函數的輸入也包括了crop_size,也就是你的輸入data_shape, 33 # 所以這個參數是很有用的。CenterCropAug()函數調用center_crop()函數,這個函數的輸入輸出都是NDArray格式。 34 # center_crop()函數和random_crop()函數的區別在於前者坐標點的生成不是隨機的,而是和原圖像一樣, 35 # 然后再將坐標點和新的長寬作為fixed_crop()函數的輸入。 36 else: 37 auglist.append(CenterCropAug(crop_size, inter_method)) 38 #可以看出不管你是否要做crop,只要你給定了data_shape參數,就默認要將輸入圖像做crop操作。 39 # 因此如果你不想在test的時候做crop,可以在這修改源碼。 40 41 # 隨機鏡像處理,參數是0.5,HorizontalFlipAug()函數調用nd.flip()函數做水平翻轉 42 if rand_mirror: 43 auglist.append(HorizontalFlipAug(0.5)) 44 45 46 # CastAug()函數主要是將數據格式轉化為float32 47 auglist.append(CastAug()) 48 49 50 # 這三個參數分別是亮度,對比度,飽和度。當你對這三個參數設置了值, 51 # 就會調用ColorJitterAug()函數對其相應的亮度或對比度或飽和度做改變 52 if brightness or contrast or saturation: 53 auglist.append(ColorJitterAug(brightness, contrast, saturation)) 54 55 56 # 這個部分主要是添加pca噪聲的,具體可以看LightingAug()函數 57 if pca_noise > 0: 58 eigval = np.array([55.46, 4.794, 1.148]) 59 eigvec = np.array([[-0.5675, 0.7192, 0.4009], 60 [-0.5808, -0.0045, -0.8140], 61 [-0.5836, -0.6948, 0.4203]]) 62 auglist.append(LightingAug(pca_noise, eigval, eigvec)) 63 64 65 # mean這個參數主要是和歸一化相關。這里的assert語句前面已經介紹過了。mean參數默認是None,這種情況下是不會進入下面的if elif條件函數的。 66 # 如果想進行均值操作,可以設置mean為True,那么就會進入第一個if條件,如果你設置為其他值,就會進入elif條件, 67 # 這個時候如果你的mean不符合要求,比如isinstance函數用來判斷類型,就會觸發異常。 68 if mean is True: 69 mean = np.array([123.68, 116.28, 103.53]) 70 elif mean is not None: 71 assert isinstance(mean, np.ndarray) and mean.shape[0] in [1, 3] 72 73 # std與mean同理 74 if std is True: 75 std = np.array([58.395, 57.12, 57.375]) 76 elif std is not None: 77 assert isinstance(std, np.ndarray) and std.shape[0] in [1, 3] 78 79 # 這里需要mean和std同時都設置正確才能進行預處理,如果你只設置了mean,沒有設置std,那么還是沒有啟動歸一化的預處理。 80 # 這里主要調用ColorNormalizeAug()函數,這個函數調用color_normalize()函數,這個函數的實現很簡單, 81 # 就是將原圖像的像素值減去均值mean,然后除以標准差std得到返回值。 82 if mean is not None and std is not None: 83 auglist.append(ColorNormalizeAug(mean, std)) 84 85 # 最后返回預處理的列表 86 return auglist
Ref:
『MXNet』第八彈_數據處理API_上
第八彈_數據處理API_下_Image IO專題
im2rec腳本使用以及數據讀取