MXNet
框架用于做图像相关的项目时,读取图像主要有两种方式:第一种是读.rec
格式的文件,类似Caffe
框架中LMDB
,优点是.rec
文件比较稳定,移植到别的电脑上也能复现,缺点是占空间(.rec
文件的大小基本上和图像的存储大小差不多),而且增删数据不大灵活。第二种是.lst和图像结合的方式,首先在前面生成.rec文件的过程中也会生成.lst文件,这个.lst
文件就是图像路径和标签的对应列表,也就是说通过维护这个列表来控制你训练集和测试集的变化,优点是灵活且不占空间,缺点是如果图像格式不符合要求的话容易出错而且如果列表中的某些图像路径对应的图像文件夹中图像被删除,就寻找不到,另外如果你不是从固态硬盘上读取图像的话,速度会很慢。
MXNet
的图像数据导入模块主要有mxnet.io.ImageRecordIter
和mxnet.image.ImageIter
两个类,前者主要用来读取.rec格式的数据,后者既可以读.rec
格式文件,也可以读原图像数据。脚本image.py
可以在~/mxnet/python/mxnet/image.py
找到.
主要分两步:生成.lst
和生成.rec
需要准备的就是你的图像。假设你的图像数据放在/home/image
文件夹下,一共有10个类别,那么在/home/image
文件夹下应该有10个子文件夹,每个子文件夹放属于这个类的图像文件,你可以用英文名命名这些子文件夹来表达类别,这个都无所谓,即便用1到10这10个数字来分别命名这10个子文件夹也没什么,只不过用英文名会方便你记忆这个文件夹包含的图像是属于哪个类别的。另外假设你要将生成的.lst文件放在/home/lst
文件夹下,你的mxnet
项目的路径是~/incubator-mxnet
,那么运行下面的命令就可以生成.lst
文件:
python ~/incubator-mxnet/tools/im2rec.py --list True --recursive True --train-ratio 0.9 /home/lst/data /home/image
or
os.system('python %s/tools/im2rec.py --list=1 --recursive=1 --shuffle=1 --test-ratio=0.2 data/caltech data/101_ObjectCategories'%os.environ['MXNET_HOME'])
–list
参数必须要是True
,说明你是要生成.lst
文件,–recursive
参数必须为True
,表示要将所有图像路径写进成.lst
文件,–train-ratio
参数表示将train和val以多少比例划分,默认为1,表示都是train的数据。
这样在/home/lst
文件夹下就会生成data_train.lst
和data_val.lst
两个文件。
.lst
文件样例:第一列是index,第二列是label,第三列是图像路径
当然有时候可能你的数据图像不是按照一个类别放在一个文件夹这种方式,那么就要考虑修改这个脚本来生成相同格式的.lst文件才能用于后续生成.rec文件。
需要准备的就是第一步生成的.lst文件和你的图像。
假设你要将生成的.rec文件放在.lst文件相同的/home/lst文件夹下(一般都会这样操作),那么运行下面的命令就可以生成.rec文件:
python ~/incubator-mxnet/tools/im2rec.py --num-thread 4 /home/lst /home/image
or
os.system("python %s/tools/im2rec.py --num-thread=4 --pass-through=1 data/caltech data/101_ObjectCategories"%os.environ['MXNET_HOME'])
这里倒数第二个参数:/home/lst是你的.lst文件所放的路径,可以不用指明.lst文件名称,因为代码会自动搜索/home/lst文件夹下所有以.lst结尾的文件。最后一个参数:/home/image是你的图像所放的路径。–num-thread 4 这个参数是表示用4个线程来执行,当你数据量较大的时候,生成.rec的过程会比较慢,所以这样可以加速。
运行成功后,在/home/rec文件夹下就生成了data_train.rec和data_val.rec文件,然后就可以用mxnet.io.ImageRecordIter
类来导入.rec文件了。
另外还会生成两个.idx文件,可忽略。
mx.io.ImageRecordIter(
path_imgrec = args.data_train,
path_imgidx = args.data_train_idx,
label_width = 1,
mean_r = rgb_mean[0],
mean_g = rgb_mean[1],
mean_b = rgb_mean[2],
data_name = 'data',
label_name = 'softmax_label',
data_shape = image_shape,
batch_size = args.batch_size,
rand_crop = args.random_crop,
max_random_scale = args.max_random_scale,
pad = args.pad_size,
fill_value = 127,
min_random_scale = args.min_random_scale,
max_aspect_ratio = args.max_random_aspect_ratio,
random_h = args.max_random_h,
random_s = args.max_random_s,
random_l = args.max_random_l,
max_rotate_angle = args.max_random_rotate_angle,
max_shear_ratio = args.max_random_shear_ratio,
rand_mirror = args.random_mirror,
preprocess_threads = args.data_nthreads,
shuffle = True,
num_parts = nworker,
part_index = rank)
这里.lst文件的生成和前面1.1部分的一样。然后用mxnet.image.ImageIter
类来导入.lst和图像数据。
mxnet.image.ImageIter
是一个非常重要的类。在MXNet中,当你要读入图像数据时,可以用im2rec.py生成lst和rec文件,然后用mxnet.io.ImageRecordIter
类来读取rec文件或者用这个mxnet.image.ImageIter
类来读取rec文件,但是这个函数和前者相比还能直接读取图像文件,这样就可以不用生成占内存的rec文件了,只需要原图像文件和lst文件即可。另外,在mxnet.io.ImageRecordIter
中对于数据的预处理操作都是固定的,不好修改,但是mxnet.image.ImageIter却可以非常灵活地添加各种预处理操作。接下来看看这个类。
mxnet.image.ImageIter
官方文档中一个ImageIter类的使用例子:
data_iter = mx.image.ImageIter(batch_size=4, data_shape=(3, 227, 227),
path_imgrec="./data/caltech.rec",
path_imgidx="./data/caltech.idx" )
# data_iter的类型是mxnet.image.ImageIter
#reset()函数的作用是:resents the iterator to the beginning of the data
data_iter.reset()
#batch的类型是mxnet.io.DataBatch,因为next()方法的返回值就是DataBatch
batch = data_iter.next()
#data是一个NDArray,表示第一个batch中的数据,因为这里的batch_size大小是4,所以data的size是4*3*227*227
data = batch.data[0]
#这个for循环就是读取这个batch中的每张图像并显示
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.image.CreateAugmenter()
函数,这个函数完整的定义是这样的:
mxnet.image.CreateAugmenter(data_shape, resize=0, rand_crop=False, rand_resize=False, rand_mirror=False, mean=None, std=None, brightness=0, contrast=0, saturation=0, pca_noise=0, inter_method=2)
这个函数具体的内容可以参看:~/mxnet/python/mxnet/image.py
修改data.py从而改变数据读取入口!路径是:~/mxnet/example/image-classification/common/data.py
。
找到data.py脚本中的get_rec_iter()
函数,可以看到get_rec.iter()里面有这一部分:
train = mx.io.ImageRecordIter(
path_imgrec = args.data_train,
label_width = 1,
mean_r = rgb_mean[0],
mean_g = rgb_mean[1],
mean_b = rgb_mean[2],
data_name = 'data',
label_name = 'softmax_label',
data_shape = image_shape,
batch_size = args.batch_size,
rand_crop = args.random_crop,
max_random_scale = args.max_random_scale,
pad = args.pad_size,
fill_value = 127,
min_random_scale = args.min_random_scale,
max_aspect_ratio = args.max_random_aspect_ratio,
random_h = args.max_random_h,
random_s = args.max_random_s,
random_l = args.max_random_l,
max_rotate_angle = args.max_random_rotate_angle,
max_shear_ratio = args.max_random_shear_ratio,
rand_mirror = args.random_mirror,
preprocess_threads = args.data_nthreads,
shuffle = True,
num_parts = nworker,
part_index = rank)
这段代码就是从rec文件读取数据的过程。现在我们不用mx.io.ImageRecordIter()
,而是改用mx.image.ImageIter()
,修改如下:
train = mx.image.ImageIter(
batch_size = args.batch_size,
data_shape = (3,224,224),
label_width = 1,
path_imglist = args.data_train,
path_root = args.image_train,
part_index = rank,
shuffle = True,
data_name = 'data',
label_name = 'softmax_label',
aug_list = mx.image.CreateAugmenter((3,224,224),resize=224,rand_crop=True,rand_mirror=True,mean=True))
这里的path_imglist参数和path_root参数是这个类特有的,分别表示.lst文件和图像的路径,这个.lst文件就是你在生成.rec文件时候要用到的,因此这个类只是不需要.rec文件,但是.lst文件还是需要的,只是一个列表文件,大大节省了存储空间,也方便以后对数据的增删改变,因为只要重新生成.lst文件即可,而不需要花时间生成占空间的.rec文件。另外因为原本的data.py脚本中没有args.image_train,所以你需要自己添加,就按其他args的一样来添加即可,这样就可以导入这个参数了。
val部分可以类似修改,这里最重要的就是最后一个参数aug_list,表示所有预处理的列表,不过在val中一般不会有类似crop,mirror等操作。为什么会用到aug_list这个参数呢?来自于image.py脚本中ImageIter类的init()函数的这几行代码:
if aug_list is None:
self.auglist = CreateAugmenter(data_shape, **kwargs)
else:
self.auglist = aug_list
就是如果aug_list这个参数没有赋值(默认是None),那么就不对图像做预处理;如果这个参数有值,那么就调用CreateAugmenter()函数生成预处理列表。