视觉数据增强

用于计算机视觉数据增强的变换
img = PILImage(PILImage.create(TEST_IMAGE).resize((600,400)))

RandTransform-


源代码

RandTransform

 RandTransform (p:float=1.0, nm:str=None, before_call:Callable=None,
                **kwargs)

一个在每次 __call__ 前调用 before_call 方法设置状态的变换

类型 默认值 详情
p float 1.0 应用变换的概率
nm str None
before_call Callable None 可选的批次预处理函数
kwargs VAR_KEYWORD

与所有 Transform 一样,你可以在初始化时传递 encodesdecodes,或通过子类化并实现它们。你也可以对在每次 __call__ 时调用的 before_call 方法做同样的操作。注意,为了使输入和目标的状态一致,RandTransform 必须应用于元组级别。

默认情况下,before_call 的行为是以概率 p 执行变换(如果子类化并希望调整此行为,则会查找属性 self.do(如果存在)来决定是否执行变换)。

注意

默认情况下,RandTransform 只应用于训练集,因此如果你直接调用它而不是通过 Datasets 调用,必须传递 split_idx=0。通过将变换的属性 split_idx 设置为 None,可以改变此行为。

RandTransform.before_call
<function __main__.RandTransform.before_call(self, b, split_idx: 'int')>

源代码

RandTransform.before_call

 RandTransform.before_call (b, split_idx:int)

此函数可以被覆盖。根据 self.p 设置 self.do

类型 详情
b
split_idx int 训练集/验证集的索引
def _add1(x): return x+1
dumb_tfm = RandTransform(enc=_add1, p=0.5)
start,d1,d2 = 2,False,False
for _ in range(40):
    t = dumb_tfm(start, split_idx=0)
    if dumb_tfm.do: test_eq(t, start+1); d1=True
    else:           test_eq(t, start)  ; d2=True
assert d1 and d2
dumb_tfm
_add1 -- {'p': 0.5}
(enc:1,dec:0)

项目变换


源代码

FlipItem

 FlipItem (p:float=0.5)

以概率 p 随机翻转

调用 ImageTensorImageTensorPointTensorBBox@patch 修饰的 flip_lr 行为

tflip = FlipItem(p=1.)
test_eq(tflip(bbox,split_idx=0), tensor([[1.,0., 0.,1]]) -1)

源代码

DihedralItem

 DihedralItem (p:float=1.0, nm:str=None, before_call:Callable=None,
               **kwargs)

以概率 p 随机翻转

类型 默认值 详情
p float 1.0 应用变换的概率
nm str None
before_call Callable None 可选的批次预处理函数
kwargs VAR_KEYWORD

调用 PILImageTensorImageTensorPointTensorBBox@patch 修饰的 dihedral 行为

默认情况下,应用变换时,8种二面体变换(包括不操作)中的每一种被选中的概率相同。你可以通过传递自定义的 draw 函数来定制此行为。要强制执行特定的翻转,你也可以传递一个0到7之间的整数。

_,axs = subplots(2, 4)
for ax in axs.flatten():
    show_image(DihedralItem(p=1.)(img, split_idx=0), ctx=ax)

带裁剪、填充或挤压的调整大小


PadMode

 PadMode (*args, **kwargs)

所有可能的填充模式作为属性,以便进行Tab补全和防止拼写错误


源代码

CropPad

 CropPad (size:int|tuple, pad_mode:PadMode='zeros', **kwargs)

中心裁剪或填充图像到指定 size

类型 默认值 详情
size int | tuple 裁剪或填充到的尺寸,如果指定一个值则会重复
pad_mode PadMode zeros 一个 PadMode 对象

调用 ImageTensorImageTensorPointTensorBBox@patch 修饰的 crop_pad 行为

_,axs = plt.subplots(1,3,figsize=(12,4))
for ax,sz in zip(axs.flatten(), [300, 500, 700]):
    show_image(img.crop_pad(sz), ctx=ax, title=f'Size {sz}');
    print(img.crop_pad(sz).shape)
(300, 300)
(500, 500)
(700, 700)

_,axs = plt.subplots(1,3,figsize=(12,4))
for ax,mode in zip(axs.flatten(), [PadMode.Zeros, PadMode.Border, PadMode.Reflection]):
    show_image(img.crop_pad((600,700), pad_mode=mode), ctx=ax, title=mode);


源代码

RandomCrop

 RandomCrop (size:int|tuple, **kwargs)

随机裁剪图像到指定 size

类型 详情
size int | tuple 裁剪到的尺寸,如果指定一个值则会重复

源代码

OldRandomCrop

 OldRandomCrop (size:int|tuple, pad_mode:PadMode='zeros', enc=None,
                dec=None, split_idx=None, order=None)

随机裁剪图像到指定 size

类型 默认值 详情
size int | tuple 裁剪或填充到的尺寸,如果指定一个值则会重复
pad_mode PadMode zeros 一个 PadMode 对象
enc NoneType None
dec NoneType None
split_idx NoneType None
order NoneType None
_,axs = plt.subplots(1,3,figsize=(12,4))
f = RandomCrop(200)
for ax in axs: show_image(f(img), ctx=ax);

在验证集上,我们进行中心裁剪。

_,axs = plt.subplots(1,3,figsize=(12,4))
for ax in axs: show_image(f(img, split_idx=1), ctx=ax);


ResizeMethod

 ResizeMethod (*args, **kwargs)

所有可能的调整大小方法作为属性,以便进行Tab补全和防止拼写错误

test_eq(ResizeMethod.Squish, 'squish')

源代码

Resize

 Resize (size:int|tuple, method:ResizeMethod='crop',
         pad_mode:PadMode='reflection', resamples=(<Resampling.BILINEAR:
         2>, <Resampling.NEAREST: 0>), **kwargs)

一个在每次 __call__ 前调用 before_call 方法设置状态的变换

类型 默认值 详情
size int | tuple 调整到的尺寸,如果指定一个值则会重复
method ResizeMethod crop 一个 ResizeMethod 对象
pad_mode PadMode reflection 一个 PadMode 对象
resamples tuple (<Resampling.BILINEAR: 2>, <Resampling.NEAREST: 0>) Pillow Image 重采样模式,resamples[1] 用于mask

size 可以是一个整数(在这种情况下图像将被调整为正方形)或一个元组。根据 method 的不同:- 我们将任何矩形挤压到 size - 我们调整大小,使较短的维度匹配 size 并使用 pad_mode 进行填充 - 我们调整大小,使较长的维度匹配 size 并进行裁剪(在训练集上随机,在验证集上中心裁剪)。

进行调整大小时,我们对图像使用 resamples[0],对分割mask使用 resamples[1]

_,axs = plt.subplots(1,3,figsize=(12,4))
for ax,method in zip(axs.flatten(), [ResizeMethod.Squish, ResizeMethod.Pad, ResizeMethod.Crop]):
    rsz = Resize(256, method=method)
    show_image(rsz(img, split_idx=0), ctx=ax, title=method);

在验证集上,裁剪始终是中心裁剪(针对被裁剪的维度)。

_,axs = plt.subplots(1,3,figsize=(12,4))
for ax,method in zip(axs.flatten(), [ResizeMethod.Squish, ResizeMethod.Pad, ResizeMethod.Crop]):
    rsz = Resize(256, method=method)
    show_image(rsz(img, split_idx=1), ctx=ax, title=method);


源代码

RandomResizedCrop

 RandomResizedCrop (size:int|tuple, min_scale:float=0.08, ratio=(0.75,
                    1.3333333333333333), resamples=(<Resampling.BILINEAR:
                    2>, <Resampling.NEAREST: 0>), val_xtra:float=0.14,
                    max_scale:float=1.0, **kwargs)

选择图像的随机缩放裁剪区域并将其调整到指定 size

类型 默认值 详情
size int | tuple 最终尺寸,如果指定一个值则会重复,,
min_scale float 0.08 裁剪区域的最小比例,相对于图像面积
ratio tuple (0.75, 1.3333333333333333) 输出的宽高比范围
resamples tuple (<Resampling.BILINEAR: 2>, <Resampling.NEAREST: 0>) Pillow Image 重采样模式,resamples[1] 用于mask
val_xtra float 0.14 在验证集中边缘被裁剪掉的尺寸比例
max_scale float 1.0 裁剪区域的最大比例,相对于图像面积

裁剪区域会随机选择一个在 (min_scale, max_scale) 范围内的缩放比例和传递范围内的 ratio,然后对图像使用 resamples[0],对分割mask使用 resamples[1] 进行调整大小。在验证集上,如果图像的 ratio 不在范围内(调整到最小值或最大值),我们则中心裁剪图像,然后调整大小。

crop = RandomResizedCrop(256)
_,axs = plt.subplots(3,3,figsize=(9,9))
for ax in axs.flatten():
    cropped = crop(img)
    show_image(cropped, ctx=ax);

test_eq(cropped.shape, [256,256])

在验证集上使用挤压(squish),首先移除每侧 val_xtra 比例的部分。

_,axs = subplots(1,3)
for ax in axs.flatten(): show_image(crop(img, split_idx=1), ctx=ax);

通过将 max_scale 设置得较低,可以强制进行小范围裁剪。

small_crop = RandomResizedCrop(256, min_scale=0.05, max_scale=0.15)
_,axs = plt.subplots(3,3,figsize=(9,9))
for ax in axs.flatten():
    cropped = small_crop(img)
    show_image(cropped, ctx=ax);


源代码

RatioResize

 RatioResize (max_sz:int, resamples=(<Resampling.BILINEAR: 2>,
              <Resampling.NEAREST: 0>), **kwargs)

保持纵横比,将图像的最大维度调整到 max_sz

类型 默认值 详情
max_sz int 调整大小后的图像的最大维度
resamples tuple (<Resampling.BILINEAR: 2>, <Resampling.NEAREST: 0>) Pillow Image 重采样模式,resamples[1] 用于mask
kwargs VAR_KEYWORD
RatioResize(256)(img)

GPU 上的仿射和坐标变换

timg = TensorImage(array(img)).permute(2,0,1).float()/255.
def _batch_ex(bs): return TensorImage(timg[None].expand(bs, *timg.shape).clone())

使用 coords 中的坐标将 x 中的坐标映射到新的位置,用于像翻转这样的变换。最好使用 TensorImage.affine_coord,因为它结合了 _grid_sampleF.affine_grid,使用更方便。使用 F.affine_grid 可以更容易地生成 coords,因为 coords 通常很大,为 [H, W, 2],其中 HW 是你的图像 x 的高和宽。

这是我们将用于以下示例的起始图像。

img=torch.tensor([[[0,0,0],[1,0,0],[2,0,0]],
               [[0,1,0],[1,1,0],[2,1,0]],
               [[0,2,0],[1,2,0],[2,2,0]]]).permute(2,0,1)[None]/2.
show_images(img)

这里我们使用 _grid_sample,但不改变原始图像。注意 grid 中的坐标如何映射到 img 中的坐标。

grid=torch.tensor([[[[-1,-1],[0,-1],[1,-1]],
               [[-1,0],[0,0],[1,0]],
               [[-1,1],[0,1],[1,1.]]]])
img=_grid_sample(img, grid,align_corners=True)
show_images(img)

接下来,我们通过手动编辑 grid 来进行翻转。

grid=torch.tensor([[[1.,-1],[0,-1],[-1,-1]],
               [[1,0],[0,0],[-1,0]],
               [[1,1],[0,1],[-1,1]]])
img=_grid_sample(img, grid[None],align_corners=True)
show_images(img)

接下来,我们将图像向上移动一个单位。默认情况下,_grid_sample 使用反射填充。

grid=torch.tensor([[[[-1,0],[0,0],[1,0]],
               [[-1,1],[0,1],[1,1]],
               [[-1,2],[0,2],[1,2.]]]]) 
img=_grid_sample(img, grid,align_corners=True)
show_images(img)

affine_coord 让我们更容易地处理图像,通过允许我们指定小得多的 mat,与需要为每个像素指定值的 grid 相比。


源代码

affine_grid

 affine_grid (theta:torch.Tensor, size:tuple, align_corners:bool=None)

从变换仿射矩阵 theta 生成 TensorFlowField

类型 默认值 详情
theta Tensor 批次仿射变换矩阵
size tuple 输出尺寸
align_corners bool None PyTorch F.grid_samplealign_corners 参数

源代码

AffineCoordTfm

 AffineCoordTfm (aff_fs:Callable|MutableSequence=None,
                 coord_fs:Callable|MutableSequence=None,
                 size:int|tuple=None, mode='bilinear',
                 pad_mode='reflection', mode_mask='nearest',
                 align_corners=None, **kwargs)

组合并应用仿射和坐标变换

类型 默认值 详情
aff_fs Union None 用于批次的仿射变换函数
coord_fs Union None 用于批次的坐标变换函数
size int | tuple None 输出尺寸,如果指定一个值则会重复
mode str bilinear PyTorch F.grid_sample 插值模式
pad_mode str reflection 一个 PadMode 对象
mode_mask str nearest mask 的重采样模式
align_corners NoneType None PyTorch F.grid_samplealign_corners 参数
kwargs VAR_KEYWORD

调用 TensorImageTensorMaskTensorPointTensorBBox@patch 修饰的 affine_coord 行为

在对对应 size 的基本 grid 进行相应的仿射变换之前,将 aff_fs 返回的所有矩阵相乘,然后将所有 coord_fs 应用于生成的坐标流,最后使用 modepad_mode 进行插值。

这里展示了如何在图像上使用 affine_coord 的示例。包括恒等变换(或原始图像)、翻转以及将图像向左移动。

imgs=_batch_ex(3)
identity=torch.tensor([[1,0,0],[0,1,0.]])
flip=torch.tensor([[-1,0,0],[0,1,0.]])
translation=torch.tensor([[1,0,1.],[0,1,0]])
mats=torch.stack((identity,flip,translation))
show_images(imgs.affine_coord(mats,pad_mode=PadMode.Zeros)) #Zeros easiest to see

现在你可能会问,“这个 mat 是什么?” 让我们快速看看下面的恒等矩阵。

imgs=_batch_ex(1)
identity=torch.tensor([[1,0,0],[0,1,0.]])
eye=identity[:,0:2]
bi=identity[:,2:3]
eye,bi
(tensor([[1., 0.],
         [0., 1.]]),
 tensor([[0.],
         [0.]]))

注意 tensor ‘eye’ 是一个恒等矩阵。如果我们将它与原始图像 x,y 中的单个坐标相乘,只会返回 xy 的相同值。 bi 在此乘法后添加。例如,让我们翻转图像,使左上角位于右上角

t=torch.tensor([[-1,0,0],[0,1,0.]])
eye=t[:,0:2]
bi=t[:,2:3]
xy=torch.tensor([-1.,-1]) #upper left corner
torch.sum(xy*eye,dim=1)+bi[0] #now the upper right corner
tensor([ 1., -1.])

源代码

AffineCoordTfm.compose

 AffineCoordTfm.compose (tfm)

self 与另一个 AffineCoordTfm 组合,以便只执行一次插值步骤


源代码

RandomResizedCropGPU

 RandomResizedCropGPU (size, min_scale=0.08, ratio=(0.75,
                       1.3333333333333333), mode='bilinear',
                       valid_scale=1.0, max_scale=1.0,
                       mode_mask='nearest', **kwargs)

选择图像的随机缩放裁剪区域并将其调整到指定 size

类型 默认值 详情
size 最终尺寸,如果指定一个值则会重复
min_scale float 0.08 裁剪区域的最小比例,相对于图像面积
ratio tuple (0.75, 1.3333333333333333) 输出的宽高比范围
mode str bilinear PyTorch F.grid_sample 插值模式
valid_scale float 1.0 验证集裁剪区域的比例,相对于图像面积
max_scale float 1.0 裁剪区域的最大比例,相对于图像面积
mode_mask str nearest TensorMask 的插值模式
kwargs VAR_KEYWORD
t = _batch_ex(8)
rrc = RandomResizedCropGPU(224, p=1.)
y = rrc(t)
_,axs = plt.subplots(2,4, figsize=(12,6))
for ax in axs.flatten():
    show_image(y[i], ctx=ax)

注意

RandomResizedCropGPU 对批次中的所有图像使用相同的区域。

GPU 辅助函数

本节包含用于在 GPU 上进行数据增强的辅助函数,这些函数在整个代码中使用。


源代码

mask_tensor

 mask_tensor (x:torch.Tensor, p=0.5, neutral=0.0, batch=False)

以概率 1-p 使用 neutral 掩盖 x 中的元素

类型 默认值 详情
x Tensor 输入 Tensor
p float 0.5 不应用掩码的概率
neutral float 0.0 掩码值
batch bool False 对整个批次应用相同的掩码

让我们看一些 mask_tensor 如何使用的示例,我们使用 clone() 是因为此操作会覆盖输入。对于此示例,我们尝试使用角度来旋转图像。

with no_random():
    x=torch.tensor([60,-30,90,-210,270,-180,120,-240,150])
    print('p=0.5: ',mask_tensor(x.clone()))
    print('p=1.0: ',mask_tensor(x.clone(),p=1.))
    print('p=0.0: ',mask_tensor(x.clone(),p=0.))
p=0.5:  tensor([  60,  -30,   90, -210,    0, -180,    0,    0,  150])
p=1.0:  tensor([  60,  -30,   90, -210,  270, -180,  120, -240,  150])
p=0.0:  tensor([0, 0, 0, 0, 0, 0, 0, 0, 0])

注意 p 控制了某个值被替换为0的可能性,或者保持不变(因为0度旋转就是原始图像)。 batch 作用于整个批次,而不是批次的单个元素。现在让我们考虑一个不同的例子,处理亮度。注意:对于亮度,0表示完全黑色的图像。

x=torch.tensor([0.6,0.4,0.3,0.7,0.4])
print('p=0.: ',mask_tensor(x.clone(),p=0))
print('p=0.,neutral=0.5: ',mask_tensor(x.clone(),p=0,neutral=0.5))
p=0.:  tensor([0., 0., 0., 0., 0.])
p=0.,neutral=0.5:  tensor([0.5000, 0.5000, 0.5000, 0.5000, 0.5000])

如果得到完全黑色的图像,这将非常糟糕,因为这不是未改变的图像。因此我们将 neutral 设置为0.5,这是亮度未改变图像的值。

_draw_mask 用于支持许多后续变换的 API,以创建 mask_tensors(p, neutral, batch) 被传递给 mask_tensordef_draw 是默认的 `draw` 函数,用于在未提供自定义用户设置时执行。 draw 是用户定义的行为,可以是函数、浮点数列表或浮点数。 drawdef_draw 都必须返回一个 tensor。

这里我们为 def_draw 使用1到8之间的随机整数,此示例与 Dihedral 非常相似。

x = torch.zeros(10,2,3)
def def_draw(x):
    x=torch.randint(1,8, (x.size(0),))
    return x
with no_random(): print(torch.randint(1,8, (x.size(0),)))
with no_random(): print(_draw_mask(x, def_draw))
tensor([2, 3, 5, 6, 5, 4, 6, 6, 1, 1])
TensorBase([2, 0, 0, 6, 5, 4, 6, 0, 0, 1])

接下来,有三种定义 draw 的方式:作为常数、作为列表和作为函数。所有这些都会覆盖 def_draw,因此 def_draw 对最终结果没有影响。

with no_random():
    print('const: ',_draw_mask(x, def_draw, draw=1))
    print('list : ', _draw_mask(x, def_draw, draw=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
    print('list : ',_draw_mask(x[0:2], def_draw, draw=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]))
    print('funct: ',_draw_mask(x, def_draw, draw=lambda x: torch.arange(1,x.size(0)+1)))
    try:
        _draw_mask(x, def_draw, draw=[1,2])
    except AssertionError as e:
        print(type(e),'\n',e)
const:  TensorBase([1., 1., 1., 1., 0., 1., 0., 0., 1., 1.])
list :  TensorBase([ 1.,  2.,  0.,  0.,  5.,  0.,  7.,  0.,  0., 10.])
list :  TensorBase([1., 0.])
funct:  TensorBase([ 1,  2,  3,  4,  0,  6,  7,  8,  9, 10])
<class 'AssertionError'> 
 

注意,当使用列表时,它可以大于批次大小,但不能小于批次大小。否则将没有足够的增强应用于批次中的元素。

x = torch.zeros(5,2,3)
def_draw = lambda x: torch.randint(0,8, (x.size(0),))
t = _draw_mask(x, def_draw)
assert (0. <= t).all() and (t <= 7).all() 
t = _draw_mask(x, def_draw, 1)
assert (0. <= t).all() and (t <= 1).all() 
test_eq(_draw_mask(x, def_draw, 1, p=1), tensor([1.,1,1,1,1]))
test_eq(_draw_mask(x, def_draw, [0,1,2,3,4], p=1), tensor([0.,1,2,3,4]))
test_eq(_draw_mask(x[0:3], def_draw, [0,1,2,3,4], p=1), tensor([0.,1,2]))
for i in range(5):
    t = _draw_mask(x, def_draw, 1,batch=True)
    assert (t==torch.zeros(5)).all() or (t==torch.ones(5)).all()

翻转/二面体 GPU 辅助函数

affine_mat 用于将长度为6的向量转换为 [bs, 3, 3] 的 tensor。这用于允许我们组合仿射变换。


源代码

affine_mat

 affine_mat (*ms)

将长度为6的向量 ms 重构为最后一行是0,0,1的仿射矩阵

这是一个使用 affine_mat 进行图像翻转的示例。

flips=torch.tensor([-1,1,-1])
ones=t1(flips)
zeroes=t0(flips)
affines=affine_mat(flips,zeroes,zeroes,zeroes,ones,zeroes)
print(affines)
tensor([[[-1,  0,  0],
         [ 0,  1,  0],
         [ 0,  0,  1]],

        [[ 1,  0,  0],
         [ 0,  1,  0],
         [ 0,  0,  1]],

        [[-1,  0,  0],
         [ 0,  1,  0],
         [ 0,  0,  1]]])

这样做是为了我们可以在不对整个图像进行数学运算的情况下组合多个仿射变换。我们需要矩阵具有相同的大小,以便进行矩阵乘法来组合仿射变换。虽然这通常是对整个批次进行的,但这是一个对单个图像进行多次翻转变换的示例。由于我们翻转两次,最终得到一个只会返回原始图像的仿射矩阵。

如果你想了解更多关于此如何工作的信息,请参阅 affine_coord

x = torch.eye(3,dtype=torch.int64)
for affine in affines: 
    x @= affine
    print(x)
tensor([[-1,  0,  0],
        [ 0,  1,  0],
        [ 0,  0,  1]])
tensor([[-1,  0,  0],
        [ 0,  1,  0],
        [ 0,  0,  1]])
tensor([[1, 0, 0],
        [0, 1, 0],
        [0, 0, 1]])

flip_mat 将以概率 p 生成表示批次翻转的 [bs, 3, 3] tensor。 draw 可以用于定义一个函数、常数或列表,用于定义要使用的翻转类型。如果 draw 是一个列表,其长度必须大于或等于批次大小。对于 draw,0表示原始图像,1表示翻转图像。 batch 表示整个批次将一起翻转或不翻转。


源代码

flip_mat

 flip_mat (x:torch.Tensor, p=0.5,
           draw:Union[int,collections.abc.MutableSequence,Callable]=None,
           batch:bool=False)

返回一个随机翻转矩阵

类型 默认值 详情
x Tensor 输入 Tensor
p float 0.5 应用变换的概率
draw Union None 自定义翻转而非随机翻转
batch bool False 对整个批次应用相同的翻转

下面是使用 draw 作为常数、列表和函数的示例。

with no_random():
    x=torch.randn(2,4,3)
    print('const: ',flip_mat(x, draw=1))
    print('list : ', flip_mat(x, draw=[1, 0]))
    print('list : ',flip_mat(x[0:2], draw=[1, 0, 1, 0, 1]))
    print('funct: ',flip_mat(x, draw=lambda x: torch.ones(x.size(0))))
    test_fail(lambda: flip_mat(x, draw=[1]))
const:  TensorBase([[[-1.,  0.,  0.],
             [ 0.,  1.,  0.],
             [ 0.,  0.,  1.]],

            [[ 1.,  0.,  0.],
             [ 0.,  1.,  0.],
             [ 0.,  0.,  1.]]])
list :  TensorBase([[[-1.,  0.,  0.],
             [ 0.,  1.,  0.],
             [ 0.,  0.,  1.]],

            [[ 1.,  0.,  0.],
             [ 0.,  1.,  0.],
             [ 0.,  0.,  1.]]])
list :  TensorBase([[[-1.,  0.,  0.],
             [ 0.,  1.,  0.],
             [ 0.,  0.,  1.]],

            [[ 1.,  0.,  0.],
             [ 0.,  1.,  0.],
             [ 0.,  0.,  1.]]])
funct:  TensorBase([[[ 1.,  0.,  0.],
             [ 0.,  1.,  0.],
             [ 0.,  0.,  1.]],

            [[-1.,  0.,  0.],
             [ 0.,  1.,  0.],
             [ 0.,  0.,  1.]]])
x = flip_mat(torch.randn(100,4,3))
test_eq(set(x[:,0,0].numpy()), {-1,1}) #might fail with probability 2*2**(-100) (picked only 1s or -1s)

水平翻转图像、mask、点和边界框。 p 是应用翻转的概率。 draw 可以用于定义自定义翻转行为。


源代码

Flip

 Flip (p=0.5, draw:int|MutableSequence|Callable=None, size:int|tuple=None,
       mode:str='bilinear', pad_mode='reflection', align_corners=True,
       batch=False)

以概率 p 随机翻转一批图像

类型 默认值 详情
p float 0.5 应用翻转的概率
draw Union None 自定义翻转而非随机翻转
size int | tuple None 输出尺寸,如果指定一个值则会重复
mode str bilinear PyTorch F.grid_sample 插值模式
pad_mode str reflection 一个 PadMode 对象
align_corners bool True PyTorch F.grid_samplealign_corners 参数
batch bool False 对整个批次应用相同的翻转

调用 TensorImageTensorMaskTensorPointTensorBBox@patch 修饰的 flip_batch 行为

这里是一些使用 flip 的示例。注意,常数 draw=1 实际上与默认设置相同。另请注意,在第三个示例中,通过设置 p=1. 并定义自定义 draw,我们可以获得精细控制。

with no_random(32):
    imgs = _batch_ex(5)
    deflt = Flip()
    const = Flip(p=1.,draw=1) #same as default
    listy = Flip(p=1.,draw=[1,0,1,0,1]) #completely manual!!!
    funct = Flip(draw=lambda x: torch.ones(x.size(0))) #same as default

    show_images( deflt(imgs) ,suptitle='Default Flip')
    show_images( const(imgs) ,suptitle='Constant Flip',titles=[f'Flipped' for i in['','','','','']]) #same above
    show_images( listy(imgs) ,suptitle='Listy Flip',titles=[f'{i}Flipped' for i in ['','Not ','','Not ','']])
    show_images( funct(imgs) ,suptitle='Flip By Function') #same as default

flip = Flip(p=1.)
t = _pnt2tensor([[1,0], [2,1]], (3,3))

y = flip(TensorImage(t[None,None]), split_idx=0)
test_eq(y, _pnt2tensor([[1,0], [0,1]], (3,3))[None,None])

pnts = TensorPoint((tensor([[1.,0.], [2,1]]) -1)[None])
test_eq(flip(pnts, split_idx=0), tensor([[[1.,0.], [0,1]]]) -1)

bbox = TensorBBox(((tensor([[1.,0., 2.,1]]) -1)[None]))
test_eq(flip(bbox, split_idx=0), tensor([[[0.,0., 1.,1.]]]) -1)

源代码

DeterministicDraw

 DeterministicDraw (vals)

初始化 self。有关准确签名,请参阅 help(type(self))。

t =  _batch_ex(8)
draw = DeterministicDraw(list(range(8)))
for i in range(15): test_eq(draw(t), torch.zeros(8)+(i%8))

源代码

DeterministicFlip

 DeterministicFlip (size:int|tuple=None, mode:str='bilinear',
                    pad_mode='reflection', align_corners=True, **kwargs)

每隔一次调用翻转批次

类型 默认值 详情
size int | tuple None 输出尺寸,如果指定一个值则会重复
mode str bilinear PyTorch F.grid_sample 插值模式
pad_mode str reflection 一个 PadMode 对象
align_corners bool True PyTorch F.grid_samplealign_corners 参数
kwargs VAR_KEYWORD

接下来,我们遍历示例图像的多个批次。 DeterministicFlip 首先不翻转图像,然后在下一个批次翻转图像。

b = _batch_ex(2)
dih = DeterministicFlip()
for i,flipped in enumerate(['Not Flipped','Flipped']*2):
    show_images(dih(b),suptitle=f'Batch {i}',titles=[flipped]*2)

由于我们处理的是正方形和矩形,我们可以将二面体翻转视为沿水平、垂直和对角线的翻转及其组合。但请记住,矩形沿其对角线不对称,因此这将有效地裁剪矩形的一部分。


源代码

dihedral_mat

 dihedral_mat (x:torch.Tensor, p:float=0.5,
               draw:Union[int,collections.abc.MutableSequence,Callable]=No
               ne, batch:bool=False)

返回一个随机二面体矩阵

类型 默认值 详情
x Tensor 输入 Tensor
p float 0.5 保持不变的概率
draw Union None 自定义二面体变换而非随机
batch bool False 对整个批次应用相同的二面体变换

源代码

Dihedral

 Dihedral (p=0.5, draw:int|MutableSequence|Callable=None,
           size:int|tuple=None, mode:str='bilinear',
           pad_mode='reflection', batch=False, align_corners=True)

以概率 p 对一批图像应用随机二面体变换

类型 默认值 详情
p float 0.5 应用二面体变换的概率
draw Union None 自定义二面体变换而非随机
size int | tuple None 输出尺寸,如果指定一个值则会重复
mode str bilinear PyTorch F.grid_sample 插值模式
pad_mode str reflection 一个 PadMode 对象
batch bool False 对整个批次应用相同的二面体变换
align_corners bool True PyTorch F.grid_samplealign_corners 参数

调用 TensorImageTensorMaskTensorPointTensorBBox@patch 修饰的 dihedral_batch 行为

如果你想定制应用变换时选择哪种翻转类型(默认是0到7之间的随机数),可以指定 draw。它可以是0到7之间的整数,一个包含此类整数的列表(其长度应等于或大于批次大小),或一个返回0到7之间 long tensor 的可调用对象。

with no_random():
    imgs = _batch_ex(5)
    deflt = Dihedral()
    const = Dihedral(p=1.,draw=1) #same as flip_batch
    listy = Dihedral(p=1.,draw=[0,1,2,3,4]) #completely manual!!!
    funct = Dihedral(draw=lambda x: torch.randint(0,8,(x.size(0),))) #same as default

    show_images( deflt(imgs) ,suptitle='Default Flips',titles=[i for i in range(imgs.size(0))])
    show_images( const(imgs) ,suptitle='Constant Horizontal Flip',titles=[f'Flip 1' for i in [0,1,1,1,1]])
    show_images( listy(imgs) ,suptitle='Manual Listy Flips',titles=[f'Flip {i}' for i in [0,1,2,3,4]]) #manually specified, not random! 
    show_images( funct(imgs) ,suptitle='Default Functional Flips',titles=[i for i in range(imgs.size(0))]) #same as default


源代码

DeterministicDihedral

 DeterministicDihedral (size:int|tuple=None, mode:str='bilinear',
                        pad_mode='reflection', align_corners=None)

以概率 p 对一批图像应用随机二面体变换

类型 默认值 详情
size int | tuple None 输出尺寸,如果指定一个值则会重复
mode str bilinear PyTorch F.grid_sample 插值模式
pad_mode str reflection 一个 PadMode 对象
align_corners NoneType None PyTorch F.grid_samplealign_corners 参数

DeterministicDihedral 保证第一次调用不会翻转,随后的调用将按确定性顺序翻转。在所有7种可能的二面体翻转后,模式将重置为未翻转的版本。如果我们对批次大小为1进行此操作,它将如下所示:

t = _batch_ex(10)
dih = DeterministicDihedral()
_,axs = plt.subplots(2,5, figsize=(14,6))
for i,ax in enumerate(axs.flatten()):
    y = dih(t)
    show_image(y[0], ctx=ax, title=f'Batch {i}')


源代码

rotate_mat

 rotate_mat (x:torch.Tensor, max_deg:int=10, p:float=0.5,
             draw:Union[int,collections.abc.MutableSequence,Callable]=None
             , batch:bool=False)

返回一个最大角度 max_deg、概率 p 的随机旋转矩阵

类型 默认值 详情
x Tensor 输入 Tensor
max_deg int 10 最大旋转角度(度)
p float 0.5 应用旋转的概率
draw Union None 自定义旋转而非随机旋转
batch bool False 对整个批次应用相同的旋转

源代码

Rotate

 Rotate (max_deg:int=10, p:float=0.5,
         draw:int|MutableSequence|Callable=None, size:int|tuple=None,
         mode:str='bilinear', pad_mode='reflection',
         align_corners:bool=True, batch:bool=False)

以概率 p 对一批图像应用最大角度为 max_deg 的随机旋转

类型 默认值 详情
max_deg int 10 最大旋转角度(度)
p float 0.5 应用旋转的概率
draw Union None 自定义旋转而非随机旋转
size int | tuple None 输出尺寸,如果指定一个值则会重复
mode str bilinear PyTorch F.grid_sample 插值模式
pad_mode str reflection 一个 PadMode 对象
align_corners bool True PyTorch F.grid_samplealign_corners 参数
batch bool False 对整个批次应用相同的旋转

调用 TensorImageTensorMaskTensorPointTensorBBox@patch 修饰的 rotate 行为

如果你想定制应用变换时选择哪个角度(默认是 `-max_deg` 和 `max_deg` 之间的随机浮点数),可以指定 draw。它可以是浮点数,一个包含此类浮点数的列表(其长度应等于或大于批次大小),或一个返回浮点 tensor 的可调用对象。

Rotate 默认只能旋转10度,这使得变化更难察觉。这通常与 `flip` 或 `dihedral` 结合使用,它们默认会产生更大的变化。例如,旋转180度与垂直翻转相同。

with no_random():
    thetas = [-30,-15,0,15,30]
    imgs = _batch_ex(5)
    deflt = Rotate()
    const = Rotate(p=1.,draw=180) #same as a vertical flip
    listy = Rotate(p=1.,draw=[-30,-15,0,15,30]) #completely manual!!!
    funct = Rotate(draw=lambda x: x.new_empty(x.size(0)).uniform_(-10, 10)) #same as default

    show_images( deflt(imgs) ,suptitle='Default Rotate, notice the small rotation',titles=[i for i in range(imgs.size(0))])
    show_images( const(imgs) ,suptitle='Constant 180 Rotate',titles=[f'180 Degrees' for i in range(imgs.size(0))])
    #manually specified, not random! 
    show_images( listy(imgs) ,suptitle='Manual List Rotate',titles=[f'{i} Degrees' for i in [-30,-15,0,15,30]])
    #same as default
    show_images( funct(imgs) ,suptitle='Default Functional Rotate',titles=[i for i in range(imgs.size(0))])


源代码

zoom_mat

 zoom_mat (x:torch.Tensor, min_zoom:float=1.0, max_zoom:float=1.1,
           p:float=0.5, draw:Union[float,collections.abc.MutableSequence,C
           allable]=None, draw_x:Union[float,collections.abc.MutableSequen
           ce,Callable]=None, draw_y:Union[float,collections.abc.MutableSe
           quence,Callable]=None, batch:bool=False)

返回一个最大缩放比例 max_zoom、概率 p 的随机缩放矩阵

类型 默认值 详情
x Tensor 输入 Tensor
min_zoom float 1.0 最小缩放比例
max_zoom float 1.1 最大缩放比例
p float 0.5 应用缩放的概率
draw Union None 用户定义的缩放比例
draw_x Union None 用户定义的在x轴上的缩放中心
draw_y Union None 用户定义的在y轴上的缩放中心
batch bool False 对整个批次应用相同的缩放

源代码

Zoom

 Zoom (min_zoom:float=1.0, max_zoom:float=1.1, p:float=0.5,
       draw:float|MutableSequence|Callable=None,
       draw_x:float|MutableSequence|Callable=None,
       draw_y:float|MutableSequence|Callable=None, size:int|tuple=None,
       mode='bilinear', pad_mode='reflection', batch=False,
       align_corners=True)

以概率 p 对一批图像应用最大缩放比例为 max_zoom 的随机缩放

类型 默认值 详情
min_zoom float 1.0 最小缩放比例
max_zoom float 1.1 最大缩放比例
p float 0.5 应用缩放的概率
draw Union None 用户定义的缩放比例
draw_x Union None 用户定义的在x轴上的缩放中心
draw_y Union None 用户定义的在y轴上的缩放中心
size int | tuple None 输出尺寸,如果指定一个值则会重复
mode str bilinear PyTorch F.grid_sample 插值模式
pad_mode str reflection 一个 PadMode 对象
batch bool False 对整个批次应用相同的缩放
align_corners bool True PyTorch F.grid_samplealign_corners 参数

调用 TensorImageTensorMaskTensorPointTensorBBox@patch 修饰的 zoom 行为

如果你想定制应用变换时选择哪个缩放比例和中心(默认情况下,第一个参数 `draw` 是1到 `max_zoom` 之间的随机浮点数,后两个参数 `draw_x` 和 `draw_y` 是0到1之间的随机浮点数),可以指定 drawdraw_xdraw_y。每个参数可以是浮点数,一个包含此类浮点数的列表(其长度应等于或大于批次大小),或一个返回浮点 tensor 的可调用对象。

draw_xdraw_y 预期是以百分比表示的中心位置,0表示最左/最上方可能的位置,1表示最右/最下方可能的位置。

注意:默认情况下,缩放比例相当小。

with no_random():
    scales = [0.8, 1., 1.1, 1.25, 1.5]
    imgs = _batch_ex(5)
    deflt = Zoom()
    const = Zoom(p=1., draw=1.5) #'Constant scale and different random centers'
    listy = Zoom(p=1.,draw=scales,draw_x=0.5, draw_y=0.5) #completely manual scales, constant center
    funct = Zoom(draw=lambda x: x.new_empty(x.size(0)).uniform_(1., 1.1)) #same as default

    show_images( deflt(imgs) ,suptitle='Default Zoom, note the small zooming', titles=[i for i in range(imgs.size(0))])
    show_images( const(imgs) ,suptitle='Constant Scale, Valiable Position', titles=[f'Scale 1.5x' for i in range(imgs.size(0))])
    show_images( listy(imgs) ,suptitle='Manual Listy Scale, Centered', titles=[f'Scale {i}x' for i in scales])
    show_images( funct(imgs) ,suptitle='Default Functional Zoom', titles=[i for i in range(imgs.size(0))]) #same as default

扭曲


源代码

find_coeffs

 find_coeffs (p1:torch.Tensor, p2:torch.Tensor)

p1p2 找到扭曲变换 (`tfm`) 的系数

类型 详情
p1 Tensor 原始点
p2 Tensor 目标点

源代码

apply_perspective

 apply_perspective (coords:torch.Tensor, coeffs:torch.Tensor)

使用 coeffscoords 应用透视变换

类型 详情
coords Tensor 原始坐标
coeffs Tensor 扭曲变换矩阵

源代码

Warp

 Warp (magnitude:float=0.2, p:float=0.5,
       draw_x:float|MutableSequence|Callable=None,
       draw_y:float|MutableSequence|Callable=None, size:int|tuple=None,
       mode:str='bilinear', pad_mode='reflection', batch:bool=False,
       align_corners:bool=True)

以强度 magnitude 和概率 p 对一批图像应用透视扭曲

类型 默认值 详情
magnitude float 0.2 默认扭曲强度
p float 0.5 应用扭曲的概率
draw_x Union None 用户定义的在x轴上的扭曲强度
draw_y Union None 用户定义的在y轴上的扭曲强度
size int | tuple None 输出尺寸,如果指定一个值则会重复
mode str bilinear PyTorch F.grid_sample 插值模式
pad_mode str reflection 一个 PadMode 对象
batch bool False 对整个批次应用相同的扭曲
align_corners bool True PyTorch F.grid_samplealign_corners 参数

调用 TensorImageTensorMaskTensorPointTensorBBox@patch 修饰的 warp 行为

如果你想定制应用变换时选择哪个强度(默认是 `-magnitude` 和 `magnitude` 之间的随机浮点数),可以指定 draw_xdraw_y。每个参数可以是浮点数,一个包含此类浮点数的列表(其长度应等于或大于批次大小),或一个返回浮点 tensor 的可调用对象。

scales = [-0.4, -0.2, 0., 0.2, 0.4]
imgs=_batch_ex(5)
vert_warp = Warp(p=1., draw_y=scales, draw_x=0.)
horz_warp = Warp(p=1., draw_x=scales, draw_y=0.)
show_images( vert_warp(imgs) ,suptitle='Vertical warping', titles=[f'magnitude {i}' for i in scales])
show_images( horz_warp(imgs) ,suptitle='Horizontal warping', titles=[f'magnitude {i}' for i in scales])

光照变换

光照变换是影响图像中光线表示方式的变换。这些变换不像之前的变换那样改变对象的位置,而是模拟场景中光线可能发生的变化。simclr 论文 评估了这些变换在自监督图像分类用例中的效果,注意他们使用“color”和“color distortion”来指代这些变换的组合。


源代码

TensorImage.lighting

 TensorImage.lighting (x:fastai.torch_core.TensorImage, func)

大多数光照变换在“对数空间”(logit space)中效果更好,因为我们不希望通过超过最大或最小亮度来“吹白”图像。对 logit 取 sigmoid 允许我们回到“线性空间”(linear space)。

x=TensorImage(torch.tensor([.01* i for i in range(0,101)]))
f_lin= lambda x:(2*(x-0.5)+0.5).clamp(0,1) #blue line
f_log= lambda x:2*x #red line
plt.plot(x,f_lin(x),'b',x,x.lighting(f_log),'r');

上图显示了在线性和对数空间中进行对比度变换的结果。注意蓝色线性图必须进行钳位(clamped),并且我们失去了关于0.0相对于0.2有多大的信息。而在红色图中,值呈现曲线变化,因此我们保留了这种相对信息。

首先,我们创建一个通用的 SpaceTfm。这使得我们可以组合多个变换,以便在进行多个变换之前只需进行一次空间转换。 space_fn 必须将 rgb 转换为某个空间,应用一个函数,然后转换回 rgb。 fs 应该是一个类似列表的对象,包含将要组合在一起的函数。


源代码

SpaceTfm

 SpaceTfm (fs:Callable|MutableSequence, space_fn:Callable, **kwargs)

fs 应用于 logits

类型 详情
fs Union 在某个空间中应用的变换函数
space_fn Callable 将 rgb 转换为某个空间并在应用 fs 后转换回 rgb 的函数
kwargs VAR_KEYWORD

LightingTfm 是一个 SpaceTfm,它使用 TensorImage.lighting 转换为对数空间(logit space)。使用它可以限制图像在变得非常暗或非常亮时丢失细节。


源代码

LightingTfm

 LightingTfm (fs:Callable|MutableSequence, **kwargs)

fs 应用于 logits

类型 详情
fs Union 在对数空间(logit space)中应用的变换函数,,
kwargs VAR_KEYWORD

亮度是指场景上的光线量。它可以为零(图像完全黑色)或为一(图像完全白色)。如果你的数据集预期会包含过曝或欠曝图像,这可能会特别有用。


源代码

亮度

 Brightness (max_lighting:float=0.2, p:float=0.75,
             draw:float|MutableSequence|Callable=None, batch=False)

fs 应用于 logits

类型 默认值 详情
max_lighting float 0.2 改变亮度的最大比例
p float 0.75 应用变换的概率
draw Union None 用户定义的批次变换行为
batch bool False 对整个批次应用相同的亮度

调用 TensorImage@patch 修饰的 brightness 行为

如果你想定制应用变换时选择哪个强度(默认是 `-0.5*(1-max_lighting)` 和 `0.5*(1+max_lighting)` 之间的随机浮点数),可以指定 draw。每个参数可以是浮点数,一个包含此类浮点数的列表(其长度应等于或大于批次大小),或一个返回浮点 tensor 的可调用对象。

scales = [0.1, 0.3, 0.5, 0.7, 0.9]
y = _batch_ex(5).brightness(draw=scales, p=1.)
fig,axs = plt.subplots(1,5, figsize=(15,3))
for i,ax in enumerate(axs.flatten()):
    show_image(y[i], ctx=ax, title=f'scale {scales[i]}')

对比度将像素推向最大或最小值。对比度的最小值是纯灰色的图像。举个例子,拍摄黑暗房间里明亮光源的照片。你的眼睛应该能看到房间里的一些细节,但拍出的照片对比度会高得多,背景的所有细节都会因为黑暗而丢失。这是这个变换可以帮助模拟的一个例子。


源代码

对比度

 Contrast (max_lighting=0.2, p=0.75,
           draw:float|MutableSequence|Callable=None, batch=False)

以概率 p 对一批图像应用最大强度为 max_lighting 的对比度变化。

类型 默认值 详情
max_lighting float 0.2 改变对比度的最大比例
p float 0.75 应用变换的概率
draw Union None 用户定义的批次变换行为
batch bool False

调用 TensorImage@patch 修饰的 contrast 行为

如果你想定制应用变换时选择哪个强度(默认是取自 (1-max_lighting)1/(1-max_lighting) 之间对数均匀分布的随机浮点数),可以指定 draw。每个参数可以是浮点数,一个包含此类浮点数的列表(其长度应等于或大于批次大小),或一个返回浮点 tensor 的可调用对象。

scales = [0.65, 0.8, 1., 1.25, 1.55]
y = _batch_ex(5).contrast(p=1., draw=scales)
fig,axs = plt.subplots(1,5, figsize=(15,3))
for i,ax in enumerate(axs.flatten()): show_image(y[i], ctx=ax, title=f'scale {scales[i]}')


源代码

灰度

 grayscale (x)

将 Tensor 转换为灰度 Tensor。使用 ITU-R 601-2 亮度变换。

上面只是转换为灰度的一种方式。我们选择这种方式因为它速度快。注意每个通道权重的总和是1。

f'{sum([0.2989,0.5870,0.1140]):.3f}'
'1.000'

源代码

饱和度

 Saturation (max_lighting:float=0.2, p:float=0.75,
             draw:float|MutableSequence|Callable=None, batch:bool=False)

以概率 p 对一批图像应用最大强度为 max_lighting 的饱和度变化。

类型 默认值 详情
max_lighting float 0.2 改变亮度的最大比例
p float 0.75 应用变换的概率
draw Union None 用户定义的批次变换行为
batch bool False 对整个批次应用相同的饱和度变化

调用 TensorImage@patch 修饰的 saturation 行为

scales = [0., 0.5, 1., 1.5, 2.0]
y = _batch_ex(5).saturation(p=1., draw=scales)
fig,axs = plt.subplots(1,5, figsize=(15,3))
for i,ax in enumerate(axs.flatten()): show_image(y[i], ctx=ax, title=f'scale {scales[i]}')

饱和度控制图像中的颜色量,但不影响图像的明暗。它对白色、灰色和黑色等中性颜色没有影响。当饱和度为零时,你会得到一个灰度图像。将饱和度推高到超过1会使更多中性颜色呈现出潜在的彩色。

rgb2hsvhsv2rgb 是用于在 hsv 空间之间进行转换的工具函数。hsv 空间代表色相(hue)、饱和度(saturation)和明度(value)空间。这使我们能够更容易地执行某些变换。

torch.max(tensor([1]).as_subclass(TensorBase), dim=0)
torch.return_types.max(
values=TensorBase(1),
indices=TensorBase(0))

源代码

rgb2hsv

 rgb2hsv (img:torch.Tensor)

将 RGB 图像转换为 HSV 图像。注意:不适用于对数空间(logit space)图像。

类型 详情
img Tensor 批次图像 Tensor,格式为 RGB

源代码

hsv2rgb

 hsv2rgb (img:torch.Tensor)

将 HSV 图像转换为 RGB 图像。

类型 详情
img Tensor 批次图像 Tensor,格式为 HSV

与在对数空间(logit space)中进行的光照变换非常相似,hsv 变换在 hsv 空间中进行。我们可以组合在 hsv 空间中进行的任何变换。


源代码

HSVTfm

 HSVTfm (fs, **kwargs)

fs 应用于 HSV 空间中的图像

调用 TensorImage@patch 修饰的 hsv 行为

fig,axs=plt.subplots(figsize=(20, 4),ncols=5)
axs[0].set_ylabel('Hue')
for ax in axs:
    ax.set_xlabel('Saturation')
    ax.set_yticklabels([])
    ax.set_xticklabels([])

hsvs=torch.stack([torch.arange(0,2.1,0.01)[:,None].repeat(1,210),
                 torch.arange(0,1.05,0.005)[None].repeat(210,1),
                 torch.ones([210,210])])[None]
for ax,i in zip(axs,range(0,5)):
    if i>0: hsvs[:,2].mul_(0.80)
    ax.set_title('V='+'%.1f' %0.8**i)
    ax.imshow(hsv2rgb(hsvs)[0].permute(1,2,0))

对于色相(Hue)变换,我们使用 hsv 空间而不是对数空间(logit space)。HSV 代表色相(hue)、饱和度(saturation)和明度(value)。hsv 空间中的色相只是在彩虹颜色中循环。注意它没有最大值,因为颜色会重复。

上面是一些在不同明度(V)下的色相(H)和饱和度(S)示例。在 HSV 空间中值得注意的一个属性是,V 控制了在 HSV 空间中最低饱和度时获得的颜色。


源代码

色相

 Hue (max_hue:float=0.1, p:float=0.75,
      draw:float|MutableSequence|Callable=None, batch=False)

以概率 p 对一批图像应用最大强度为 max_hue 的色相变化。

类型 默认值 详情
max_hue float 0.1 改变色相的最大比例
p float 0.75 应用变换的概率
draw Union None 用户定义的批次变换行为
batch bool False 对整个批次应用相同的色相变化

调用 TensorImage@patch 修饰的 hue 行为

scales = [0.5, 0.75, 1., 1.5, 1.75]
y = _batch_ex(len(scales)).hue(p=1., draw=scales)
fig,axs = plt.subplots(1,len(scales), figsize=(15,3))
for i,ax in enumerate(axs.flatten()): show_image(y[i], ctx=ax, title=f'scale {scales[i]}')

RandomErasing

随机擦除数据增强 (Random Erasing Data Augmentation)。这个变体由 Ross Wightman 设计,在 Tensor 被归一化后应用于批次或单个图像 Tensor。


源代码

cutout_gaussian

 cutout_gaussian (x:torch.Tensor, areas:list)

用 N(0,1) 噪声替换 x 中的所有 areas 区域

类型 详情
x Tensor 输入图像
areas list 要剪切掉的区域列表。顺序为 rl, rh, cl, ch

由于这应该在归一化后应用,我们将定义一个辅助函数,用于在归一化内部应用函数。


源代码

norm_apply_denorm

 norm_apply_denorm (x:torch.Tensor, f:Callable, nrm:Callable)

使用 nrm 归一化 x,然后应用 f,然后反归一化

类型 详情
x Tensor 输入图像
f Callable 要应用的函数
nrm Callable 归一化变换
nrm = Normalize.from_stats(*imagenet_stats, cuda=False)
f = partial(cutout_gaussian, areas=[(100,200,100,200),(200,300,200,300)])
show_image(norm_apply_denorm(timg, f, nrm)[0]);


源代码

RandomErasing

 RandomErasing (p:float=0.5, sl:float=0.0, sh:float=0.3,
                min_aspect:float=0.3, max_count:int=1)

在图像中随机选择一个矩形区域并将其像素随机化。

类型 默认值 详情
p float 0.5 应用 Random Erasing 的概率
sl float 0.0 擦除区域的最小比例
sh float 0.3 擦除区域的最大比例
min_aspect float 0.3 擦除区域的最小纵横比
max_count int 1 每张图像最大擦除块数,每个块的面积会根据数量进行缩放
tfm = RandomErasing(p=1., max_count=6)

_,axs = subplots(2,3, figsize=(12,6))
f = partial(tfm, split_idx=0)
for i,ax in enumerate(axs.flatten()): show_image(norm_apply_denorm(timg, f, nrm)[0], ctx=ax)

tfm = RandomErasing(p=1., max_count=6)

_,axs = subplots(2,3, figsize=(12,6))
f = partial(tfm, split_idx=0)
for i,ax in enumerate(axs.flatten()): show_image(norm_apply_denorm(timg, f, nrm)[0], ctx=ax)

tfm = RandomErasing(p=1., max_count=6)

_,axs = subplots(2,3, figsize=(12,6))
f = partial(tfm, split_idx=1)
for i,ax in enumerate(axs.flatten()): show_image(norm_apply_denorm(timg, f, nrm)[0], ctx=ax)

组合应用


源代码

setup_aug_tfms

 setup_aug_tfms (tfms)

遍历 tfms 并将仿射/坐标变换或光照变换组合在一起

#Affine only
tfms = [Rotate(draw=10., p=1), Zoom(draw=1.1, draw_x=0.5, draw_y=0.5, p=1.)]
comp = setup_aug_tfms([Rotate(draw=10., p=1), Zoom(draw=1.1, draw_x=0.5, draw_y=0.5, p=1.)])
test_eq(len(comp), 1)
x = torch.randn(4,3,5,5)
test_close(comp[0]._get_affine_mat(x)[...,:2],tfms[0]._get_affine_mat(x)[...,:2] @ tfms[1]._get_affine_mat(x)[...,:2])
#We can't test that the ouput of comp or the composition of tfms on x is the same cause it's not (1 interpol vs 2 sp)
#Affine + lighting
tfms = [Rotate(), Zoom(), Warp(), Brightness(), Flip(), Contrast()]
comp = setup_aug_tfms(tfms)
aff_tfm,lig_tfm = comp
test_eq(len(aff_tfm.aff_fs+aff_tfm.coord_fs+comp[1].fs), 6)
test_eq(len(aff_tfm.aff_fs), 3)
test_eq(len(aff_tfm.coord_fs), 1)
test_eq(len(lig_tfm.fs), 2)

源代码

aug_transforms

 aug_transforms (mult:float=1.0, do_flip:bool=True, flip_vert:bool=False,
                 max_rotate:float=10.0, min_zoom:float=1.0,
                 max_zoom:float=1.1, max_lighting:float=0.2,
                 max_warp:float=0.2, p_affine:float=0.75,
                 p_lighting:float=0.75, xtra_tfms:list=None,
                 size:int|tuple=None, mode:str='bilinear',
                 pad_mode='reflection', align_corners=True, batch=False,
                 min_scale=1.0)

一个用于轻松创建翻转、旋转、缩放、扭曲、光照变换列表的工具函数。

类型 默认值 详情
mult float 1.0 应用于 max_rotatemax_lightingmax_warp 的乘数
do_flip bool True 随机翻转
flip_vert bool False 垂直翻转
max_rotate float 10.0 最大旋转角度(度)
min_zoom float 1.0 最小缩放比例
max_zoom float 1.1 最大缩放比例
max_lighting float 0.2 改变亮度的最大比例
max_warp float 0.2 每次改变扭曲的最大值
p_affine float 0.75 应用仿射变换的概率
p_lighting float 0.75 改变亮度和对比度的概率
xtra_tfms list None 自定义变换
size int | tuple None 输出尺寸,如果指定一个值则会重复
mode str bilinear PyTorch F.grid_sample 插值模式
pad_mode str reflection 一个 PadMode 对象
align_corners bool True PyTorch F.grid_samplealign_corners 参数
batch bool False 对整个批次应用相同的变换
min_scale float 1.0 裁剪区域的最小比例,相对于图像面积

do_flip=True 时,会以 p=0.5 添加随机翻转(如果 flip_vert=True 则为二面体变换)。通过 p_affine,我们应用最大角度为 max_rotate 的随机旋转,min_zoommax_zoom 之间的随机缩放以及强度为 max_warp 的透视扭曲。通过 p_lighting,我们应用最大强度为 max_lighting 的亮度与对比度变化。可以添加自定义 xtra_tfmssizemodepad_mode 将用于插值。 max_rotatemax_lightingmax_warp 会乘以 mult,这样你可以更轻松地通过一个参数来增加或减少数据增强。

tfms = aug_transforms(pad_mode='zeros', mult=2, min_scale=0.5)
y = _batch_ex(9)
for t in tfms: y = t(y, split_idx=0)
_,axs = plt.subplots(1,3, figsize=(12,3))
for i,ax in enumerate(axs.flatten()): show_image(y[i], ctx=ax)

tfms = aug_transforms(pad_mode='zeros', mult=2, batch=True)
y = _batch_ex(9)
for t in tfms: y = t(y, split_idx=0)
_,axs = plt.subplots(1,3, figsize=(12,3))
for i,ax in enumerate(axs.flatten()): show_image(y[i], ctx=ax)

集成测试

分割

camvid = untar_data(URLs.CAMVID_TINY)
fns = get_image_files(camvid/'images')
cam_fn = fns[0]
mask_fn = camvid/'labels'/f'{cam_fn.stem}_P{cam_fn.suffix}'
def _cam_lbl(fn): return mask_fn
cam_dsrc = Datasets([cam_fn]*10, [PILImage.create, [_cam_lbl, PILMask.create]])
cam_tdl = TfmdDL(cam_dsrc.train, after_item=ToTensor(),
                 after_batch=[IntToFloatTensor(), *aug_transforms()], bs=9)
cam_tdl.show_batch(max_n=9, vmin=1, vmax=30)

点目标

mnist = untar_data(URLs.MNIST_TINY)
mnist_fn = 'images/mnist3.png'
pnts = np.array([[0,0], [0,35], [28,0], [28,35], [9, 17]])
def _pnt_lbl(fn)->None: return TensorPoint.create(pnts)
pnt_dsrc = Datasets([mnist_fn]*10, [[PILImage.create, Resize((35,28))], _pnt_lbl])
pnt_tdl = TfmdDL(pnt_dsrc.train, after_item=[PointScaler(), ToTensor()],
                 after_batch=[IntToFloatTensor(), *aug_transforms(max_warp=0)], bs=9)
pnt_tdl.show_batch(max_n=9)

边界框

coco = untar_data(URLs.COCO_TINY)
images, lbl_bbox = get_annotations(coco/'train.json')
idx=2
coco_fn,bbox = coco/'train'/images[idx],lbl_bbox[idx]

def _coco_bb(x):  return TensorBBox.create(bbox[0])
def _coco_lbl(x): return bbox[1]
coco_dsrc = Datasets([coco_fn]*10, [PILImage.create, [_coco_bb], [_coco_lbl, MultiCategorize(add_na=True)]], n_inp=1)
coco_tdl = TfmdDL(coco_dsrc, bs=9, after_item=[BBoxLabeler(), PointScaler(), ToTensor(), Resize(256)],
                  after_batch=[IntToFloatTensor(), *aug_transforms()])

coco_tdl.show_batch(max_n=9)