前言
这篇笔记继续上一篇笔记的内容,接着深入的学习了解Pytorch的各种数据增强方法,由于内容很多,所以分为了上下两篇。上篇笔记学习了裁剪,这篇笔记主学习旋转翻转、拓展、颜色增强和仿射变换等的数据增强方法。本笔记的知识框架主要来源于深度之眼,并依次作了内容的丰富拓展,拓展内容主要源自对torch文档的翻译理解,所用数据来源于网络。
代码结构与其他数据处理方法见:
深度之眼Pytorch打卡(九):Pytorch数据预处理——预处理过程与数据标准化(transforms过程、Normalize原理、常用数据集均值标准差与数据集均值标准差计算) 深度之眼Pytorch打卡(十):Pytorch数据预处理——数据统一与数据增强(上)
几篇数据增强笔记所涉代码见:Poetair/Pytorch-Learning/transforms
数据增强——翻转(Flip)
torchvision.transforms.RandomVerticalFlip(p=0.5)
transforms.RandomVerticalFlip(0.5),
transforms.ToTensor(),
依概率竖直翻转,只有一个参数p
,一般设为0.5
,表示获取到的图片有一半的概率是竖直翻转过的,而有一半的概率是原样,任取10张图的效果如图1所示。
torchvision.transforms.RandomHorizontalFlip(p=0.5)
transforms.RandomHorizontalFlip(0.5),
transforms.ToTensor(),
依概率水平翻转,只有一个参数p
,一般设为0.5
,表示获取到的图片有一半的概率是水平翻转过的,而有一半的概率是原样,任取10张图的效果如图2所示。
数据增强——旋转(Rotation)
torchvision.transforms.RandomRotation(degrees, resample=False, expand=False, center=None, fill=None)
degrees: 随机旋转角度的上下限,若为一数a
,则限为(-a,a)
,若为一个序列(a,b)
则限为(a,b)
。每次取出的图像,都是在限内随机旋转一个角度值而得到处理后的图像。
resample: 重采样,其实就是插值方法,决定旋转前后像素的映射方式。有如下方法:resample=False或1
=PIL.Image.NEAREST
,resample=2
=PIL.Image.BILINEAR
,resample=3
=PIL.Image.BICUBIC
。
expand: 是否拓展,旋转后图片边角会出界,如果不想边角信息丢失,设置expand=True
,表示拓展图像以保留边角。不同的旋转角度,保留边角后形成的图像尺寸不一致,故如果设置expand=True
,必须后面再跟一个Resize
。以绕图像中心旋转的情况来计算的,所以绕其他点时,就算拓展了也会存在信息丢失。
center: 旋转中心,默认是图像中心。用元组(x,y)表示旋转中心位置,如(0,0)就表示左上角,(width,0)表示右上角。
fill: 拓展时填充的值,默认0,即填充黑色。为一个值时就是灰度值,为一个元组(R,G,B)是表示颜色。
transforms.RandomRotation(90, resample=2),
transforms.ToTensor()
transforms.RandomRotation(90, resample=2, expand=True, fill=(0, 0, 255)),
transforms.ToTensor()
transforms.RandomRotation(90, resample=2, center=(224, 0)),
transforms.ToTensor(),
数据增强——拓展(Pad)
torchvision.transforms.Pad(padding, fill=0, padding_mode='constant')
padding: 拓展边缘的宽度,为常数a
时,表示四条边均拓展宽度a
。为元组(a,b)
时,表示左右拓展a
,上下拓展b
,为元组(a,b,c,d)
时,表示左,上,右,下,分别拓展a
,b
,c
,d
。
fill: 当padding_mode='constant'
时,填充边缘的颜色值。若为一个值,则表示灰度(发现也有颜色,应该不是灰度,有点像是通道R),如默认0
,填充黑色。若为一个元组(R,G,B)
时,表示填充某种RGB颜色。
padding_mode: 填充模式。填充常数,padding_mode='constant'
。以在原图中与该拓展点关于原图边缘对称位置的像素值填充,即镜像,padding_mode='reflect'
,padding_mode='symmetric'
。以原图最边缘的点的值进行填充,padding_mode='edge'
。
transforms.Pad((30, 200, 100, 20), fill=200),
transforms.ToTensor(),
transforms.Pad((30, 200, 100, 20), fill=200, padding_mode='edge'),
transforms.ToTensor(),
数据增强——变色(ColorJitter)
很常用的一种数据增强方式,平时由于采集设备、光线等原因,采到的图像的亮度brightness
、饱和度saturation
、对比度contrast
甚至是色相hue
都会发生变化。用进行过色彩增强的数据训练出的模型,能更好的克服这些因素变化带来的干扰。
torchvision.transforms.ColorJitter(brightness=0, contrast=0, saturation=0, hue=0)
该函数源码中,对输入的单个数进行了如下处理。对于亮度、饱和度和对比度而言,center=1
,clip_first_on_zero=True
。对于色相而言,center=0
,clip_first_on_zero=False
。由此便可以确定输入是单个数时的取值范围。
if isinstance(value, numbers.Number): # 如果输入是个数
value = [center - value, center + value]
if clip_first_on_zero:
value[0] = max(value[0], 0)
再深入探究源码的操作过程,可以发现输出是由两个部分构成的。如下,alpha
是在取值范围内随机产生的数,image2
是原图片,image1
是在特定类中为达到增强目的而新产生的一幅图像。
out = image1 * (1.0 - alpha) + image2 * alpha
brightness
类中产生的image1
,是一张全黑图像,叠加之后,输出相当于是输入亮度的alpha
倍。
Image.new(image.mode, image.size, 0)
Contrast
类中取原图的平均值,以此形成的image1
。易得,若alpha>1
,则1-alpha<0
,此时就相当于减去均值,为锐化操作,提升对比度,反之则减小对比度。
Image.new("L", image.size, mean).convert(image.mode)
saturation
的image1
是在Color
类中产生的。由一幅灰度图像转成的RGB图像,所以三个通道都是一样的值,即白色。若alpha<1
,则1-alpha>0
,此时就相当于加上白光,主色调被稀释,饱和度降低。
image.convert(self.intermediate_mode).convert(image.mode)
色调hue
,需要将图像转换到HSI
空间后单独调整H
才可以实现。下面代码中,hue_factor=alpha
是那个区间内的随机数,h = h+255*alpha
。
np_h += np.uint8(hue_factor * 255)
通过以上分析,可以更好地理解以下参数。
brightness: 亮度,决定明暗,为非负值,即bound=(0, float('inf')
。为正常数a
时,表示亮度变化倍数
是在区间[max(0,1-a),1+a]
上随机取的一个值,为元组(a,b)
时,表示亮度变化倍数
是在区间[a,b]
上随机取的一个值。
contrast: 对比度,衡量像素间的差异。取值情况同brightness
。
saturation: 饱和度,主色调的纯度,越高表示白光越少,颜色越鲜艳。取值情况同brightness
。
hue: 色相,可以理解为主色调。为正常数a
时,表示色相变化值是区间[-a,a]
上随机取的一个值,其中0<=a<=0.5
。为元组(a,b)
时,表示色相变化的值是在区间[a,b]
上随机取的一个值,其中-0.5<=a<=b<=0.5
。
transforms.ColorJitter(brightness=(1,2)), # (1,2),区间内随机选一个数来作为倍数
transforms.ToTensor(),
1-alpha<0
效果相当于锐化,增加对比度。
transforms.ColorJitter(contrast=(1, 2)),
transforms.ToTensor(),
transforms.ColorJitter(contrast=(0, 1)),
transforms.ToTensor(),
1-alpha>0
效果相当于平滑,降低对比度。
1-alpha>0
加白光,主色调被稀释,饱和度降低。
transforms.ColorJitter(saturation=(0, 1)),
transforms.ToTensor(),
transforms.ColorJitter(hue=0.5),
transforms.ToTensor(),
数据增强——转灰度图(Grayscale)
torchvision.transforms.RandomGrayscale(p=0.1)
对于输入图片,被转换成灰度图像的概率是p
。当输入是一个通道时,输出自动也是一个通道,当输入是三个通道时,输出也是三个通道,且R=G=B
。
transforms.RandomGrayscale(0.5),
transforms.ToTensor(),
transforms.Grayscale(num_output_channels=1)
该函数可以看做是RandomGrayscale()
的特例。输出通道数需要手动设置。当num_output_channels=1
输出是单通道。当num_output_channels=3
时,输出是三通道,且R=G=B
。
数据增强——仿射变换(Affine)
torchvision.transforms.RandomAffine(degrees, translate=None, scale=None, shear=None, resample=False, fillcolor=0)
随机进行仿射变换。仿射变换包括平移,旋转,缩放,翻转和错切,一般通过乘一个变换矩阵来实现,是一种二维线性变换。
degrees: 设置旋转角度。为元组或者数值。为数字a
时也表示旋转(-a,a)
间任意角度。
translate: 设置平移区间,为一个二元组(a,b)
。a
表示,在宽维度(一般是左右)方向移动的距离是在区间-width * a < dx < width * a
,b
表示,在高维度(一般是上下)方向移动的距离是在区间-hight * b < dy < hight * b
。移动的是高度或者宽度的若干倍。
scale: 缩放比例,该值是与原图的面积比。为二元组,设置上下限。
resample: 重采样,即插值,详见上述旋转方法。
fillcolor: 产生空白时的填充颜色,同上述旋转方法中的fill
。
shear: 错切角度设置。为单个数字或者元组。所谓错切,笔者理解的就是向x
或者y
方向倾斜原图成平行四边形(即为错),然后切出中间的部分(即为切),倾斜角度与往那边倾斜由shear
定。为单值a时,表示在x
方向错切,且往x
方向倾斜角度在区间(-a,a)
间随机取值。为二元组(a,b)
时,表示往x
方向倾斜角度在区间(-a,a)
间随机取值,往y
方向倾斜角度在区间(-b,b)
间随机取值。为四元组(a,b,c,d)
时,表示往x
方向倾斜角度在区间(a,b)
间随机取值,往y
方向倾斜角度在区间(c,d)
间随机取值。
transforms.RandomAffine(degrees=60),
transforms.ToTensor(),
transforms.RandomAffine(degrees=0, translate=(0.2, 0.5)),
transforms.ToTensor(),
transforms.RandomAffine(degrees=0, scale=(0.5, 1.5), fillcolor=(255, 0, 255)),
transforms.ToTensor(),
transforms.RandomAffine(degrees=0, shear=40, fillcolor=(0, 255, 255)),
transforms.ToTensor(),
数据增强——擦除(Erasing)
torchvision.transforms.RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace=False)
随机遮挡。实际应用中,取得的图片,目标可能被部分遮挡。这样会降低模型判断的准确率。如果在模型训练时,就用随机遮挡来增强训练集,那么最后训练出来的模型对于部分遮挡的目标就会比较鲁棒。p
是执行遮挡操作的概率。
scale: 遮挡区域面积与原图面积的比值,区间内随机选取。根据目标的大小,设置合适的值区间,不要太大了避免将目标盖住。为二元组,表上下限。
ratio: 遮挡区域的长宽比,在区间内随机选取。为二元组,表上下限。
value: 遮挡区域的像素值。大致同上述旋转方法中的fill
。不过当值为某字符串时,是填随机值。
注:该方法输入是Tessor
,故在此之前需要进行ToTensor
操作,否则会报错。
transforms.ToTensor(),
transforms.RandomErasing(value=(100, 130, 110)),
transforms.ToTensor(),
transforms.RandomErasing(scale=(0.1, 0.15), value='xyz', ratio=(1, 1)),
参考
https://pytorch.org/docs/stable/torchvision/transforms.html
https://ai.deepshare.net/detail/p_5df0ad9a09d37_qYqVmt85/6