该文件引用于:Ayoosh Kathuria的翻译稿。
引用地址:Data Augmentation For Bounding Boxes: Flipping
当我们谈到从深度学习任务中获得良好的性能时,一般认为数据越多越好。但是,我们可能只有有限的数据。数据增强是通过人工增强我们的数据集来应对这种数据短缺的一种方法。事实上,该技术已被证明非常成功,以至于它已经成为深度学习系统的主要内容。
为什么数据增强是有效的?
理解数据增强为何有效的一种非常直接的方法是将其视为人为扩展数据集的一种方式。与深度学习应用程序一样,数据越多越好。另一种理解数据增强为何如此有效的方法是将其视为对我们的数据集添加的噪声。在在线数据增强的情况下尤其如此,或者每次我们将每个数据样本提供给训练循环时随机地增强它。
左:原始图像,右:增强图像。
每次神经网络看到相同的图像时,由于应用了随机数据增强,它会有点不同。这种差异可以看作是每次都添加到我们的数据样本中的噪声,这种噪声迫使神经网络学习泛化特征,而不是在数据集上过度拟合。
边界框的对象检测
现在,Github 上的很多深度学习库,如 torchvision、keras 和专门的库都为分类训练任务提供了数据增强。但是,仍然缺少对对象检测任务的数据增强的支持。例如,用于分类任务的水平翻转图像的增强将如上图所示。但是,为对象检测任务执行相同的增强还需要您更新边界框。例如,这个。
水平翻转期间边界框的变化
正是这种数据增强,或者具体来说,主要数据增强技术的检测等价物需要我们更新边界框,我们将在本文中介绍。准确地说,这是我们将要介绍的增强功能的确切列表。
1.水平翻转(如上图)
2. 缩放和翻译
3. 旋转
4. 剪切
5. 调整神经网络输入的大小
技术细节
我们将基于Numpy和OpenCV 构建我们的小型数据增强库。我们将我们的扩充定义为类,可以调用其实例来执行扩充。我们将定义一种统一的方式来定义这些类,以便您也可以编写自己的数据扩充。我们还将定义一个 Data Augmentation,它自己什么都不做,而是结合数据增强,以便它们可以应用到Sequence中。
对于每个数据增强,我们将定义它的两种变体,一种是随机的,一种是确定的。在随机的情况下,增强是随机发生的,而在确定性的情况下,增强的参数(如要旋转的角度是固定的)。
数据增强示例:水平翻转
本文将概述编写增强的一般方法。我们还将介绍一些实用功能,这些功能将帮助我们可视化检测以及其他一些东西。那么,让我们开始吧。
存储注释的格式
对于每个图像,我们将边界框注释存储在一个N行 5 列的 numpy 数组中。这里,N代表图像中物体的数量,而五列代表:
1.左上x坐标
2.左上y坐标
3.右下x坐标
4.右下y坐标
5.对象的类别
存储边界框注释的格式
我知道很多数据集,并且注释工具以其他格式存储注释,因此,我会让您将存储数据注释的任何存储格式转换为上述格式。
是的,出于演示目的,我们将使用下面的莱昂内尔·梅西(Lionel Messi)在对阵尼日利亚的比赛中打入漂亮进球的图片。
文件组织
我们将代码保存在 2 个文件中,data_aug.py并且bbox_util.py. 第一个文件将包含增强的代码,而第二个文件将包含辅助函数的代码。这两个文件都将位于一个名为data_aug;让我们假设您必须在训练循环中使用这些数据增强。我会让您弄清楚如何提取图像并确保注释格式正确。
但是,为了简单起见,让我们一次只使用一张图像。您可以轻松地将此代码移动到循环内,或者您的数据获取函数以扩展功能。克隆包含训练代码文件的文件夹中的 github 存储库,或者您需要进行增强的文件。
随机水平翻转
首先,我们导入所有必要的东西并确保添加了路径,即使我们从包含文件的文件夹之外调用函数也是如此。以下代码进入文件data_aug.py
import random
import numpy as np
import cv2
import matplotlib.pyplot as plt
import sys
import os
lib_path = os.path.join(os.path.realpath("."), "data_aug")
sys.path.append(lib_path)
将要实现的数据增强是以概率pRandomHorizontalFlip水平翻转图像。
我们首先从定义类开始,它是__init__方法。init 方法包含增强的参数。对于这种增强,它是每个图像被翻转的概率。对于像旋转这样的另一种增强,它可能包含对象要旋转的角度。
class RandomHorizontalFlip(object):
"""Randomly horizontally flips the Image with the probability *p*
Parameters
----------
p: float
The probability with which the image is flipped
Returns
-------
numpy.ndaaray
Flipped image in the numpy format of shape `HxWxC`
numpy.ndarray
Tranformed bounding box co-ordinates of the format `n x 4` where n is
number of bounding boxes and 4 represents `x1,y1,x2,y2` of the box
"""
def __init__(self, p=0.5):
self.p = p
该函数的文档字符串已以Numpy文档字符串格式编写。这对于使用 Sphinx 生成文档很有用。
每个函数的__init__方法用于定义增强的所有参数。然而,增强的实际逻辑是在 __call__ 函数中定义的。
当从类实例调用时,调用函数有两个参数,img其中bboxes包含img像素值的 OpenCV numpy 数组和bboxes包含边界框注释的 numpy 数组。该__call__函数还返回相同的参数,这有助于我们将一堆增强链接在一起以应用于序列。
def __call__(self, img, bboxes):
img_center = np.array(img.shape[:2])[::-1]/2
img_center = np.hstack((img_center, img_center))
if random.random() < self.p:
img = img[:,::-1,:]
bboxes[:,[0,2]] += 2*(img_center[[0,2]] - bboxes[:,[0,2]])
box_w = abs(bboxes[:,0] - bboxes[:,2])
bboxes[:,0] -= box_w
bboxes[:,2] += box_w
return img, bboxes
让我们一点一点地打破这里发生的事情。
在水平翻转中,我们围绕通过其中心的垂直线旋转图像。
然后每个角的新坐标可以描述为该角在穿过图像中心的垂直线上的镜像。对于数学上的倾斜,穿过中心的垂直线将是连接原始角和新的转换角的线的垂直平分线。
为了更好地了解正在发生的事情,请考虑下图。变换图像的右半部分和原始图像的左半部分中的像素是关于中心线的彼此的镜像。
上面是通过下面的一段代码来完成的。
img_center = np.array(img.shape[:2])[::-1]/2
img_center = np.hstack((img_center, img_center))
if random.random() < self.p:
img = img[:,::-1,:]
bboxes[:,[0,2]] += 2*(img_center[[0,2]] - bboxes[:,[0,2]])
请注意,该行img = img[:,::-1,:]基本上采用包含图像的数组并将其元素反转为第一维或存储像素值的 x 坐标的维。但是,必须注意左上角的镜像是结果框的右上角。事实上,结果坐标是边界框的右上角和左下角坐标。但是,我们需要它们以左上角和右下角的格式。
我们代码的副作用
以下代码负责转换。
box_w = abs(bboxes[:,0] - bboxes[:,2])
bboxes[:,0] -= box_w
bboxes[:,2] += box_w
我们最终返回图像和包含边界框的数组。
Horizo ntalFlip的确定性版本
上面的代码以概率p随机应用转换。但是,如果我们想构建一个确定性版本,我们可以简单地将参数p传递为 1。或者我们可以编写另一个类,其中我们根本没有参数p,并__call__像这样实现函数。
def __call__(self, img, bboxes):
img_center = np.array(img.shape[:2])[::-1]/2
img_center = np.hstack((img_center, img_center))
img = img[:,::-1,:]
bboxes[:,[0,2]] += 2*(img_center[[0,2]] - bboxes[:,[0,2]])
box_w = abs(bboxes[:,0] - bboxes[:,2])
bboxes[:,0] -= box_w
bboxes[:,2] += box_w
return img, bboxes
在行动中看到它
现在,假设您必须对图像使用Horizo ntalFlip 增强。我们将在一张图片上使用它,但您可以在任何您喜欢的数字上使用它。首先,我们创建一个文件test.py。我们首先导入所有好东西。
from data_aug.data_aug import *
import cv2
import pickle as pkl
import numpy as np
import matplotlib.pyplot as plt
然后,我们导入图像并加载注释。
img = cv2.imread("messi.jpg")[:,:,::-1] #OpenCV uses BGR channels
bboxes = pkl.load(open("messi_ann.pkl", "rb"))
#print(bboxes) #visual inspection
为了查看我们的增强是否真的有效,我们定义了一个辅助函数draw_rect,它接收img并bboxes返回一个 numpy 图像数组,并在该图像上绘制边界框。
让我们创建一个文件bbox_utils.py并导入必要的东西。
import cv2
import numpy as np
现在,我们定义函数draw_rect
def draw_rect(im, cords, color = None):
"""Draw the rectangle on the image
Parameters
----------
im : numpy.ndarray
numpy image
cords: numpy.ndarray
Numpy array containing bounding boxes of shape `N X 4` where N is the
number of bounding boxes and the bounding boxes are represented in the
format `x1 y1 x2 y2`
Returns
-------
numpy.ndarray
numpy image with bounding boxes drawn on it
"""
im = im.copy()
cords = cords.reshape(-1,4)
if not color:
color = [255,255,255]
for cord in cords:
pt1, pt2 = (cord[0], cord[1]) , (cord[2], cord[3])
pt1 = int(pt1[0]), int(pt1[1])
pt2 = int(pt2[0]), int(pt2[1])
im = cv2.rectangle(im.copy(), pt1, pt2, color, int(max(im.shape[:2])/200))
return im
一旦完成,让我们回到我们的test.py文件,并绘制原始边界框。
plt.imshow(draw_rect(img, bboxes))
这会产生类似的东西。
让我们看看我们改造的效果。
hor_flip = RandomHorizontalFlip(1)
img, bboxes = hor_flip(img, bboxes)
plt.imshow(draw_rect(img, bboxes))
你应该得到这样的东西。