对图像进行预处理,可以尽量避免模型受到无关因素的影响。大部分图像识别问题中,通过图像预处理过程可以提高模型的准确率。
图像编码处理
一张RGB彩色模型的图像可以看成一个三维矩阵,矩阵中的每一个数表示了图像上不同的位置,不同颜色的亮度。然而图像在存储时并不是直接记录这些矩阵中的数字,而是记录经过压缩编码之后的结果。所以要将一张图片还原成一个三维矩阵,需要解码过程。tensorflow提供了jpeg和png格式图像的编码/解码的函数。以下代码示范了如何使用tensorflow中对jpeg格式图像进行编码/解码。
# matplotlib.pyplot是一个python的画图工具。下面的代码将使用这个工具
# 来可视化经过tensorflow处理的图像。
import matplotlib.pyplot as plt
import tensorflow as tf
# 读取图像的原始数据
image_raw_data = tf.gfile.FastGFile("/path/to/picture", 'r').read()
with tf.Session() as sess:
# 对图像进行jpeg的格式解码从而得到图像对应的三维矩阵。tensorflow还提供了
# tf.image.decode_png 函数对png格式的图像进行解码。解码之后的结果为一个
# 张量,在使用它的取值之前需要明确调用运行的过程。
img_data = tf.image.decode_jpeg(image_raw_data)
print img_data.eval()
# 输出解码之后的三维矩阵,上面这一行代码将输出以下内容。
'''
[[[165 160 138]
...,
[105 140 50]
[[166 161 139]
...,
[106 139 48]]
...,
[207 200 181]
...,
[106 81 50]]]
'''
# 使用pyplot工具可视化得到的图像。
plt.imshow(img_data.eval())
plt.show()
# 将表示一张图像的三维矩阵重新按照jpeg格式编码并存入文件中。打开这张图片,
# 可以得到和原始图像一样的图像。
encoded_image = tf.image.encode_jpeg(img_data)
with tf.gfile.GFile("/path/to/output", "wb") as f:
f.write(encode_image.eval())
图像大小调整
一般来说,网络上获取的图像大小是不固定,但神经网络输入节点的个数是固定的,所以在将图像的像素作为输入提供给神经网路之前,需要先将图像的大小统一。这就是图像大小调整需要完成的任务。图像大小调整有两种方式,第一种是通过算法使得新的图像尽量保存原始图像上的所有信息。tensorflow提供了4种不同的方法,并且将它们封装到了tf.image.resize_images函数。以下代码示范了如何使用这个函数。
# 加载原始图像,定义会话等过程和图像编码处理中代码一致,
# 假设img_data是已经解码的图像。
...
# 首先将图片数据转化为实数类型。这一步将0-255的像素值转化为 0.0-1.0 范围内的实数,
# 大多数图像处理API支持整数和实数类型的输入,如果输入是整数类型,这些API会
# 在内部将输入转化为实数后处理,再将输出转化为整数。如果有多个处理步骤,在整数和
# 实数之间的反复转化将导致精度损失,因此推进在图像处理前将其转化为实数类型。
# 下面的样例子将略去这一步骤,假设img_data是经过类型转化的图像
img_data = tf.image.convert_image_dtype(img_data, dtype=tf.float32)
# 通过tf.image.resize_images函数调整图像的大小。这个函数第一个参数为原始图像,
# 第二个和第三个参数为调整后图像的大小,method参数给出了调整图像大小的算法。
# 注意,如果输入数据时unit8格式,那么输出将是0-255内的实数,不方便后续处理。
resized = tf.image.resize_images(img_data, [300, 300], method=0)
# 通过pyplot可视化过程和图像编码处理中给出的代码一致
下表给出了tf.image.resize_images函数的method参数取值对应的图像大小调整算法。
Method取值 | 图像大小调整算法 |
---|---|
0 | 双线性插值(Bilinear interpolation) |
1 | 最邻近发(Nearest nighbor interpolation) |
2 | 双三次插值(Bicubic interpolation) |
3 | 面积插值法(Area interpolation) |
不同算法调整出来的结果会有细微差别,但不会相差太远。除了将整张图像信息完整保存,tensorflow还提供了API对图像进行剪裁或者填充。以下代码展示了通过tf.image.resize_image_with_crop_or_pad函数来调整图像大小的功能。
# 通过tf.image.resize_image_with_crop_or_pad函数调整图像的大小。这个函数的
# 第一个参数为原始图像,后面两个参数是调整后的目标图像大小。如果原始图像的尺寸大于目标
# 图像,那么这个函数会自动截取原始图像中的部分。如果目标图像
# 大于原始图像,这个函数会自动在原始图像的四周填充全0背景。因为原
# 始图像的大小为1797*2673,所以下面的第一个命令会自动剪裁,而第二个命令会自动填充。
croped = tf.image.resize_image_crop_or_pad(img_data, 1000, 1000)
padded = tf.image.resize_image_crop_or_pad(img_data, 3000, 3000)
tensorflow还支持通过比例调整图像大小,以下代码给出了一个样例。
# 通过tf.image.central_crop函数可以按比例剪裁图像。这个函数的第一个参数为原始图
# 像,第二个为调整比例,这个比例需要时一个(0,1]的实数。
central_cropped = tf.image.central_crop(img_data, 0.5)
上面介绍的图像剪切函数都是截取或者填充图像中间的部分。tensorflow也提供了tf.image.crop_to_bounding_box函数和tf.image.pad_to_bounding_box函数来剪切或者填充给定区域的图像。这两个函数都要求给出的尺寸满足一定的要求,否则程序会报错。比如在使用tf.image.crop_to_bounding_box函数时,tensorflow要求提供的图像尺寸要大于目标尺寸,也就是要求原始图像能够剪切目标图像的大小。
图像翻转
tensorflow提供了一些函数来支持对图像的翻转。以下代码实现了将图像上下翻转、左右翻转以及沿对角线翻转的功能。
# 将图像上下翻转
flipped = tf.image.flip_up_down(img_data)
# 将图像左右翻转
flipped = tf.image.flip_left_right(img_data)
# 将图像沿对角线
transposed = tf.image.transpose_image(img_data)
在很多图像识别问题中,图像的翻转不应该影响识别的结果。于是在训练图像识别的神经网络模型时,可以随机地翻转训练图像,这样训练得到的模型可以识别不同角度的实体。比如假设在训练模型中所有的猫头都是向右的,那么训练出来的模型就无法很好地识别猫头向左的猫。虽然这个问题可以通过收集更多的训练数据来解决,但是通过随机翻转识别训练图像的方式可以在零成本的情况下很大程度地缓解该问题。所以随机翻转训练图像时一种很常用的图像预处理方式。tensorflow提供了方便的API完成随机图像翻转的过程。
# 以50%概率上下翻转
flipped = tf.image.random_flip_up_down(img_data)
# 以50%概率左右翻转图像
flipped = tf.image.random_flip_left_right(img_data)
图像色彩调整
和图像翻转类似,调整图像的亮度、对比度、饱和度和色相在很多图像识别应用中都不会影响识别的结果。所以在训练神经网络模型时,可以随机调整训练图像的这些属性,从而使得到的模型尽可能小地受到无关因素的影响。tensorflow提供了调整这些色彩相关属性的API。以下代码显示了如何修改图像的亮度。
# 将图像的亮度-0.5
adjusted = tf.image.adjust_brightness(img_data, -0.5)
# 色彩调整的API可能导致像素的实数超出0.0-1.0的范围,因此在输出最终图像前需要
# 将其值截断在0.0-1.0范围区间,否则不仅图像无法正常可视化,以此为输入的神经网络
# 的训练质量也可能受到影响。
# 如果对图像进行多项处理操作,那么这一截断过程应在所有处理完成后进行。举例而言,
# 假如对图像进行多项处理操作,那么这一截断过程应当在所有处理完成后进行。举例而言。
# 假如对图像一次提高亮度和减少对比度,那么第二个操作可能将第一个操作生成的部分
# 过亮的像素回到不超过1.0的范围内,因此在第一个操作后不应该立即截断。
# 下面的样例假设截断操作在最终可视化图像前进行。
adjusted = tf.clip_by_value(adjusted, 0.0, 1.0)
# 将图像的亮度+0.5
adjuested = tf.image.adjusted_brightness(img_data, 0.5)
# 在(-max_delta, max_delta)的范围随机调整图像的亮度。
adjusted = tf.image.random_brightness(image, max_delta)
以下代码显示了如何调整图像的对比度。
# 将图像的对比度减少到0.5倍
adjusted = tf.image.adjust_contrast(img_data, 0.5)
# 将图像的对比度增加5倍
adjusted = tf.image.adjust_contrast(img_data, 5)
# 在[lower, upper]的范围随机调整图的对比度
adjusted = tf.image.random_contrast(image, lower, upper)
以下代码显示了如何调整图像的色相。
# 下面4条命令分别将色相加0.1, 0.3, 0.6和0.9
adjusted = tf.image.adjust_hue(img_data, 0.1)
adjusted = tf.image.adjust_hue(img_data, 0.3)
adjusted = tf.image.adjust_hue(img_data, 0.6)
adjusted = tf.image.adjust_hue(img_data, 0.9)
# 在[-max_delta, max_delta]的范围内随机调整图像的色相。max_delta的取值在[0, 0.5]之间
adjusted = tf.image.random_hue(image, max_delta)
以下代码显示了如何调整图像的饱和度。
# 将图像的饱和度-5
adjusted = tf.image.adjust_saturation(img_data, -5)
# 将图像的饱和度+5
adjusted = tf.image.adjust_saturation(img_data, +5)
# 在[lower, upper]的范围内随机调整图像的饱和度
adjusted = tf.images.random_satutation(image, lower, upper)
除了调整图像的亮度、对比度、饱和度和色相,tensorflow还提供API来完成图像标准化的操作。这个操作就是将图像上的亮度均值变为0,方差变为1.以下代码实现了这个功能。
# 将代表一张图像的三维矩阵中的数字均值变为0,方差变为1
adjusted = tf.image.pet_standerdization(img_data)
处理框标准
在很多图像识别任务的数据集中,图像中需要关注的物体通常会被标注框圈出来。tensorflow提供了一些工具来处理标注框。以下代码展示了如何通过tf.image.draw_bounding_boxes函数加入标注框。
# 将图像缩小一些,这样可视化能让标注框更加清楚。
img_data = tf.image.resize_images(img_data, [180, 26], method=1)
# tf.image.draw_bounding_boxes函数要求图像矩阵中的数字为实数,所以需要先将
# 图像转化为实数类型。tf.images.draw_bounding_boxes函数图像的输入是一个
# batch的数据,也就是多张图像组成的四维矩阵,所以需要将解码之后的图像矩阵加一维。
batched = tf.expand_dims(
tf.images.convert_image_dtype(img_data, tf.float32), 0)
# 给出每一张图像的所有标注框。一个标注框有4个数字,分别代表[ymin, xmin, ymax, xmax]
# 注意这里给出的数字都是图像的相对位置。比如在180*267的图像中,
# [0.35, 0.47, 0.5, 0.56]代表了从(63, 125)到(90, 150)的图像。
boxes = tf.constant([[[0.05, 0.05, 0.9, 0.7], [0.35, 0.47, 0.5, 0.56]]])
result = tf.image.draw_bounding_boxes(batched, boxes)
和随机翻转图像、随机调整颜色类似,随机截取图像上有信息含量的部分也是一个提高模型健壮性(robustness)的一种方式。这样可以使训练得到的模型不受被识别物体大小的影响。以下程序中展示了如何通过tf.image.sample_distored_bounding_box函数来完成截取图像的过程。
boxes = tf.constant([[[0.05, 0.05, 0.9, 0.7],[0.35, 0.47, 0.5, 0.56]]])
# 可以通过提供标注框的方式来告诉随机截取图像的算法哪些部分是“有信息量”的
# min_object_covered=0.4 表示截取部分至少包含某个标注框40%的内容。
begin, size, bbox_for_draw = tf.image.sample_distorted_bounding_box(
tf.shape(img_data), bounding_boxes=boxes,
min_object_covered=0.4)
# 通过标注框可视化随机截取得到的图像。
batched = tf.expand_dims(
tf.images.covert_image_dtype(img_data, tf.float32), 0)
image_with_box = tf.image.draw_bounding_boxes(batched, bbox_for_draw)
# 截取随机出来的图像。因为算法带有随机成分,所以
# 每次得到的结果会有所不同
distorted_image = tf.slice(img_data, begin, size)
在解决真实的图像识别问题时,一般同时使用多种处理方法。这节将给出一个完整的样例程序展示如何将不同的图像处理函数结合成一个完成了从图像片段截取,到图像大小调整再到图像翻转及色彩调整的整个图像预处理过程。
import tensorflow as tf
import numpy as np
import matplotlib as plt
# 给定一张图像,随机调整图像的色彩。因为调整亮度、对比度、饱和度和色相的顺序会影
# 响最后得到的结果,所以可以定义多种不同的顺序。具体使用哪一种顺序可以在训练
# 数据预处理时随机地选择一种。这样可以进一步降低无关因素对模型的影响。
def distort_color(image, color_ordering=0):
if color_ordering == 0:
image = tf.image.random_brightness(image, max_delta=32. / 255.)
image = tf.image.random_saturation(image, lower=0.5, upper=1.5)
image = tf.image.random_hue(image, max_delta=0.2)
image = tf.iamge.random_contrast(image, lower=0.5, upper=1.5)
elif color_ordering == 1:
image = tf.image.random_saturation(image, lower=0.5, upper=1.5)
image = tf.image.random_brightness(image, max_delta = 32. / 255.)
image = tf.iamge.random_contrast(image, lower=0.5, upper=1.5)
image = tf.image.random_hue(image, max_delta=0.2)
elif color_ordering == 2:
# 还可以定义其他的排列,但在这里就不再一一列出。
...
return tf.clip_by_value(image, 0.0, 1.0)
# 给定一张解码后的图像、目标图像的尺寸以及图像上的标注框,此函数可以对给出的图像进行预
# 处理。这个函数的输入图像时图像识别问题中原始的训练图像,而输出则是神经网络模型的输入
# 层。注意这里只处理模型的训练数据,对于预测的数据,一般不需要随机变换的步骤。
def preprocess_for_train(image, height, width, bbox):
# 如果没有提供标注框,则认为整个图像就是需要关注的部分。
if bbox is None:
bbox = tf.constant([0.0, 0.0, 1.0, 1.0], dtype=tf.float32, shape=[1, 1, 4])
# 转换图像张量的类型。
if image.dtype != tf.float32:
image = tf.image.convert_image_dtype(image, dtype=tf.flaot32)
# 随机截取图像,减少需要关注的物体大小对图像识别算法的影响。
bbox_begin. bbox_size, _ = tf.image.sample_distorted_bounding_box(
tf.shape(image), bounding_boxes=bbox)
distorted_image = tf.slice(image, bbox_begin, bbox_size)
# 将随机截取的图像调整为神经网络输入层的大小。大小调整的算法是随机选择的。
distorted_image = tf.image.resize_image(
distorted_images, [height, width], method = np.random.randint(2))
# 随机左右翻转图像。
distorted_image = tf.image.random_flip_left_right(distorted_image)
# 使用一种随机的顺序调整图像色彩。
distorted_image = distort_color(distorted_image, np.random.randint(2))
return distorted_image
image_raw_data = tf.gfile.FastGFile("/path/to/picture","r").read()
with tf.Session( ) as sess:
img_data = tf.image.decode_jpeg(image_raw_data)
boxes = tf.constant([[[0.05, 0.05, 0.9, 0.7],[0.35, 0.47, 0.5, 0.56]]])
# 运行6次获得6种不同的图像
for i in range(6):
# 将图像的尺寸调整为299*299
result = preprocess_for_train(img_data, 299, 299, boxes)
plt.imshow(result.eval())
plt.show()
参考文献:《Tensorflow:实战Google深度学习框架》,电子工业出版社,2017年