im2rec.py是MXNet提供的一个将图片转为rec文件的工具。 当训练数据包含大量图片的时候,一次性将所有数据载入内存很容易导致out of memory。使用rec文件和MXNet提供的 ImageRecordIter或ImageIter迭代器,可以按批次读取数据到内存,且读取图片更加简单方便。本文将介绍.lst,.rec文件的生成、多标签文件的处理,ImageRecordIter和ImageIter迭代器的使用以及比较。
Platform: Ubuntu 16.04
MXNet: 0.11.0
首先需要下载mxnet,找到tools文件夹的im2rec.py文件,我的是
/home/username/mxnet/tools/im2rec.py
然后需要准备图片数据,放入test_img文件夹中。
提示:最好将图片统一size,避免使用ImageRecordIter时将图片裁剪。
执行命令
python /home/username/mxnet/tools/im2rec.py --list=1 my_test test_img
--list=1
表示制作.lst而不是.rec文件。
my_test
是.lst的文件名。
test_img
是图片所在文件夹。
更多参数设置参考文档。
运行完毕后打开my_test.lst,格式如下
1 0.000000 xxxx.jpg
0 0.000000 xxxx.jpg
...
每列之间都是\t
分隔。
第一列数字是编号,第二列是标签,第三列是文件名。
此处因为只有一个图片文件夹,因此其标签分类只有一个,即0。现实情况是多个文件夹存放不同分类的图片,im2rec会根据不同文件夹生成不同的标签。例如Caltech 101数据集:
生成的.lst文件为
3073 6.000000 airplanes/image_0684.jpg
7167 69.000000 okapi/image_0017.jpg
6153 52.000000 ibis/image_0073.jpg
7761 81.000000 scissors/image_0005.jpg
7792 81.000000 scissors/image_0036.jpg
...
6对应的分类为airplanes。详见【MXNet官方教程5】Iterators-加载数据。
如果图片为多标签数据,需要手动修改第二列。参见本文后面部分。
有了.lst文件后,直接生成.rec文件:
python /home/username/mxnet/tools/im2rec.py --num-thread=4 my_test.lst test_img
更多参数参考文档。
在当前目录下生成了my_test.idx
和my_test.rec
两个文件。接下来就可以从.idx和.rec文件中读取图片了。
在【MXNet官方教程5】Iterators-加载数据中介绍过使用方法,这里再解释一下。
data_iter = mx.image.ImageRecordIter(batch_size=4,
resize=30,
data_shape=(3, 30, 60),# depth,height,width
path_imgrec="./xxx/my_test.rec",
path_imgidx="./xxx/my_test.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()
data_shape
:图片的size,前文中提到最好将图片size统一。如果不统一,ImageRecordIter将按照data_shape
对图片裁剪,可能丢失图片信息。
resize
:当图片短边小于data_shape
时,resize成给定大小。
更多参数配置参考文档。
what(): [15:45:50] src/io/image_aug_default.cc:300: Check failed:
static_cast(res.rows) >= param_.data_shape[1] &&
static_cast(res.cols) >= param_.data_shape[2]
input image size smaller than input shape
原因是resize值小于图片实际长度。
一些场景比如目标检测下,一张图片有多个标签(bbox)。需要修改.lst文件为以下格式:
46330 1 0.1 0.1 0.3 0.3 xxxx.jpg
213723 2 1.1 1.1 1.3 1.3 xxxx.jpg
173090 3 2.1 2.1 2.3 2.3 xxxx.jpg
...
每列之间都是\t
分隔。
其中,第2-6列为标签数据(比如一个class标签和4个bbox标签)。
参考脚本:
with open('my_test.lst', 'w+') as f:
for i in range(3):
f.write(
str(i) + '\t' +
# idx
str(i) + '\t' +
# class
str((i / 10)) + '\t' + str((i / 10)) + '\t' + str(((i + 3) / 10)) + '\t' +str(((i + 3) / 10)) + '\t' +
# xmin, ymin, xmax, ymax
'xxxx.jpg\n'
# image path
)
然后执行命令生成.rec文件:
python /home/username/mxnet/tools/im2rec.py --num-thread=4 --pack-label=1 my_test.lst test_img
多了一个--pack-label=1
参数。
使用ImageRecordIter:
data_iter = mx.image.ImageRecordIter(batch_size=4,
resize=30,
label_width=5,
data_shape=(3, 30, 60),# depth,height,width
path_imgrec="./xxx/my_test.rec",
path_imgidx="./xxx/my_test.idx" )
data_iter.reset()
batch = data_iter.next()
img, labels = batch.data[0], batch.label[0]
print(labels)
多了一个label_width
配置。
相比ImageRecordIter,ImageIter既能读原图数据,也能读rec数据。在ImageRecordIter中对于数据的预处理操作都是固定的,不好修改,但ImageIter却可以非常灵活地添加各种预处理操作。
data_iter = mx.image.ImageIter(
batch_size=4,
data_shape=(3, 30, 60),
label_width=5,
path_imglist="./xxx/my_test.lst",
path_root='/xxx',
shuffle=True
)
用ImageIter读取原图与ImageRecordIter读取rec比较:
因此当数据修改频繁时,可使用ImageIter测试效果。确定好数据集后,生成一次rec文件可以大大提高训练速度。
使用rec文件训练的模型在测试中表现很差
训练过程中,训练集和验证集都得到了比较高的准确率。训练好模型之后,输入图片进行测试,结果比验证集都要差很多。即使是用验证集的数据,效果也很差。为什么同样的图片会一个准一个不准呢?问题只可能出在图片的加载上。在测试时,使用的是opencv进行读取。debug图片数据,发现和rec中读取的图片有细微差别。后来找到原因是opencv读取图片是BGR模式,而MXNet从rec中读取的是RGB,导致这个问题。
解决方法是调整opencv的图片格式:
img = cv2.imread(imgpath)
b, g, r = cv2.split(img)
img = cv2.merge([r, g, b])
参考:
https://github.com/leocvml/mxnet-im2rec_tutorial