图片和数据之间的转换,有两个要点,一个是size的transpose,一个是归一化和反归一化。
torch里数据的格式是(数量

×

\times

×通道数

×

\times

×

×

\times

×宽),而初始图片读取得到的格式是(长

×

\times

×

×

\times

×通道数),因此在转变时得用 transpose 转置一下。归一化和反归一化,前者是为了把图片数据转换到神经网络友好的数值范围,后者是为了还原到人眼友好的数值范围。

1 将图片数值化

主要包括图片的读取和转置操作。图片的读取有更好的方式,请参阅:
https://blog.csdn.net/weixin_42468475/article/details/127536163

1.1 最便捷的读取图片方式

from torchvision.io import image

img=image.read_image('./test2.png')
print(type(img))
print(img.shape)

1.2 比较麻烦的读取方式:先用PIL读取,然后转成tensor

import numpy as np
from PIL import Image
import torchvision
import torchvision.transforms as transforms


path = './imgs/input/img1.jpg'
image = Image.open(path).convert("RGB")

# 查看shape
print(np.array(image).shape)
# 得到 (251, 201, 3),如果要转化成神经网络可读的格式,我们要转化成 (3, 251, 201) 
# ToTensor() 自带 transpose 的操作 ,很方便
transform_list = [transforms.ToTensor()]
transformer = transforms.Compose(transform_list)
print(transformer(image).shape)
# 得到 torch.Size([3, 251, 201])

当然,也可以进行手动进行转置

import numpy as np
from PIL import Image
import torchvision
import torchvision.transforms as transforms


path = './imgs/input/img1.jpg'
image = Image.open(path).convert("RGB")

# 查看shape
data = np.array(image)
print(data.shape)
# 得到 (251, 201, 3),如果要转化成神经网络可读的格式,我们要转化成 (3, 251, 201) 
# 将第三个维度放到最前面
data = data.transpose(2,0,1)
data = torch.tensor(data)
print(data.shape)
# 得到 torch.Size([3, 251, 201])

如何把 tensor 转换成 Image?:

torchvision.transforms.functional.to_pil_image(tensor)

2 归一化和反归一化

为了神经网络更快收敛,我们需要把读取图片得到的 tensor 进行归一化,处理后的图片如果

如果不进行反归一化到 [0,1],出现了负值,就会变成下面这样,(即经过归一化后的图像,满足均值为0方差为1):
在这里插入图片描述

总结一下他们的关系:

读取的图片得到数值(范围大小在 [0,255] 或 [0,1] )

=

=

=

=

=

=

===归一化===》

====== 神经网络适合处理的数值(均值为0,方差为1)

=

=

=

=

=

=

===反归一化===》

====== 原图得到数值(范围大小在 [0,255] 或 [0,1] )

反归一化是为了还原原来人眼可以识别的图。

归一化

归一化的目的:
经过这样处理后的数据符合标准正态分布,即均值为0,标准差为1。使模型更容易收敛。并非是归于[-1,1]或是[0,1]!!!

归一化需要计算 meanstd,我们先来看一下他的格式,即(数量

×

\times

×通道数

×

\times

×

×

\times

×宽):
【PIL】torch.tensor和为PIL.Image的相互转换;归一化和反归一化-小白菜博客
显然有负值,那么接下来我们开始计算meanstd吧:

nb_samples = 0
#创建3维的空列表
channel_mean = torch.zeros(3)
channel_std = torch.zeros(3)
print(image.shape)
N, C, H, W = image.shape[:4]
image = image.view(N, C, -1)     #将w,h维度的数据展平,为batch,channel,data,然后对三个维度上的数分别求和和标准差
print(image.shape)
#展平后,w,h属于第二维度,对他们求平均,sum(0)为将同一纬度的数据累加
channel_mean += image.mean(2).sum(0)  
#展平后,w,h属于第二维度,对他们求标准差,sum(0)为将同一纬度的数据累加
channel_std += image.std(2).sum(0)
#获取所有batch的数据,这里为1
nb_samples += N
#获取同一batch的均值和标准差
channel_mean /= nb_samples
channel_std /= nb_samples
print(channel_mean, channel_std)

然后利用transforms.Normalize 进行转换:

# 归一化生成器
normalizer = transforms.Normalize(mean=channel_mean, std=channel_std)

# 归一化得到可处理的值
data = normalizer(image)

反归一化

根据归一化计算得到的mean和std,我们可以反推出反归一化的 mean 和 std,从而利用 transforms.Normalize 进行转换,计算方法如下:

d

e

M

E

A

N

=

[

m

e

a

n

/

s

t

d

f

o

r

m

e

a

n

,

s

t

d

i

n

z

i

p

(

M

E

A

N

,

S

T

D

)

]

de_MEAN = [-mean/std for mean, std in zip(MEAN, STD)]

deMEAN=[mean/stdformean,stdinzip(MEAN,STD)]

d

e

S

T

D

=

[

1

/

s

t

d

f

o

r

s

t

d

i

n

S

T

D

]

de_STD = [1/std for std in STD]

deSTD=[1/stdforstdinSTD]
MEAN是指归一化时计算出来的均值,de_MEAN是计算出来反归一化的均值,后面需要用。

完整的流程是这样的:

# 定义一个image图像,torch.Size([1, 3, 319, 256])
image = torch.rand([1,3,319,256])

# 计算原图的 mean 和std
nb_samples = 0
#创建3维的空列表
channel_mean = torch.zeros(3)
channel_std = torch.zeros(3)
print(image.shape)
N, C, H, W = image.shape[:4]
#将w,h维度的数据展平,为batch,channel,data,然后对三个维度上的数分别求和和标准差
image = image.view(N, C, -1)     
print(image.shape)
#展平后,w,h属于第二维度,对他们求平均,sum(0)为将同一纬度的数据累加
channel_mean += image.mean(2).sum(0)  
#展平后,w,h属于第二维度,对他们求标准差,sum(0)为将同一纬度的数据累加
channel_std += image.std(2).sum(0)
#获取所有batch的数据,这里为1
nb_samples += N
#获取同一batch的均值和标准差
channel_mean /= nb_samples
channel_std /= nb_samples
print(channel_mean, channel_std)


# 这是归一化的 mean 和std 
channel_mean = torch.tensor([-0.5321, -0.8102, -0.5532])
channel_std = torch.tensor([1.2582, 1.0009, 0.9211])
# 这是反归一化的 mean 和std 
MEAN = [-mean/std for mean, std in zip(channel_mean, channel_std)]
STD = [1/std for std in channel_std]

# 归一化和反归一化生成器
normalizer = transforms.Normalize(mean=channel_mean, std=channel_std)
denormalizer = transforms.Normalize(mean=MEAN, std=STD)

# 归一化得到可处理的值
data = normalizer(image)
# 反归一化得到原图
image2 = denormalizer(data)

# image ≈ image2 因为浮点数计算有误差,
# 所以不会完全一样,但是问题不大~