图像增强的示例

数据收集可能很耗时、昂贵,而且说实话,很无聊。当我们收集数据的能力有限时,数据增强可以发挥重要作用。它可以帮助我们构建更强大的数据集,减少过度拟合并增加训练数据量。

我们将讨论数据增强及其好处。我们还将保持实用性。即通过介绍用于增强图像的 Python 代码。具体来说,我们将讨论如何进行这些增强:

  • 翻转图像

  • 调整亮度

  • 随机颜色抖动

  • 随机噪声(高斯噪声、椒盐噪声和删除噪声)

    最后,我们将讨论增强图像方面的最佳实践。具体来说,如何最好地验证我们的模型并在生产中对其进行测试。

  • 用例——自动驾驶汽车

    为了让事情变得有趣,我们将增强自动驾驶汽车的图像。你可以在图 1 中看到我们的意思。汽车前面有一个摄像头,模型使用这些图像进行预测。然后根据预测来引导汽车。

    图 1:使用摄像头传感器实现自动化的汽车

    目标是让汽车在赛道上行驶,同时保持在橙色线内。在此过程中,我们将讨论哪些增强功能对于此特定应用有意义。这是为了强调数据增强方面的选择需要一些批判性思考。

  • 收集可靠的数据集

    在讨论这个问题之前,我们有必要讨论一下数据收集。这是因为数据增强是收集稳健数据集的替代或补充方法。

  • 什么是稳健数据集?

    稳健的数据集可以反映模型预期执行的所有条件。这些条件由诸如照明条件、摄像头角度、房间颜色或背景中的物体等变量决定。使用这样的数据集进行训练将产生一个能够适应这些变量变化的模型。

    稳健的数据集=稳健的模型

    一个很好的例子来自我们在自动驾驶汽车方面的经验。我们收集数据,训练模型并部署它。它运行完美!直到我们打开了百叶窗…

    图 2:模型在不同条件下表现不佳

    阳光在赛道上反射,让模型“困惑”。在这种情况下,模型无法做出准确的预测。换句话说,模型对光照条件的变化不够稳健。

  • 如何收集可靠的数据集

    构建可靠的数据集始于良好的数据收集策略。你需要考虑所有会影响条件的变量。然后,你需要收集能够捕捉这些变量变化的数据。例如,对于不同的照明条件,我们可以:

  • 打开或关闭灯

  • 打开和关闭窗帘

  • 在一天中的不同时间收集数据

    其他变量是图像背景的不同方面。这包括墙壁和地毯的颜色以及背景中的不同物体。为了解释这些,我们可以:

  • 收集不同房间的数据

  • 将不同的物体移到背景中

    通过这些更改,我们正在向数据集添加噪音。希望模型能够学会忽略这些噪音,只使用轨迹进行预测。换句话说,我们希望模型使用真正的原因而不是关联。

  • 数据增强的好处

    数据增强是指我们使用代码系统地或随机地更改图像。这使我们能够人为地引入噪声并增加数据集的大小。实际上,其目的与数据收集相同,因此其好处也类似。

  • 建立强大的数据集

    我们常常受限于可以收集的数据量。在这种情况下,数据增强可以帮助提高数据集的稳健性。即使你已经收集了大量数据,增强也可以提供额外的稳健性。

    不过,要做到这一点,我们需要认真思考增强的类型。也就是说,它们应该模拟我们期望在现实世界中看到的条件。例如,稍后我们将看到如何调整图像的亮度以模拟不同的光照条件。

  • 减少对一组条件的过度拟合

    通过良好的增强,我们可以减少过度拟合。需要明确的是,这与对训练集的过度拟合不同。以图 3 为例。假设我们只在一个房间中收集数据。结果,模型将背景中的物体与左转预测联系起来。

    图 3:对房间内物体的过度拟合

    物体将出现在我们收集的所有图像中。这意味着它将出现在训练、验证甚至测试集中。该模型可以在所有这些集合上表现良好,但在生产中仍然表现不佳。例如,如果我们移除物体,它可能会变得“困惑”并且不会左转。换句话说,模型过度拟合了我们数据集中反映的条件。

    数据增强可以帮助解决这种类型的过度拟合。稍后,我们将看到删除像素如何有助于解决上述示例。也就是说,我们可以人为地从背景中移除物体。

  • 模型融合

    我们可以用多种不同的方式增强同一张图像。这可以人为地增加我们的数据集的大小。考虑到深度学习需要大量数据集,这可以帮助模型参数的收敛。

  • 使用 Python 增强数据

    好了,记住这些,让我们开始实际增强数据。我们将介绍代码,你也可以在 GitHub1 上找到该项目。

    首先,我们将使用下面的导入。我们有一些标准包(第 1-2 行)。Glob用于处理文件路径(第 6 行)。我们还有一些用于处理图像的包(第 10-13 行)。

    import numpy as np
    import matplotlib.pyplot as plt
    plt.style.use('default')
    
    import os
    import glob
    import string
    import random
    
    import torchvision.transforms as transforms
    import matplotlib.image as mpimg
    from PIL import Image, ImageEnhance
    import cv2
    

    如上所述,我们将增强用于驱动自动驾驶汽车的图像。你可以在数据集中找到这些示例。这些图像都是 224 x 224 像素。我们使用下面的代码显示其中一个。

    记下图像名称(第 3 行)。前两个数字是 224 x 224 框架内的 x 和 y 坐标。在图 4中,你可以看到我们使用绿色圆圈显示了这些坐标(第 11 行)。

    data = dp + 'JatRacer_Images/direction/'
    
    name = "32_50_c78164b4-40d2-11ed-a47b-a46bb6070c92.jpg" 
    
    #从名称获取 x,y 坐标
    x = int (name.split( "_" )[ 0 ]) 
    y = int (name.split( "_" )[ 1 ]) 
    
    #加载图像并为坐标添加圆圈
    img = mpimg.imread(read_path + name) 
    img = img.copy()
    cv2.circle(img, (x, y), 8 , ( 0 , 255 , 0 ), 3 ) 
    
    plt.imshow(img)
    

    图 4:示例图像

    这些坐标是目标变量。模型使用图像来尝试预测它们。然后使用这个预测来引导汽车。在本例中,你可以看到汽车即将左转。理想的方向是朝着绿色圆圈给出的坐标行驶。

  • 翻转图像

    假设我们收集了逆时针方向(即仅左转)的一组图像。如果我们想让汽车右转,就必须收集更多数据。或者,由于我们的轨道是对称的,我们可以在 x 轴上翻转图像。

    图5:翻转对称轨道

    我们使用 flip_img 函数来实现这一点。请记住,在水平轴上翻转时,x 坐标也需要调整。我们在第 9 行通过从 224(图像宽度)中减去当前坐标来实现这一点。你可以在图 6中看到此函数的结果。

    def flip_img(name, img):
      '''Invert image and target on x axis'''
    
      # flip image
      img = cv2.flip(img, 1)
    
      # flip target variable
      s = name.split('_')
      s[0] = str(224 - int(s[0]))
      name = '_'.join(s)
    
      return name, img
    

    图6:水平翻转

    即使你已经收集了两个方向的数据,翻转图像也是有意义的。这使我们能够将数据集的大小翻倍。但是垂直翻转呢?

    对于某些应用来说,这可能有意义。但对于我们的自动驾驶汽车来说……就没那么有意义了。请看图 7。垂直翻转意味着汽车将在天花板上行驶。除非我们在太空中行驶,否则这不是我们在生产中期望的情况。

    图7:垂直翻转

  • 调整亮度

    使用 adjust_brightness,我们可以使用 factor 参数来改变图像的亮度。如图 8 所示,如果我们增加因子(1.5),图像就会更亮。同样,如果因子小于1,图像就会变暗。

    def adjust_brightness(img, factor=1):
      '''
      Invert image on x axis
          factor: brightness level < 1 will decrease and > 1 will increase
      '''
    
      img = Image.fromarray(img)
    
      enhancer = ImageEnhance.Brightness(img)
      img = enhancer.enhance(factor)
    
      return img
    

    图8:调整亮度

    此功能可以帮助我们模拟不同的照明条件。我们可以发现,如果我们在数据收集期间打开和关闭灯光,我们可以得到类似的结果。

  • 颜色抖动

    我们可以使用抖动函数进一步实现这些类型的增强。这将随机改变图像的亮度、对比度、饱和度和色调。使用这些参数,我们可以定义这些方面可以变化的程度。你可以在图 9 中看到一些示例。这些是使用默认参数值创建的。

    def jitter(img, b=0.2, c=0.2, s=0.2, h=0.1):
      '''
      Randomly alter brightness, contrast, saturation, hue within given range
      Note: img should be a PIL image
      '''
    
      img = Image.fromarray(img)
    
      transform = transforms.ColorJitter(brightness=b, contrast=c, saturation=s, hue=h)
    
      # Apply transform
      img = transform(img)
    
      return img
    

    图 9:抖动增强

    同样,你需要考虑这些增强是否适合你的应用程序。你可以看到,默认情况下,我们将色调因子设置为 0.1(即 h=0.1)。如图 10 所示,较高的色调因子将返回具有不同颜色轨迹的图像。然而,在生产中,我们的轨迹将始终为橙色。

    图 10:色调 = 0.5 的抖动

    我们还应该考虑这些类型的转换的局限性。它们调整整个图像的颜色。实际上,照明条件更加复杂。阳光可以从不同的角度反射出赛道。赛道的某些部分可能比其他部分更暗。如果你真的想捕捉这种噪音,你必须通过良好的数据收集来实现。

  • 输入噪声

    一种不太系统的方法是随机引入噪声。你可以在图 11 中看到一些示例。在每种情况下,我们都可以调整引入的噪声量。

    图 11:噪音

    在进行这些增强时,请记住,我们的 224 x 224 图像中的每个像素都有 3 个通道 - R、G、B。每个通道可以取 0 到 255 之间的值。这些决定了像素的颜色。

    图 11 中的第一行是使用 gaussian_noise 函数创建的。我们创建一个与图像具有相同维度(224 x 224 x 3)的随机噪声数组(第 4-5 行)。此数组中的每个元素都将从均值为 0 且方差为给定值(var)的正态分布中采样。将其添加到图像中将以随机量调整 R、G、B 通道。

    def gaussian_noise(img, var=5):
      '''
      Add guassian noise to image
      '''
    
      dims = np.shape(img)
    
      noise = np.random.normal(0, var, size=dims).astype('uint8')
      img = img + noise
    
      return img
    

    sp_noise 函数的工作方式类似。只不过现在我们以给定的概率 ( prob ) 随机将像素更改为黑色或白色。你可以在图 11 的第二行中看到这一点。

    def sp_noise(img, prob=0.1):
      '''
      Add salt and pepper noise to image
      '''
      height, width, channels = np.shape(img)
      img = np.array(img)
    
      for i in range(height):
          for j in range(width):
              # randomly change pixel values
              if random.random() < prob:
                  if random.random() < 0.5:
                      img[i][j] = np.array([255, 255, 255]) # white
                  else:
                      img[i][j] = np.array([0, 0, 0]) # black
      img = Image.fromarray(img)
    
      return img
    

    高斯噪声和椒盐噪声会降低图像质量。在生产中,模型可能会使用不同质量的图像进行预测。这些增强功能可以帮助创建一个能够抵御这些变化的模型。

    delete_square 函数是添加噪声的另一种方法。它的工作原理是删除图像的大块。更具体地说,它将具有给定尺寸(像素)的随机正方形变成黑色。图 11 的最后一行给出了示例。

    def delete_square(img, pixels=20):
      '''
      Delete random square from image
      '''
    
      img = np.array(img)
      h, w, channels = np.shape(img)
    
      # Random starting pixel
      rh = random.randint(0, h)
      rw = random.randint(0, w)
    
      sub = round(pixels/2)
      add = pixels-sub
    
      # Boundries for square
      hmin = max(rh-sub, 0)
      hmax = min(rh+add, h-1)
      vmin = max(rw-sub, 0)
      vmax = min(rw+add, w-1)
    
      # Turn pixel within range black
      img[hmin:hmax,vmin:vmax] = np.array([0,0,0])
    
      img = Image.fromarray(img)
      return img
    

    删除还可以帮助构建更强大的模型。在进行预测时,模型可能会专注于特定特征。例如,我们的模型可能仅使用外部橙色车道。删除图像的部分内容将迫使模型使用多个特征。因此,如果其中一个特征出现问题,模型可能仍能做出准确的预测。

    尽管删除操作很耗时,但你可能希望采用更系统的方法。即排除图像的特定部分。你可以在图 12 中看到这一点。在这里,我们从背景中删除了椅子。这样模型就不会将其与右转联系起来。

    图12:系统性删除

  • 测量增强的效果

    我们已经了解了不同类型的增强。我们还了解了如何改变其中一些增强的级别。实际上,类型和级别可以视为超参数。在调整这些增强时,务必要记住一些事项。

  • 不要扩充测试集

    假设我们扩充整个数据集并进行训练和测试分割。这可能会导致对模型性能的估计过高。这是因为对训练集的过度拟合并不一定会导致测试集上的性能不佳。

    以我们在图 8 中看到的亮度增强为例。其中一些可能最终用于训练,而另一些则用于测试集。现在考虑其他增强也会发生同样的事情。你可以看到测试集与训练集非常相似。

    图8:调整亮度

    事实上,最好的做法是根本不要扩充测试集。这是因为测试集用于评估模型在生产中的性能。在这里,模型不应该对扩充的数据进行预测。

  • 并非所有条件都反映在测试集中

    同时,你需要考虑到你的测试集不够稳健。因此,良好的测试性能可能并不意味着在生产中也有良好的性能。这是因为你的模型可能会面临测试集中未捕获的条件。因此,要真正了解增强的影响,我们需要在生产中对其进行测试。

    这让我们陷入了困境。生产测试非常耗时,而且你无法在所有环境中进行测试。这意味着无法衡量增强的效果。最终,你需要认真思考哪些增强适合你的应用程序。领域知识和经验可能更能说明哪些增强有效。

希望这篇文章对你有所帮助!你还可以阅读我的其他文章,或者查看有关企业 AI 实战项目的教程,相信会让你拥有更多收获。

「AI秘籍」系列课程:

人工智能应用数学基础

人工智能Python基础

人工智能基础核心知识

人工智能BI核心知识

人工智能CV核心知识

AI企业项目实战课优惠二维码