keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

时间:2024-05-23 10:58:31

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

前言:前面已经详细介绍了keras整个图像预处理模块的详细流程,参见下面两篇文章:

keras的图像预处理全攻略(二)—— ImageDataGenerator 类

keras的图像预处理全攻略(三)—— ImageDataGenerator 类的辅助类

本文结合实际案例来说说明到底该怎么应用 ImageDataGenerator 类以及需要注意的一些事项

一、图像分类问题中ImageDataGenerator 类的flow方法的应用

二、图像分类问题中ImageDataGenerator 类的flow_from_directory方法应用

三、对于分割数据集使用ImageDataGenerator类

一、图像分类问题中ImageDataGenerator 类的flow方法的应用

我们经常遇见一些分类问题,往往由于数据量较小的原因,导致模型很容易过拟合,所以需要进行数据增强,本文以最简单的mnist手写字为例子来加以说明ImageDataGenerator 类的简单应用。再说明之前,我们学要结合系列文章的第二篇的相关知识点,ImageDataGenerator 类的使用分为“四步走”策略。

1.1 第一步:数据集的划分,得到x_train,y_train,x_test,y_test;

from keras.preprocessing.image import ImageDataGenerator

from keras.datasets import mnist
from keras.datasets import cifar10
from keras.utils import np_utils

import numpy as np
import matplotlib.pyplot as plt


# 第一步:划分数据集。得到x_train,y_train,x_test,y_test;
num_classes = 10
(x_train, y_train), (x_test, y_test) = mnist.load_data()

x_train = np.expand_dims(x_train, axis = 3)
y_train = np_utils.to_categorical(y_train, num_classes)
y_test = np_utils.to_categorical(y_test, num_classes)

print(np.shape(x_train))  #返回(60000, 28, 28, 1)
print(np.shape(x_test))   #返回(10000, 28, 28),因为这里没有对测试数据进行维度扩充,这里不需要使用到测试数据,所以没管它
print(np.shape(y_train))  #返回(60000, 10)
print(np.shape(y_test))   #返回(10000, 10)
print("======================================================")

注意:由于mnist.npz会默认没有的,需要下载,但是国内下载实在是太慢,所以我是已经下载好了并且保存在了用户文件夹之下的.keras/datasets里面了。所以不需要下载的,网上有很多的下载资源,也可以找我要数据,大改10M。

1.2 第二步:构造ImageDataGenerator对象

其中要进行某一些操作是通过在构造函数中的参数指定的,datagen = ImageDataGenerator(......)

#第二步骤:构造ImageDataGenerator类的对象,通过参数指定要进行处理项目
#对数据进行预处理,注意这里不是一次性要将所有的数据进行处理完,而是在后面的代码中进行逐批次处理
datagen = ImageDataGenerator(
     featurewise_center=True,
     featurewise_std_normalization=True,
     rotation_range=20,
     width_shift_range=0.2,
     height_shift_range=0.2,
     horizontal_flip=True)

1.3 第三步:对样本数据进行data augmentation处理,通过fit方法

#第三步:对需要处理的数据进行fit
datagen.fit(x_train)

1.4 产生迭代对象,通过flow方法,并且查看每一次迭代对象的信息

#第四步:使用.flow方法构造Iterator, 
data_iter = datagen.flow(x_train, y_train, batch_size=8,save_to_dir="save_data")  #返回的是一个“生成器对象”
print(type(data_iter))    #返回的是一个NumpyArrayIterator对象

 
# 通过循环迭代每一次的数据,并进行查看
for x_batch,y_batch in data_iter:
    for i in range(8):
        plt.subplot(2,4,i+1)
        plt.imshow(x_batch[i].reshape(28,28), cmap='gray')
    plt.show()

这里是核心点,有几个需要注意的地方:

(1)迭代器的数据格式

我们通过ImageDataGenerator类来对训练数据做处理,目的是产生一个迭代器,然后将每一次迭代产生的数据放进model.fit_generator()里面进行训练,而迭代器的数据格式如下:

  • 一个 (inputs, targets) 元组
  • 一个 (inputs, targets, sample_weights) 元组。

(2)flow函数save_to_dir参数

这个参数会将没一个批次所进行data augmentation之后的图像保存在指定的文件夹,它的作用是方便对于处理之后的图像进行查看。比如我上面保存到了当前文件之下的save_data文件夹里面。

(3)迭代器可以通过for进行循环我也可以直接通过next()方法来进行迭代,上面的第四步等价与下面的代码:

while True:
    x_batch, y_batch = data_iter.next()
    for i in range(8):
        plt.subplot(2,4,i+1)
        plt.imshow(x_batch[i].reshape(28,28), cmap='gray')
    plt.show()

(4)flow是无限迭代的。也就是说每一次迭代8组样本,等一个轮回迭代完之后依然会继续重新迭代,不会自己停止。

记住:flow()、flow_from_dataframe()、flow_from_directory()都是无限迭代的。

但是这个地方一直有一个问题,我还没找到答案,望大神告知!

如果总的样本数samples能够被batch_size整除,那么恰好可以每一次都恰好对batch_size个样本进行处理,这样当所有的样本都处理完之后,又会进入下一次循环迭代,即迭代不会终止;但是如果样本数samples不能够被batch_size整除,那么最后一个batch_size会少几个样本,这样在所有的样本数迭代完成一次之后就不再循环迭代了,也就是所有的样本只迭代一次,这倒是怎么回事,我还不太清楚,我是在实验中尝试得出的结果,网友大神告知,谢谢!

1.5 运行结果展示

由于图片很多,每一次仅仅只处理8张图片,所以每次要查看很多次才能将所有的样本数据完全看完,本文在运行了三次迭代器之后就停止了程序的运行,得到的运行结果,和保存的数据展示如下:

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

这是三次迭代,每一次迭代产生的8张图片,我自己保存的sav_data文件夹之下的24张图片如下:

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

二、图像分类问题中ImageDataGenerator 类的flow_from_directory方法应用

为了演示flow_from_dirctory()方法的效果,本文没有使用其它的训练数据集,自己随便找几张图片做实验就可以了。

本试验依然是以分类问题为背景,自己选择了15张约翰逊斯嘉丽的图片作为训练数据集,15张图片分为三类,分别是happy、smile、silent,另外下载了5张图片作为测试集,类别是一样的这三类,文件夹的组织结构如下所示:

├─Scarlett_Johansson_test
│  ├─happy
│  │      2001.jpg
│  │
│  ├─silent
│  │      2002.jpg
│  │      2003.jpg
│  │
│  └─smile
│          2004.jpg
│          2005.jpg

└─Scarlett_Johansson_train
    ├─happy
    │      1006.jpg
    │      1007.jpg
    │      1008.jpg
    │      1009.jpg
    │      1010.jpg
    │
    ├─silent
    │      1011.jpg
    │      1012.jpg
    │      1013.jpg
    │      1014.jpg
    │      1015.jpg
    │
    └─smile
            1001.jpg
            1002.jpg
            1003.jpg
            1004.jpg
            1005.jpg

即Scarlett_Johansson_data数据文件夹之下分为Scarlett_Johansson_test和Scarlett_Johansson_train两个文件夹,这两个文件夹之下没一个里面都包含smile、happy、silent三个类别的子文件夹,每一个字文件夹之下都保存着相应的样本数据。

下面开始写代码:

2.1 第一步:构建ImageDataGenerator类的对象

from keras.preprocessing.image import ImageDataGenerator
import numpy as np
import matplotlib.pyplot as plt

#第一步:对测试数据和训练数据分别构造一个ImageDataGenerator对象
train_datagen = ImageDataGenerator(
        rescale=1./255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

test_datagen = ImageDataGenerator(rescale=1./255)

2.2 第二步:调用flow_from_directory方法


#第二步:分别对训练数据和测试数据使用flow_from_directory方法
train_generator = train_datagen.flow_from_directory(
        'Scarlett_Johansson_data/Scarlett_Johansson_train',
        target_size=(150, 150),    #将图片大小转化为150x150
        batch_size=5,              #每一次处理6张图片
        class_mode='categorical',  #对类别进行独热编码
        save_to_dir="Scarlett_Johansson_data_augmentation", #将augmentation之后的图片保存
        save_prefix="sjl_")        #将augmentation之后的图片添加sjl_前缀

validation_generator = test_datagen.flow_from_directory(
        'Scarlett_Johansson_data/Scarlett_Johansson_test',
        target_size=(150, 150),
        batch_size=2,    
        class_mode='binary')

print('##############################################################')

注意:本例子中一共有三个类别,及三个不同的文件夹smile、happy、silent,程序会自动判别一共有3各类型,这里有一个很重要的参数class_mode

如果class_mode==“binary”,则输出的类别是0,1,2,来分别表示三个类,

如果是class_mode==“'categorical”,则这三个类别会用独热编码的方式来表示,即【0,0,1】、【0,1,0】、【1,0,0】.

2.3 第三步:迭代训练数据

这里只对训练数据train_generator进行迭代,由于一共只有15组训练样本,所以每一次迭代5组样本,恰好可以除尽,这样可以循环迭代而不终止。本文选择迭代9次,即每个样本共产生3个数据增强的样本,共产生15*3=45组样本。代码如下:


#第三步:迭代查看,这里只查看训练数据集的,测试数据集的就不管了
count=1
for x_batch,y_batch in train_generator:
    print(F"------------开始第{count}次迭代-----------------------------")
    print(F"------------x_batch、y_batch的形状如下----------------------")
    print(np.shape(x_batch),np.shape(y_batch))
    print('-------------y_batch打印结果如下-----------------------------')
    print(y_batch)
    print('============================================================')

    #将每次augmentation之后的5幅图像显示出来
    for i in range(5):
        plt.subplot(1,5,i+1)
        plt.imshow(x_batch[i].reshape(150,150,3))
        plt.savefig("Scarlett_Johansson_data_iter_time/iter_time_"+str(count)+".png",format="png")
    plt.show()
    count+=1

程序运行的结果如下:

'''
Using TensorFlow backend.
Found 15 images belonging to 3 classes. #这是调用train_datagen.flow_from_directory()后打印的
Found 5 images belonging to 3 classes. ##这是调用test_datagen.flow_from_directory()后打印的
##############################################################
------------开始第1次迭代-----------------------------
------------x_batch、y_batch的形状如下----------------------
(5, 150, 150, 3) (5, 3)
-------------y_batch打印结果如下-----------------------------
[[0. 0. 1.]
 [0. 1. 0.]
 [0. 1. 0.]
 [0. 0. 1.]
 [0. 1. 0.]]
============================================================
.
.
.  中间的省略了,一共迭代了9次
.
.
------------开始第9次迭代-----------------------------
------------x_batch、y_batch的形状如下----------------------
(5, 150, 150, 3) (5, 3)
-------------y_batch打印结果如下-----------------------------
[[0. 1. 0.]
 [0. 0. 1.]
 [0. 1. 0.]
 [1. 0. 0.]
 [1. 0. 0.]]
============================================================
'''

2.4 程序运行的结果查看

(1)Scarlett_Johansson_data_iter_time文件夹——即每次迭代后使用plt预览的6张图片的文件夹

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

(2)Scarlett_Johansson_data_augmentation文件夹——即迭代6次,共产生了9x5=45张增强图片的效果

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

从上面可以看出,对15张样本数据分别都进行了三次增强,每挨着的3张图片对比可以发现,图像是不一样的,有的经过了翻转即flip操作、有的经过了缩放即zoom操作、有的经过了错切即shear操作,这其实都是来源于ImageDataGenerator中构造函数参数的指定来实现的。

三、对于分割数据集使用ImageDataGenerator类

由于只是做实验,本次是自己在voc2012数据集中只选择了15张样本数据来进行试验,只选择了三种类型,分别是飞机airplane、人person、火车car(因为火车的单词train与训练的train相同,这里就暂时使用car代替一下),文件目录的组织方式同上面的案例是一样的,如下:

├─mask_data
│  └─train
│      ├─airplane
│      │      2007_000032.png
│      │      2007_000033.png
│      │      2007_000256.png
│      │      2007_001288.png
│      │      2007_001568.png
│      │
│      ├─car
│      │      2007_000042.png
│      │      2007_000123.png
│      │      2007_000629.png
│      │      2007_000636.png
│      │      2007_004951.png
│      │
│      └─person
│              2007_000129.png
│              2007_000170.png
│              2007_000323.png
│              2007_000346.png
│              2007_000999.png

└─source_data
    └─train
        ├─airplane
        │      2007_000032.jpg
        │      2007_000033.jpg
        │      2007_000256.jpg
        │      2007_001288.jpg
        │      2007_001568.jpg
        │
        ├─car
        │      2007_000042.jpg
        │      2007_000123.jpg
        │      2007_000629.jpg
        │      2007_000636.jpg
        │      2007_004951.jpg
        │
        └─person
                2007_000129.jpg
                2007_000170.jpg
                2007_000323.jpg
                2007_000346.jpg
                2007_000999.jpg

注意:为什么针对分割模型这里单独拿出来,主要是因为分割模型的特殊性,它不仅有原始图像,还有与原始图像相对应的的分割标签图像,这里称之为mask图形,我对原始图形使用了某一种变换,必须给相对应的mask图形使用相同的变换进行变换,这才能保证训练数据与mask之间的一一对应关系不变。具体的实现如下:

3.1 第一步:构造ImageDataGenerator类的对象,

由于是需要对image和mask构造相同的变换,所以这里通过一个字典来构造该类的参数,如下:

from keras.preprocessing.image import ImageDataGenerator
import numpy as np
import matplotlib.pyplot as plt

#第一步:对测试数据和训练数据分别构造一个ImageDataGenerator对象
#注意:由于原始训练数据和分割数据是绑定的,有对应关系,这里要创建两个相同参数的实例
data_gen_args = dict(featurewise_center=True,
                     featurewise_std_normalization=True,
                     rescale=1./255,
                     rotation_range=90,
                     width_shift_range=0.1,
                     height_shift_range=0.1,
                     zoom_range=0.2)
image_datagen = ImageDataGenerator(**data_gen_args) #构造训练数据image的对象
mask_datagen = ImageDataGenerator(**data_gen_args)  #构造分割数据mask的对象

3.2 第二步:同时对image和mask调用flow_from_directory方法

这个地方需要注意的是,由于flow_from_directory方法会从训练数据中随机选择一个批次的图片进行变换,为了保持原始图像与分割图像mask之间的一一对应关系,需要指定一个随机数种子seed。(备注:需要懂得到底什么是伪随机数,什么时随机数种子哦?

另外,由于这个地方只考虑如何将训练的image和分割的mask进行统一的变换,类别就先不做讨论了,所以设置

class_mode=None.

另外这里没有设置save_to_dir参数,就不保存变换之后的原始图像image和变换之后的mask图像了,后面预览一下即可

#第二步:分别对训练数据和测试数据使用flow_from_directory方法,由于伪随机性的特质,这里需要
#为原始数据和mask标注数据提供相同的种子
seed = 1

image_generator = image_datagen.flow_from_directory(
    'segment_data/source_data/train',
    batch_size=5,
    target_size=(150, 150),
    class_mode=None,  #这个地方将class_mode设置为None,即不返回类别标签
    seed=seed)

mask_generator = mask_datagen.flow_from_directory(
    'segment_data/mask_data/train',
    batch_size=5,
    target_size=(150, 150),
    class_mode=None, #这个地方将class_mode设置为None,即不返回类别标签
    seed=seed)

print('##############################################################')

3.3 第二步:同时对image和mask进行迭代

#第三步:迭代查看,这里只查看训练数据集的,测试数据集的就不管了
# 将生成器组合成一个产生图像和蒙版(mask)的生成器
train_generator = zip(image_generator, mask_generator)

count=1
#犹豫期前面没有返回类别标签,这里迭代器只返回原始图像数据和mask对应的数据
for x_batch_image,x_batch_mask in train_generator:
    print(F"------------开始第{count}次迭代-----------------------------")
    print(F"------------x_batch_image、x_batch_mask的形状如下----------------------")
    print(np.shape(x_batch_image),np.shape(x_batch_mask))
    print(x_batch_image[0])
    print('============================================================')

    #将每次augmentation之后的5幅图像显示出来
    for i in range(5):
        plt.subplot(2,5,i+1)
        plt.imshow(x_batch_image[i])  #由于本来就是(150,150,3)的格式,所以不需要再reshape了
        plt.subplot(2,5,5+i+1)
        plt.imshow(x_batch_mask[i])
        #plt.savefig("Scarlett_Johansson_data_iter_time/iter_time_"+str(count)+".png",format="png")
    plt.show()
    count+=1

这里进行五次迭代,程序运行的结果如下:

‘'''
Using TensorFlow backend.
Found 15 images belonging to 3 classes.
Found 15 images belonging to 3 classes.
##############################################################
D:\ProgramData\Anaconda3\envs\tensorflow1.10.0\lib\site-packages\keras_preprocessing\image\image_data_generator.py:699: UserWarning: This ImageDataGenerator specifies `featurewise_center`, but it hasn't been fit on any training data. Fit it first by calling `.fit(numpy_data)`.
  warnings.warn('This ImageDataGenerator specifies '
D:\ProgramData\Anaconda3\envs\tensorflow1.10.0\lib\site-packages\keras_preprocessing\image\image_data_generator.py:707: UserWarning: This ImageDataGenerator specifies `featurewise_std_normalization`, but it hasn't been fit on any training data. Fit it first by calling `.fit(numpy_data)`.
  warnings.warn('This ImageDataGenerator specifies '
------------开始第1次迭代-----------------------------
------------x_batch_image、x_batch_mask的形状如下----------------------
(5, 150, 150, 3) (5, 150, 150, 3)
============================================================
------------开始第2次迭代-----------------------------
------------x_batch_image、x_batch_mask的形状如下----------------------
(5, 150, 150, 3) (5, 150, 150, 3)
============================================================
------------开始第3次迭代-----------------------------
------------x_batch_image、x_batch_mask的形状如下----------------------
(5, 150, 150, 3) (5, 150, 150, 3)
============================================================
------------开始第4次迭代-----------------------------
------------x_batch_image、x_batch_mask的形状如下----------------------
(5, 150, 150, 3) (5, 150, 150, 3)
============================================================
------------开始第5次迭代-----------------------------
------------x_batch_image、x_batch_mask的形状如下----------------------
(5, 150, 150, 3) (5, 150, 150, 3)
============================================================
'''

注意:中间有一段警告信息,说由于我在构造ImageDataGenerator类的对象的时候,使用了

featurewise_center=True,

featurewise_std_normalization=True,

这两个参数,这就需要从这两个参数的意义说起了,可以参考我前面的系列文章(二),这里不再赘述,简单就是说这里需要使用到样本数据的统计信息,但是因为我这里没有原先的样本数据,所以就没有用,使用了上面的两个参数之后,第一步需要先使用fit()函数在样本数据上进行“拟合”(引号,明白意思就行),根据统计信息进行计算,所以这里会进行警告。

3.4 变化结果展示

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

keras的图像预处理全攻略(四)—— ImageDataGenerator 类实践案例

从上面的结果可以看出,原始图像image和分割标注图像mask的变化是同步进行的,他们的对应关系没有被打乱。