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 一样,你可以在初始化时传递 encodes 和 decodes,或通过子类化并实现它们。你也可以对在每次 __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 随机翻转
调用 Image、TensorImage、TensorPoint 和 TensorBBox 的 @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 |
调用 PILImage、TensorImage、TensorPoint 和 TensorBBox 的 @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 对象 |
调用 Image、TensorImage、TensorPoint 和 TensorBBox 的 @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_sample 和 F.affine_grid,使用更方便。使用 F.affine_grid 可以更容易地生成 coords,因为 coords 通常很大,为 [H, W, 2],其中 H 和 W 是你的图像 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_sample 的 align_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_sample 的 align_corners 参数 |
| kwargs | VAR_KEYWORD |
调用 TensorImage、TensorMask、TensorPoint 和 TensorBBox 的 @patch 修饰的 affine_coord 行为
在对对应 size 的基本 grid 进行相应的仿射变换之前,将 aff_fs 返回的所有矩阵相乘,然后将所有 coord_fs 应用于生成的坐标流,最后使用 mode 和 pad_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 中的单个坐标相乘,只会返回 x 和 y 的相同值。 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 cornertensor([ 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_tensor。 def_draw 是默认的 `draw` 函数,用于在未提供自定义用户设置时执行。 draw 是用户定义的行为,可以是函数、浮点数列表或浮点数。 draw 和 def_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_sample 的 align_corners 参数 |
| batch | bool | False | 对整个批次应用相同的翻转 |
调用 TensorImage、TensorMask、TensorPoint 和 TensorBBox 的 @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_sample 的 align_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_sample 的 align_corners 参数 |
调用 TensorImage、TensorMask、TensorPoint 和 TensorBBox 的 @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_sample 的 align_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_sample 的 align_corners 参数 |
| batch | bool | False | 对整个批次应用相同的旋转 |
调用 TensorImage、TensorMask、TensorPoint 和 TensorBBox 的 @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_sample 的 align_corners 参数 |
调用 TensorImage、TensorMask、TensorPoint 和 TensorBBox 的 @patch 修饰的 zoom 行为
如果你想定制应用变换时选择哪个缩放比例和中心(默认情况下,第一个参数 `draw` 是1到 `max_zoom` 之间的随机浮点数,后两个参数 `draw_x` 和 `draw_y` 是0到1之间的随机浮点数),可以指定 draw、draw_x 和 draw_y。每个参数可以是浮点数,一个包含此类浮点数的列表(其长度应等于或大于批次大小),或一个返回浮点 tensor 的可调用对象。
draw_x 和 draw_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)
从 p1 到 p2 找到扭曲变换 (`tfm`) 的系数
| 类型 | 详情 | |
|---|---|---|
| p1 | Tensor | 原始点 |
| p2 | Tensor | 目标点 |
apply_perspective
apply_perspective (coords:torch.Tensor, coeffs:torch.Tensor)
使用 coeffs 对 coords 应用透视变换
| 类型 | 详情 | |
|---|---|---|
| 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_sample 的 align_corners 参数 |
调用 TensorImage、TensorMask、TensorPoint 和 TensorBBox 的 @patch 修饰的 warp 行为
如果你想定制应用变换时选择哪个强度(默认是 `-magnitude` 和 `magnitude` 之间的随机浮点数),可以指定 draw_x 和 draw_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会使更多中性颜色呈现出潜在的彩色。
rgb2hsv 和 hsv2rgb 是用于在 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_rotate、max_lighting、max_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_sample 的 align_corners 参数 |
| batch | bool | False | 对整个批次应用相同的变换 |
| min_scale | float | 1.0 | 裁剪区域的最小比例,相对于图像面积 |
当 do_flip=True 时,会以 p=0.5 添加随机翻转(如果 flip_vert=True 则为二面体变换)。通过 p_affine,我们应用最大角度为 max_rotate 的随机旋转,min_zoom 和 max_zoom 之间的随机缩放以及强度为 max_warp 的透视扭曲。通过 p_lighting,我们应用最大强度为 max_lighting 的亮度与对比度变化。可以添加自定义 xtra_tfms。 size、mode 和 pad_mode 将用于插值。 max_rotate、max_lighting、max_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_fncam_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)