使用图像数据进行特征工程

通过特征工程,我们立即想到的是表格数据。然而,我们也可以为图像数据获取特征。目标是提取图像的最重要方面。这样做将使我们更容易找到数据和目标变量之间的映射。

这意味着你可以使用更少的数据和更小的模型进行训练。较小的模型可以减少进行预测所需的时间。当部署在边缘设备上时,这是特别有用的。另一个好处是,你可以更确定你的模型用于进行预测的内容。

我们将使用Python通过一些图像特征工程的方法来演示这一点:

  • 裁剪
  • 灰度化
  • 选择RGB通道
  • 强度阈值
  • 边缘检测
  • 色彩滤镜(即提取给定颜色范围内的像素)

为了保持趣味性,我们将为自动汽车进行操作。如下所示,我们希望使用轨道的图像训练模型。然后,该模型将用于进行指导汽车的预测。最后,我们将讨论从图像数据中进行特征工程的局限性。

图片

特征工程与增强

在我们深入探讨之前,有必要讨论图像增强。这种方法与特征工程有着类似的目标。但是,它们通过不同的方式实现了这些目标。

什么是数据增强?

数据增强是指我们使用代码系统或随机地改变数据。对于图像来说,包括翻转、调整颜色和添加随机噪声等方法。这些方法使我们能够人为地引入噪声并增加数据集的大小。如果你想要更详细地了解图像增强,请参阅本文:

https://towardsdatascience.com/augmenting-images-for-deep-learning-3f1ea92a891c

在生产中,模型将需要在不同的条件下运行。这些条件是由变量确定的,如照明、摄像机的角度、房间的颜色或背景中的物体。

数据增强的目的是创建一个能够应对不同条件下变化的模型,它通过添加模拟真实世界条件的噪声来实现这一目标。例如,改变图像的亮度类似于在不同时间收集数据。

通过增加数据集的大小,数据增强也可以让我们训练更复杂的架构。换句话说,它有助于模型参数的收敛。

图像特征工程

图像特征工程的目标类似,我们想创建一个更加稳健的模型。但现在,我们需要删除不必要的噪声以进行准确的预测。换句话说,我们删除会随着不同条件而变化的变量。

通过提取图像的最重要的方面,我们也在简化问题。这使我们能够依赖于更简单的模型结构。这意味着我们可以使用更小的数据集来找到输入和目标之间的映射。

一个重要的区别在于如何在生产中处理这些方法。你的模型不会对增强后的图像进行预测。但是,在特征工程中,你的模型需要在它训练过的相同特征上进行预测。这意味着你必须能够在生产中进行特征工程。

Python图像特征工程

好的,考虑到这些,让我们开始特征工程。

我们将逐步讲解代码,你还可以在GitHub上找到该项目。

https://github.com/conorosully/medium-articles/blob/master/src/image_tools/image_features.ipynb

首先,我们使用以下导入。我们有一些标准包(2-3行)。Glob用于处理文件路径(第5行)。我们还有一些用于处理图像的包(第7-8行)。

#Imports 
import numpy as np
import matplotlib.pyplot as plt

import glob

from PIL import Image
import cv2

正如所提到的,我们将使用用于自动驾驶汽车的图像。你可以在Kaggle上找到这些的示例。

https://www.kaggle.com/datasets/conorsully1/jatracer-images?select=object_detection

我们使用以下代码加载其中一张图像。我们首先加载所有图像的文件路径(第2-3行)。然后加载(第8行)并显示(第9行)第一个路径的图像。你可以在图1中看到这张图像。

#Load image paths
read_path = "../../data/direction/"
img_paths = glob.glob(read_path + "*.jpg")

fig = plt.figure(figsize=(10,10))

#Display image
img = Image.open(img_paths[0])
plt.imshow(img) 
图片

裁剪

一个简单的方法是裁剪图像,以去除不需要的外部区域。目的是只删除对预测不必要的图像部分。对于我们的自动驾驶汽车,我们可以从背景中移除像素。

为了实现这一点,我们首先加载一张图像(第2行)。然后将此图像转换为数组(第5行)。该数组的尺寸将为224 x 224 x 3。

图像的高度和宽度为224像素,每个像素都有一个R G B通道。

要裁剪图像,我们仅在y轴上从第25个位置开始选择像素(第8行)。你可以在图2中看到结果。

#Load image
img = Image.open(img_paths[609])

#Covert to array
img = np.array(img)

#Simple crop
crop_img = img[25:,]
图片

你可能希望保持纵横比。在这种情况下,你可以将不需要的像素变为黑色(第3行),从而实现类似的结果。

#Change pixels to black
crop_img = np.array(img)
crop_img[:25,] = [0,0,0]
图片

通过裁剪,我们可以删除不必要的像素。我们还可以避免模型对训练数据的过拟合。例如,背景中的椅子可能存在于所有左拐处。因此,模型可能会将它们与预测关联起来。

查看上面的图像,你可能会想进一步裁剪它。也就是说,你可以在不删除任何轨道的情况下裁剪图像的左侧。但是,在图4中,你可以看到对于其他图像,我们将删除轨道的重要部分。

#Additional cropping 
crop_img = np.array(img)
crop_img[:25,] = [0,0,0]
crop_img[:,:40] = [0,0,0]
图片

在这里,你不知道将在何时向模型显示哪个图像。这意味着必须对所有图像应用相同的裁剪功能。你需要确保它永远不会删除图像的重要部分。

灰度

对于某些应用程序,图像的颜色并不重要。在这种情况下,我们可以将图像转为灰度。我们使用OpenCV的cvtColor函数进行此操作(第2行)。

#Gray scale
gray_img = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
图片

灰度缩放通过捕捉图像中的颜色强度来工作。它通过对RGB通道进行加权平均来实现这一点。具体来说,上面的函数使用了这个公式:

Y = 0.299*R + 0.587*G + 0.114*B

我们可以通过查看每个图像的输入值的数量来了解这一点的好处。如果我们使用所有的RGB通道,它将包含150,528个值(224*224*3)。对于灰度图像,我们现在只有50,176个值(224*224)。简单的输入意味着我们需要更少的数据和更简单的模型。

RGB通道

其中一个通道可能更重要。我们可以只使用那个通道而不是灰度化。下面,我们选择R(第6行),G(第7行)和B(第8行)通道。每个结果数组的维数将为224 x 224。你可以在图6中看到各自的图像。

#Load image
img = Image.open(img_paths[700])
img = np.array(img)

#Get rgb channels
r_img = img[:, :, 0]
g_img = img[:, :, 1]
b_img = img[:, :, 2]
图片

你也可以使用channel_filter函数。这里,通道参数(c)将根据你想要的通道取值0、1或2。请记住,一些包会以不同的顺序加载通道。我们使用的是PIL,即RGB。但是,如果你使用cv2.imread()加载图像,则通道将按BGR顺序排列。

def channel_filter(img,c=0):
    """Returns given channel from image pixels"""
    img = np.array(img)
    c_img = img[:, :, c]

    return c_img

使用这些变换时,你需要考虑是否从图像中删除了重要的信息。对于我们的应用程序,轨道是橙色的。换句话说,轨道的颜色可以帮助将其与图像的其余部分区分开来。

强度阈值

使用灰度化,每个像素将具有0到255之间的值。我们可以通过将其转换为二进制值来进一步简化输入。如果灰度值超过了阈值,则像素值为1,否则为0。我们将此截止值称为强度阈值。

下面的函数用于应用此阈值。我们首先将图像灰度化(第5行)。如果像素值超过了截止值,则将其赋予值1000(第8行)。如果我们将像素设置为1,它将低于截止值。换句话说,在下一步中,所有像素都将设置为0(第9行)。最后,我们缩放所有像素,使它们采用0或1的值(第11行)。

def threshold(img,cutoff=80):
    """Apply intesity thresholding"""
    
    img = np.array(img)
    img = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
    
    #Apply cutoff
    img[img>cutoff] = 1000 #black
    img[img<=cutoff] = 0 #white
    
    img = img/1000

    return img

自动驾驶汽车项目的一部分是避免障碍物。这些是涂成黑色的罐头。在图7中,你可以看到如何应用强度阈值函数,我们可以将罐头与图像的其余部分隔离开来。这仅在罐头是黑色时才可能。换句话说,它的强度大于图像的其余部分。

图片

截止点可以视为超参数。从图7中可以看出,较大的截止点意味着我们包括的背景噪声较少。缺点是我们捕捉到的罐头较少。

边缘检测

如果我们想要隔离轨道,我们可以使用Canny边缘检测。这是用于检测图像中边缘的多阶段算法。如果你想了解它的工作原理,建议阅读Sofiane Sahir的有关Canny边缘检测的文章。

https://towardsdatascience.com/canny-edge-detection-step-by-step-in-python-computer-vision-b49c3a2d8123

我们使用cv2.Canny函数应用算法。threshold1和threshold2参数用于迟滞过程。这是边缘检测算法的最终过程,用于确定哪些线实际上是边缘。

#Apply canny edge detection
edge_img = cv2.Canny(img,threshold1 = 50, threshold2 = 80)

你可以在图8中看到一些示例。与强度阈值处理类似,我们得到的是一个二进制地图——白色代表边缘,否则为黑色。希望现在轨道更容易从图像的其余部分区分出来。然而,你会发现背景中的边缘也被检测到了。

图片

颜色过滤器

通过使用像素颜色,我们可能会更幸运地隔离轨道。我们使用下面的pixel_filter函数来实现这一点。使用cv2.inRange将图像转换为二进制地图(第10行)。

此函数检查像素是否落在由下限(第5行)和上限(第6行)列表给出的范围内。具体来说,每个RGB通道必须落在各自的范围内(例如,134-t≤R≤194+t)。

def pixel_filter(img, t=0):
    
    """Filter pixels within range"""
    
    lower = [134-t,84-t,55-t]
    upper = [192+t,121+t,101+t]

    img = np.array(img)
    orange_thresh = 255 - cv2.inRange(img, np.array(lower), np.array(upper))

    return orange_thresh

简单来说,该函数确定像素颜色是否足够接近赛道橙色。你可以在图9中看到结果。参数t引入了一定的灵活性。较高的值可以捕捉更多的赛道,但同时也会保留更多的噪声。这是因为背景中的像素也会落在范围内。

图片

你可能想知道我们如何得到下限和上限。也就是说,我们如何知道通道将落在[134,84,55]和[192,121,101]之间?我们使用Python创建了一个颜色选择器。我们在下面的文章中概述了如何创建它。

https://towardsdatascience.com/building-a-color-picker-with-python-55e8357539e7

在图10中,你可以看到选择器的工作原理。我们从多个图像中选择像素,并尝试在赛道的不同位置选择它们。这样我们就可以在不同条件下获得像素值的完整范围。

图片

我们总共选择了60种颜色。你可以在图11中看到所有这些颜色(还带有额外的视觉错觉)。所有这些颜色的RGB通道存储在一个列表中–“colors”。

图片

最后,我们获取每个RGB通道的最小和最大值。这给出了下限和上限。

lower = [min(x[0] for x in colours),
              min(x[1] for x in colours),
              min(x[2] for x in colours)]

upper = [max(x[0] for x in colours),
              max(x[1] for x in colours),
              max(x[2] for x in colours)]

特征工程的限制

操作之后,你可能还没有被说服。深度学习的一个主要优点是它可以识别复杂的模式,而不需要进行特征工程。这是一个很好的观点。

特征工程需要批判性思考。你需要弄清楚图像的哪些方面是重要的。然后,你需要编写代码来提取这些方面。对于某些应用程序,执行所有这些操作所需的时间没有意义。

此外,对于某些方法,我们发现我们无法消除所有噪声。例如,在强度阈值处理中的黑色背景。直觉上,剩下的噪声可能会更难与重要的内容区分。也就是说,剩余的噪声和对象像素具有相同的值(1)。

实际上,在处理相对简单的计算机视觉问题时,才能得到好处。我们的赛道永远不会改变,对象的颜色始终相同。对于更复杂的问题,你需要更多的数据。或者,你可以在较小的数据集上微调预训练模型。

数据集

JatRacer Images (CC0: Public Domain) https://www.kaggle.com/datasets/conorsully1/jatracer-images

参考引用

OpenCV, Color conversions https://docs.opencv.org/3.4/de/d25/imgproc_color_conversions.html

作者:磐怼怼 | 来源:公众号——深度学习与计算机视觉(uncle_pn)

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论