torchvision中Transform的normalize参数含义, 自己计算mean和std,可视化后的情况,其他必要的数据增强方式


一下是一个常用的Normalize的代码

normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                     std=[0.229, 0.224, 0.225])
  • mean就是均值,
  • std是标准差(Standard Deviation),标准差也被称为标准偏差,标准差(Standard Deviation)描述各数据偏离平均数的距离(离均差)的平均数,它是离差平方和平均后的方根,用σ表示。标准差是方差的算术平方根。标准差能反映一个数据集的离散程度,标准偏差越小,这些值偏离平均值就越少,反之亦然。标准偏差的大小可通过标准偏差与平均值的倍率关系来衡量。平均数相同的两个数据集,标准差未必相同。
    在这里插入图片描述
    在这里插入图片描述

注意:

  • 总体标准偏差:针对总体数据的偏差,所以要平均1/N ;
  • 样本标准偏差,也称实验标准偏差:针对从总体抽样,利用样本来计算总体偏差,为了使算出的值与总体水平更接近,就必须将算出的标准偏差的值适度放大,1/(N-1)

transforms的源码中解释:

input[channel] = (input[channel] - mean[channel]) / std[channel]

假设:

  • 你数据的范围是图片的数据范围四[0,1],
  • 如果mean = [0.485, 0.456, 0.406],std = [0.229, 0.224, 0.225],
  • 根据上述式子计算
    (0−0.485)/0.229=−2.118

(1−0.485)/0.229=2.248

mean = [0.5, 0.5, 0.5]
std = [0.5, 0.5, 0.5]
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean, std), 
    transforms.ToPILImage() # 这里是为了可视化,故将其再转为 PIL,以下同理
])

new_img = transform(img)
new_img

在这里插入图片描述

疑问1:

在此页面(https://pytorch.org/docs/stable/torchvision/models.html)中,它说:“所有经过预训练的模型都希望输入图像以相同的方式归一化,即3通道RGB的迷你批处理形状为(3 x H x W)的图像,其中H和W至少应为224。图像必须加载到[0,1]的范围内,然后使用均值= [0.485,0.456, 0.406]和std = [0.229,0.224,0.225]”。

标准化的通常平均值和标准差不应该是[0.5、0.5、0.5]和[0.5、0.5、0.5]吗?为什么要设置这么奇怪的值?

使用Imagenet的均值和标准差是一种常见的做法。 它们是根据数百万张图像计算得出的。 如果要在自己的数据集上从头开始训练,则可以计算新的均值和标准差。 否则,建议使用Imagenet预设模型及其平均值和标准差。

是否使用ImageNet的均值和stddev取决于您的数据。 假设您的数据是“自然场景”†(人,建筑物,动物,变化的照明/角度/背景等)的普通照片,并假设您的数据集与ImageNet的偏向相同(就类平衡而言), 然后可以使用ImageNet的场景统计数据进行标准化。 如果照片以某种方式是“特殊的”(滤色,调整对比度,不常见的照明等)或“非自然的主题”(医学图像,卫星图像,手绘图等),那么我建议正确地对数据集进行规范化 在进行模型训练之前!*

计算你的数据集的mean和std

import os
import torch
from torchvision import datasets, transforms
from torch.utils.data.dataset import Dataset
from tqdm.notebook import tqdm
from time import time

N_CHANNELS = 1

dataset = datasets.MNIST("data", download=True,
                 train=True, transform=transforms.ToTensor())
full_loader = torch.utils.data.DataLoader(dataset, shuffle=False, num_workers=os.cpu_count())

before = time()
mean = torch.zeros(1)
std = torch.zeros(1)
print('==> Computing mean and std..')
for inputs, _labels in tqdm(full_loader):
    for i in range(N_CHANNELS):
        mean[i] += inputs[:,i,:,:].mean()
        std[i] += inputs[:,i,:,:].std()
mean.div_(len(dataset))
std.div_(len(dataset))
print(mean, std)

print("time elapsed: ", time()-before)

疑问2 ,如何得到[0-1]的图像

  • 1、ToTensor中就有转到0-1之间了。toTensor源码:uint8类型的图像会自动除以255,划到0-1之间,但是float32后就不会归到0-1之间。可以 转换 PIL Image or numpy.ndarray to tensor。

( Converts a PIL Image or numpy.ndarray (H x W x C) in the range [0, 255] to a torch.FloatTensor of shape (C x H x W) in the range [0.0, 1.0] )

       img = torch.from_numpy(pic.transpose((2, 0, 1)))
        # backward compatibility
        if isinstance(img, torch.ByteTensor):
            return img.float().div(255)
        else:
            return img

    if accimage is not None and isinstance(pic, accimage.Image):
        nppic = np.zeros([pic.channels, pic.height, pic.width], dtype=np.float32)
        pic.copyto(nppic)
        return torch.from_numpy(nppic)
# -*- coding:utf-8 -*-
# 归一化与反归一化
import time
import torch
from torchvision import transforms
import cv2
transform_val_list = [
    # transforms.Resize(size=(160, 160), interpolation=3),  # Image.BICUBIC
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]
trans_compose = transforms.Compose(transform_val_list)

if __name__ == '__main__':
    std= [0.229, 0.224, 0.225]
    mean=[0.485, 0.456, 0.406]
    path="d:/2.jpg"
    data=cv2.imread(path)
    
    t1 = time.time()
    # 归一化
    x = trans_compose(data)
    
    # 反归一化
    x[0]=x[0]*std[0]+mean[0]
    x[1]=x[1]*std[1]+mean[1]
    x[2]=x[2].mul(std[2])+mean[2]

    img = x.mul(255).byte()
    img = img.numpy().transpose((1, 2, 0))#  #将 0轴 和 2轴 交换,然后0轴和1轴交换
    # torch.set_num_threads(3)
    # img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
    cv2.imshow("sdf", img)
    cv2.waitKeyEx()

所以我们常常在代码中看到normallize在ToTensor之后

torchvision.transforms包,我们可以用transforms进行以下操作:

PIL.Image/numpy.ndarray与Tensor的相互转化;归一化;对PIL.Image进行裁剪、缩放等操作。

通常,在使用torchvision.transforms,我们通常使用transforms.Compose将transforms组合在一起。

self.transforms = T.Compose([
                   T.Scale(224),
                   T.CenterCrop(224),
                   T.ToTensor(),
                   normalize
               ]

PIL.Image/numpy.ndarray转化为Tensor,常常用在训练模型阶段的数据读取,而Tensor转化为PIL.Image/numpy.ndarray则用在验证模型阶段的数据输出。

  • 2、也可以是加载数据集的时候就已经将图片转换为[0, 1],例如imageNet数据集就是在加载ImageNet的数据的时候就转换成[0,1]。

疑问3:PIL.Image/numpy.ndarray与Tensor的相互转换

PIL.Image/numpy.ndarray转化为Tensor,常常用在训练模型阶段的数据读取,而Tensor转化为PIL.Image/numpy.ndarray则用在验证模型阶段的数据输出。

我们可以使用 transforms.ToTensor() 将 PIL.Image/numpy.ndarray 数据进转化为torch.FloadTensor,并归一化到[0, 1.0]:

取值范围为[0, 255]的PIL.Image,转换成形状为[C, H, W],取值范围是[0, 1.0]的torch.FloadTensor;
形状为[H, W, C]的numpy.ndarray,转换成形状为[C, H, W],取值范围是[0, 1.0]的torch.FloadTensor。 

而transforms.ToPILImage则是将Tensor转化为PIL.Image。如果,我们要将Tensor转化为numpy,只需要使用 .numpy() 即可。如下:

img_path = "./data/img_37.jpg"

# transforms.ToTensor()
transform1 = transforms.Compose([
    transforms.ToTensor(), # range [0, 255] -> [0.0,1.0]
    ]
)

##numpy.ndarray
img = cv2.imread(img_path)# 读取图像
img1 = transform1(img) # 归一化到 [0.0,1.0]
print("img1 = ",img1)
# 转化为numpy.ndarray并显示
img_1 = img1.numpy()*255
img_1 = img_1.astype('uint8')
img_1 = np.transpose(img_1, (1,2,0))
cv2.imshow('img_1', img_1)
cv2.waitKey()

##PIL
img = Image.open(img_path).convert('RGB') # 读取图像
img2 = transform1(img) # 归一化到 [0.0,1.0]
print("img2 = ",img2)
#转化为PILImage并显示
img_2 = transforms.ToPILImage()(img2).convert('RGB')
print("img_2 = ",img_2)
img_2.show() 

疑问4 :PIL.Image的缩放裁剪等操作

此外,transforms还提供了裁剪,缩放等操作,以便进行数据增强。下面就看一个随机裁剪的例子,这个例子中,仍然使用 Compose 将 transforms 组合在一起,如下:

# transforms.RandomCrop()
transform4 = transforms.Compose([
    transforms.ToTensor(), 
    transforms.ToPILImage(),
    transforms.RandomCrop((300,300)),
    ]
)

img = Image.open(img_path).convert('RGB')
img3 = transform4(img)
img3.show()

其他必要的数据增强方式

这两篇总结的挺好:
https://zhuanlan.zhihu.com/p/88530492
https://zhuanlan.zhihu.com/p/91477545

1、对 PIL 数据操作的变换

ToTensor : 将 PIL Image 或者 numpy.ndarray 格式的数据转换成 tensor

Resize : Resize the input PIL Image to the given size. 参数: size: 一个值的话,高和宽共享,否则对应是 (h, w)interpolation: 插值方式 默认 PIL.Image.BILINEAR

CenterCrop: Crops the given PIL Image at the center. 裁剪一定 size 的图片,以图片的中心往外. 参数: size: 一个值的话,高和宽共享,否则对应是 (h, w),若是该值超过原始图片尺寸,则外围用 0 填充

Pad : Pad the given PIL Image on all sides with the given “pad” value. 填充图片的外部轮廓 PIL 数据格式. 参数: padding:填充的宽度,可以是一个 值、或者元组,分别对应 4 个边fill:填充的值,可以是一个值(所有通道都用该值填充),或者一个 3 元组(RGB 三通道) 当 padding_mode=constant 起作用padding_mode:填充的模式:constant, edge(填充值为边缘), reflect (从边缘往内一个像素开始做镜像) or symmetric(从边缘做镜像).

Lambda : 根据用户自定义的方式进行变换

lambd = lambda x: TF.rotate(x, 100)
transform = transforms.Compose([
    transforms.Lambda(lambd)
])

new_img = transform(img)
new_img

在这里插入图片描述RandomApply : 给定一定概率从一组 transformations 应用
RandomChoice: Apply single transformation randomly picked from a list 随机从一组变换中选择一个
RandomOrder : Apply a list of transformations in a random order
RandomCrop: Crop the given PIL Image at a random location. 随机进行裁剪
RandomHorizontalFlip & RandomVerticalFlip: Horizontally/Vertically flip the given PIL Image randomly with a given probability. 按一定概率进行水平 / 竖直翻转
RandomResizedCrop: Crop the given PIL Image to random size and aspect ratio. 裁剪给定的 PIL 图像到随机的尺寸和长宽比。

transform = [transforms.Pad(100, fill=(0, 255, 255)), transforms.CenterCrop((100, 300))]
transform = transforms.Compose([
    transforms.RandomChoice(transform)
])

transform = [transforms.Pad(100, fill=(0, 255, 255)), transforms.CenterCrop((100, 300))]
transform = transforms.Compose([
    transforms.RandomChoice(transform)
])

transform = [transforms.Pad(100, fill=(0, 255, 255)), transforms.CenterCrop((50, 50))]
transform = transforms.Compose([
    transforms.RandomOrder(transform)
])

new_img = transform(img)
new_img

FiveCrop : 将给定的 PIL 图像裁剪成四个角和中间的裁剪
TenCrop: 裁剪一张图片的 4 个角以及中间得到指定大小的图片,并且进行水平翻转 / 竖直翻转 共 10 张

UNIT_SIZE = 200 # 每张图片的宽度是固定的
size = (100, UNIT_SIZE)
transform = transforms.Compose([
    transforms.FiveCrop(size)
])

new_img = transform(img)
delta = 20  # 偏移量,几个图片间隔看起来比较明显
new_img_2 = Image.new("RGB", (UNIT_SIZE*5+delta, 100))
top_right = 0
for im in new_img:
    new_img_2.paste(im, (top_right, 0)) # 将image复制到target的指定位置中
    top_right += UNIT_SIZE + int(delta/5) # 左上角的坐标,因为是横向的图片,所以只需要 x 轴的值变化就行

new_img_2

在这里插入图片描述
LinearTransformation: 白化变换
ColorJitter: Randomly change the brightness, contrast and saturation of an image. 随机改变图像的亮度、对比度和饱和度
RandomRotation: 一定角度旋转图像
RandomAffine: 保持图像中心不变的随机仿射变换,可以进行随心所欲的变化
Grayscale: 转换图像灰度。
RandomGrayscale: Randomly convert image to grayscale with a probability of p (default 0.1). 以一定的概率对图像进行灰度化,转换后的图片还是 3 通道的
RandomPerspective: 对给定的 PIL 图像以给定的概率随机进行透视变换。
transforms.Compose 函数是将几个变化整合在一起的,变换是有顺序的,需要注意是变换函数是对 PIL 数据格式进行还是 Torch 数据格式进行变换

参考:

https://blog.csdn.net/qq_18649781/article/details/92120175
https://stackoverflow.com/questions/58151507/why-pytorch-officially-use-mean-0-485-0-456-0-406-and-std-0-229-0-224-0-2
https://www.huaweicloud.com/articles/134789bff5798a5908e295470b32f1aa.html
https://zhuanlan.zhihu.com/p/27382990
https://zhuanlan.zhihu.com/p/88530492

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hali_Botebie

文中错误请不吝指正!!!!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值