5.4 图像与 TensorFlow
TensorFlow在设计时,就考虑了给将图像作为神经网络的输入提供支持。TensorFlow支持加载常见的图像格式(JPG、PNG),可在不同的颜色空间(RGB、RGBA)中工作,并能够完成常见的图像操作任务。虽然TensorFlow使得图像操作变得容易,但仍然面临一些挑战。使用图像时,所面临的最大挑战便是最终需要加载的张量的尺寸。每幅图像都需要用一个与图像尺寸(height*width*channel)相同的张量表示。再次提醒,通道是用一个包含每个通道中颜色数量的标量的秩1张量表示。
在TensorFlow中,一个红色的RGB像素可用如下张量表示:
每个标量都可修改,以使像素值为另一个颜色值或一些颜色值的混合。对于RGB颜色空间,像素对应的秩1张量的格式为[red,green,blue]。一幅图像中的所有像素都存储在磁盘文件中,它们都需要被加载到内存中,以便TensorFlow对其进行操作。
5.4.1 加载图像
TensorFlow在设计时便以能够从磁盘快速加载文件为目标。图像的加载与其他大型二进制文件的加载是相同的,只是图像的内容需要解码。加载下列3×3的JPG格式的示例图像的过程与加载任何其他类型的文件完全一致。
在上述代码中,假定该图像位于代码运行的当前目录的某个相对路径之下。输入生成器(tf.train.string_input_producer)会找到所需的文件,并将其加载到一个队列中。加载图像要求将完整的文件加载到内存中(tf.WholeFileReader)。一旦文件被读取(image_reader.read),所得到的图像就将被解码(tf.image.decode_jpeg)。
这样便可以查看这幅图像。由于按照名称只存在一个文件,所以队列将始终返回同一幅图像。
加载图像后,查看输出。注意,它是一个非常简单的三阶张量。RGB值对应9个一阶张量。通过前面章节的学习,应该对图像张量的高阶数已经比较熟悉了。被加载到内存中的图像格式为[batch_size,image_height,image_width,channels]。
本例中的batch_size为1,因为没有任何批运算发生。关于对输入分批的更多细节请参考TensorFlow文档[1]。在处理图像时,请注意加载原始图像所需的内存。如果一个批数据中的图像过大或过多,系统可能会停止响应。
5.4.2 图像格式
考虑图像的各个方面以及它们如何对模型造成影响非常重要。当使用来自一台RED Weapon摄像机的单帧图像训练一个网络时,考虑会发生什么。在笔者撰写本书时,这种摄像机的分辨率为6144×3160像素。这样的一帧图像需要用包含19415040个带有3个维度的颜色信息的一阶张量表示。
实际上,这种尺寸的输入将占用个大量系统内存。训练一个CNN需要大量时间,加载非常大的文件会进一步增加训练所需的时间。即便增加的时间在可接受的范围内,单幅图像的尺寸也很难存放在大多数系统的GPU显存中。
输入图像尺寸过大也会为大多数CNN模型的训练产生不利影响。CNN总是试图找到图像中的本征属性,虽然这些属性有一定的独特性,但也需要推广到其他具有类似结果的图像上。使用尺寸过大的输入会使网络中充斥大量无关信息,从而影响模型的泛化能力。
在Stanford Dogs数据集中,哈巴狗类别中存在两幅外观迥异的图像。虽然非常可爱,但这些图像中充斥了大量会在训练中对网络造成误导的无用信息。例如,文件n02110958_4030.jpg中的哈巴狗所戴的帽子不是CNN为匹配哈巴狗所需要学习的特征。大多数哈巴狗喜欢海盗帽,因此图像中有小丑帽实际上是在训练网络去匹配一个大多数哈巴狗都没戴着的帽子。
图像中的重要信息是通过按照某种恰当的文件格式存储并处理得以强调的。在使用图像时,不同的格式可用于解决不同的问题。
1.JPEG与PNG
TensorFlow拥有两种可对图像数据解码的格式,一种是tf.image.decode_jpeg,另一种是tf.image.decode_png。在计算机视觉应用中,这些都是常见的文件格式,因为将其他格式转换为这两种格式非常容易。
值得注意的是,JPEG图像不会存储任何alpha通道的信息,但PNG图像会。如果在训练模型时需要利用alpha信息(透明度),则这一点非常重要。一种应用场景是当用户手工切除图像的一些区域,如狗所戴的不相关的小丑帽。将这些区域置为黑色会使它们与该图像中的其他黑色区域看起来有相似的重要性。若将所移除的帽子对应的区域的alpha值设为0,则有助于标识该区域是被移除的区域。
使用JPEG图像时,不要进行过于频繁的操作,因为这样会留下一些伪影(artifact)。在进行任何必要的操作时,获取图像的原始数据,并将它们导出为JPEG文件。为了节省训练时间,请试着尽量在图像加载之前完成对它们的操作。
如果一些操作是必要的,PNG图像可以很好地工作。PNG格式采用的是无损压缩,因此它会保留原始文件(除非被缩放或降采样)中的全部信息。PNG格式的缺点在于文件体积相比JPEG要大一些。
2.TFRecord
为将二进制数据和标签(训练的类别标签)数据存储在同一个文件中,TensorFlow设计了一种内置文件格式,该格式被称为TFRecord,它要求在模型训练之前通过一个预处理步骤将图像转换为TFRecord格式。该格式的最大优点是将每幅输入图像和与之关联的标签放在同一文件中。
从技术角度讲,TFRecord文件是protobuf格式的文件。作为一种经过预处理的格式,它们是非常有用的。由于它们不对数据进行压缩,所以可被快速加载到内存中。在下面这个例子中,我们将一幅图像及其标签写入一个新的TFRecord格式的文件中。
标签的格式被称为独热编码(one-hot encoding),这是一种用于多类分类的有标签数据的常见表示方法。Stanford Dogs数据集之所以被视为多类分类数据,是因为狗会被分类为单一品种,而非多个品种的混合。在现实世界中,当预测狗的品种时,多标签解决方案通常较为有效,因为它们能够匹配同时属于多个品种的狗。
在这段示例代码中,图像被加载到内存中并被转换为字节数组。之后,这些字节被添加到tf.train.Example文件中,而后者在被保存到磁盘之前先通过SerializeToString序列化为二进制字符串。序列化是一种将内存对象转换为某种可安全传输到某个文件的格式。上面序列化的样本现在被保存为一种可被加载的格式,并可被反序列化为这里的样本格式。
由于图像被保存为TFRecord文件,所以可被再次加载(从TFRecord文件加载,而非从图像文件加载)。在训练阶段,加载图像及其标签是必需的。这样相比将图像及其标签分开加载会节省一些时间。
首先,按照与其他任何文件相同的方式加载该文件,主要差别在于之后该文件会由TFRecordReader对象读取。tf.parse_single_example并不对图像进行解码,而是解析TFRecord,然后图像会按原始字节(tf.decode_raw)被读取。
该文件被加载后,为使其布局符合tf.nn.conv2d的要求,即[image_height,image_width,image_channels],需要对形状进行调整(tf.reshape)。为将batch_size维添加到input_batch中,需要对维数进行扩展(tf.expand)。
在本例中,TFRecord文件中虽然只包含一个图像文件,但这类记录文件也支持被写入多个样本。将整个训练集保存在一个TFRecord文件中是安全的,但分开存储也完全可以。
当需要检查保存到磁盘的文件是否与从TensorFlow加载的图像是同一图像时,可使用下列代码:
可以看出,原始图像的所有属性都和从TFRecord文件加载的图像一致。为确认这一点,可从TFRecord文件加载标签,并检查它与之前保存的版本是否一致。
创建一个既可存储原始图像数据,也可存储其期望的输出标签的文件,能够降低训练中的复杂性。尽管使用TFRecord文件并非必需,但在使用图像数据时,却是强烈推荐的。如果对于某个工作流,它不能很好地工作,那么仍然建议在训练之前对图像进行预处理并将预处理结果保存下来。每次加载图像时才对其进行处理是不推荐的做法。
5.4.3 图像操作
当给定大量不同质量的训练数据时,CNN往往能够很好地工作。图像能够通过可视化的方式传达复杂场景所蕴涵的某种目标主题。在Stanford Dogs数据集中,很重要的一点是图像能够以可视化的方式突出图片中狗的重要性。一幅狗位于画面中心的图像会被认为比狗作为背景的图像更有价值。
并非所有数据集都拥有最有价值的图像。下面所示的两幅图像都来自Stanford Dogs数据集,按照假设,该数据集本应突出不同的狗的品种。
左图n02113978_3480.jpg突出的是一条典型的墨西哥无毛犬的重要属性,而右图n02113978_1030.jpg强调的是两个参加聚会的人在逗一条墨西哥无毛犬。右图中充斥了大量的无关信息,这可能会导致所训练的CNN模型对参加聚会的人的面部信息更为关注,而非墨西哥无毛犬。类似这样的图像中可能会包含狗,我们可对其进行操作,使狗而非人成为真正被突出的对象。
在大多数场景中,对图像的操作最好能在预处理阶段完成。预处理包括对图像裁剪、缩放以及灰度调整等。另一方面,在训练时对图像进行操作有一个重要的用例。当一幅图像被加载后,可对其做翻转或扭曲处理,以使输入给网络的训练信息多样化。虽然这个步骤会进一步增加处理时间,但却有助于缓解过拟合现象。
TensorFlow并未设计成一个图像处理框架。与TensorFlow相比,有一些Python库(如PIL和OpenCV)支持更丰富的图像操作。对于TensorFlow,可将那些对训练CNN十分有用的图像处理方法总结如下。
1.裁剪
裁剪会将图像中的某些区域移除,将其中的信息完全丢弃。裁剪与tf.slice类似,后者是将一个张量中的一部分从完整的张量中移除。当沿某个维度存在多余的输入时,为CNN对输入图像进行裁剪便是十分有用的。例如,为减少输入的尺寸,可对狗位于图像中心的图片进行裁剪。
执行上面的代码后,可得到输出:
这段示例代码利用了tf.image.central_crop将图像中10%的区域抠出,并将其返回。该方法总是会基于所使用的图像的中心返回结果。
裁剪通常在预处理阶段使用,但在训练阶段,若背景也有用时,它也可派上用场。当背景有用时,可随机化裁剪区域起始位置到图像中心的偏移量来实现裁剪。
执行上述代码,可得到输出:
为从位于(0,0)的图像的左上角像素开始对图像裁剪,这段示例代码使用了tf.image.crop_to_bounding_box。目前,该函数只能接收一个具有确定形状的张量。因此,输入图像需要事先在数据流图中运行。
2.边界填充
为使输入图像符合期望的尺寸,可用0进行边界填充。可利用tf.pad函数完成该操作,但对于尺寸过大或过小的图像,TensorFlow还提供了另外一个非常有用的尺寸调整方法。对于尺寸过小的图像,该方法会围绕该图像的边界填充一些灰度值为0的像素。通常,该方法用于调整小图像的尺寸,因为任何其他调整尺寸的方法都会使图像的内容产生扭曲。
这段示例代码将图像的高度和宽度都增加了一个像素,所增加的新像素的灰度值均为0。对于尺寸过小的图像,这种边界填充方式是非常有用的。如果训练集中的图像存在多种不同的长宽比,便需要这样的处理方法。对于那些长宽比不一致的图像,TensorFlow还提供了一种组合了pad和crop的尺寸调整的便捷方法。
real_image的高度被减小了两个像素,而通过边界填充0像素使宽度得以增加。这个函数的操作是相对图像输入的中心进行的。
3.翻转
翻转操作的含义与其字面意思一致,即每个像素的位置都沿水平或垂直方向翻转。从技术角度讲,翻转是在沿垂直方向翻转时所采用的术语。利用TensorFlow对图像执行翻转操作是非常有用的,这样可以为同一幅训练图像赋予不同的视角。例如,一幅左耳卷曲的澳大利亚牧羊犬图像如果经过了翻转,便有可能与其他的图像中右耳卷曲的狗匹配。
TensorFlow有一些函数可实现垂直翻转、水平翻转,用户可随意选择。随机翻转一幅图像的能力对于防止模型对图像的翻转版本产生过拟合非常有用。
上述代码执行后的输出如下:
这段示例代码对一幅图像的一个子集首先进行水平翻转,然后进行垂直翻转。该子集是用tf.slice选取的,这是因为对原始图像翻转返回的是相同的图像(仅对这个例子而言)。这个像素子集解释了当图像发生翻转时所发生的变化。tf.image.flip_left_right和tf.image.flip_up_down都可对张量进行操作,而非仅限于图像。这些函数对图像的翻转具有确定性,要想实现对图像随机翻转,可利用另一组函数。
这个例子与之前的例子具有相同的逻辑,唯一的区别在于本例中的输出是随机的。这个例程每次运行时,都会得到不同的输出。有一个名称为seed的参数可控制翻转发生的随机性。
4.饱和与平衡
可在互联网上找到的图像通常都事先经过了编辑。例如,Stanford Dogs数据集中的许多图像都具有过高的饱和度(大量颜色)。当将编辑过的图像用于训练时,可能会误导CNN模型去寻找那些与编辑过的图像有关的模式,而非图像本身所呈现的内容。
为向在图像数据上的训练提供帮助,TensorFlow实现了一些通过修改饱和度、色调、对比度和亮度的函数。利用这些函数可对这些图像属性进行简单的操作和随机修改。对训练而言,这种随机修改是非常有用的,原因与图像的随机翻转类似。对属性的随机修改能够使CNN精确匹配经过编辑的或不同光照条件下的图像的某种特征。
这个例子提升了一个以红色为主的像素的灰度值(增加了0.2)。不幸的是,在TensorFlow 0.9版本中,该方法尚不支持tf.uint8类型的输入[2]。因此,如果使用的是TensorFlow 0.9以下的版本,且在预处理环节需要对图像的灰度值进行调整时,请尽量避免使用tf.uint8类型。
这段示例代码将对比度调整了-0.5,这将生成一个识别度相当差的新图像。调节对比度时,最好选择一个较小的增量,以避免对图像造成“过曝”。这里的“过曝”的含义与神经元出现饱和类似,即达到了最大值而无法恢复。当对比度变化时,图像中的像素可能会呈现出全白和全黑的情形。
简而言之,tf.slice运算的目的是突出发生改变的像素。当运行该运算时,它是不需要的。
这段示例代码调整了图像中的色度,使其色彩更加丰富。该调整函数接收一个delta参数,用于控制需要调节的色度数量。
这段代码与调节对比度的那段代码非常类似。为识别边缘,对图像进行过饱和处理是很常见的,因为增加饱和度能够突出颜色的变化。
5.4.4 颜色
CNN通常使用具有单一颜色的图像来训练。当一幅图像只有单一颜色时,我们称它使用了灰度颜色空间,即单颜色通道。对大多数计算机视觉相关任务而言,使用灰度值是合理的,因为要了解图像的形状无须借助所有的颜色信息。缩减颜色空间可加速训练过程。为描述图像中的灰度,仅需一个单个分量的秩1张量即可,而无须像RGB图像那样使用含3个分量的秩1张量。
虽然只使用灰度信息有一些优点,但也必须考虑那些需要利用颜色的区分性的应用。在大多数计算机视觉任务中,如何使用图像中的颜色都颇具挑战性,因为很难从数学上定义两个RGB颜色之间的相似度。为在CNN训练中使用颜色,对图像进行颜色空间变换有时是非常有用的。
1.灰度
灰度图具有单个分量,且其取值范围与RGB图像中的颜色一样,也是[0,255]。
这个例子将RGB图像转换为灰度图。tf.slice运算提取了最上一行的像素,并查看其颜色是否发生了变化。这种灰度变换是通过将每个像素的所有颜色值取平均,并将其作为灰度值实现的。
2.HSV空间
色度、饱和度和灰度值构成了HSV颜色空间。与RGB空间类似,这个颜色空间也是用含3个分量的秩1张量表示的。HSV空间所度量的内容与RGB空间不同,它所度量的是图像的一些更为贴近人类感知的属性。有时HSV也被称为HSB,其中字母B表示亮度值。
3.RGB空间
到目前为止,所有的示例代码中使用的都是RGB颜色空间。它对应于一个含3个分量的秩1张量,其中红、绿和蓝的取值范围均为[0,255]。大多数图像本身就位于RGB颜色空间中,但考虑到有些图像可能会来自其他颜色空间,TensorFlow也提供了一些颜色空间转换的内置函数。
这段示例代码非常简单,只是从灰度空间转换到RGB空间并无太大的实际意义。RGB图像需要三种颜色,而灰度图像只需要一种颜色。当转换(灰度到RGB)发生时,RGB中每个像素的各通道都将被与灰度图中对应像素的灰度值填充。
4.LAB空间
TensorFlow并未为LAB颜色空间提供原生支持。它是一种有用的颜色空间,因为与RGB相比,它能够映射大量可感知的颜色。虽然TensorFlow并未为它提供原生支持,但它却是一种经常在专业场合使用的颜色空间。Python库python-colormath为LAB和其他本书未提及的颜色空间提供了转换支持。
使用LAB颜色空间最大的好处在于与RGB或HSV空间相比,它对颜色差异的映射更贴近人类的感知。在LAB颜色空间中,两个颜色的欧氏距离在某种程度上能够反映人类所感受到的这两种颜色的差异。
5.图像数据类型转换
在这些例子中,为说明如何修改图像的数据类型,tf.to_float被多次用到。对于某些例子,使用这种方式是可以的,但TensorFlow还提供了一个内置函数,用于当图像数据类型发生变化时恰当地对像素值进行比例变换。tf.image.convert_iamge_dtype(image,dtype,saturate=False)是将图像的数据类型从tf.uint8更改为tf.float的便捷方法。
[1] https://www.tensorflow.org/versions/master/how_tos/reading_data/index.html#batching
[2] 自TensorFlow 10.0起,该函数已支持tf.uint8类型的输入。——译者注
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论