色图真好玩.jpg
transforms
Ⅰ. transforms.ToTensor()
①将(H,W,C)输入的numpy array或img转换成(C,H,W)格式
通道的具体顺序与cv2读的还是PIL.Image读的图片有关系
cv2:(B,G,R)
PIL.Image:(R, G, B)
②归一化,归一到$(0,1)$区间
Ⅱ. transforms.Normalize()
标准化。
即使输入的每个值分布到$(-1,1)$中
公式为$Normalize = \frac{(x-mean)}{std}$(std为标准差,mean为均值)
标准差公式$std = \sigma = \sqrt{\frac{1}{N} \Sigma^N_{i=1}(x_i-μ)^2 }$
注意实际是img里的每个像素(每个值)都要经过此公式运算
通过标准化后可以让数据服从标准正态分布
一般来说参数为 transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
这里的三个维度是因为输入的$x$为三维的,如果$x$是灰度图,那就是一维
$(0.5,0.5,0.5)$即为三个维度(分别对应RGB三个通道)的均值和标准差。这里默认为该值,如果你能够计算出你的数据集的均值和标准差,那么用计算出来的值更好。(如imagenet数据集提供方就给出了该数据集的均值和标准差)
以上两个函数通常是连着用的
Ⅲ.transforms.Lambda
用法:transforms.Lambda(lambda img:func(img,))
这里的func
可以是自己定义的函数,也可以直接调用表达式比如transforms.Lambda(lambda img:img)
(什么都没处理,返回了自己本身)
在transforms库中存在很多官方给的预处理工具,但是如果需要自己定义,比如截取图像指定区域,则需要自己用Lambda函数封装一个处理函数
eg:自己定义一个截取图片的crop函数
from torchvision import transforms
def __crop(img, pos, size):
"""
:param img: 输入的图像
:param pos: 图像截取的位置,类型为元组,包含(x, y)
:param size: 图像截取的大小
:return: 返回截取后的图像
"""
ow, oh = img.size
x1, y1 = pos
tw = th = size
# 有足够的大小截取
# img.crop坐标表示 (left, upper, right, lower)
if (ow > tw or oh > th):
return img.crop((x1, y1, x1+tw, y1+th))
return img
# 然后使用transforms.Lambda封装其为transforms策略
# 然后定义新的transforms为
normalize = transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
data_transforms = transforms.Compose([
#注意这里的用法
transforms.Lambda(lambda img: __crop(img, (5,5), 224)),
transforms.RandomHorizontalFlip(), # 随机水平翻转给定的PIL.Image,翻转概率为0.5
transforms.ToTensor(), # 转成Tensor格式,大小范围为[0,1]
normalize
])
Ⅳ. 逆过程
transforms.Normalize()
的逆过程即原公式求反函数
原公式$Normalize = \frac{(x-(-mean))}{std}$的反函数即为
$x = Normalize*std+mean$
注意是$x$中的每个元素都要经过此逆运算,可以使用numpy array的矩阵批量乘法
如果$std$和$mean$三个维度(RGB情况)是不一样的,那就要分别在三个维度上单独做逆运算
transforms.ToTensor()
的逆过程为transforms.ToPILImage()
(这个类里面就包含各种处理了包括逆归一化)
这里也可以直接用plt.imshow(tensor)
来显示图像
这里tensor要*255,因为.ToTensor也有归一化的功能
注:另外transforms组件可以单独拿出来用,不一定要compose组合
但有个问题需要注意!用法是
transforms.ToPILImage()(tensor)
因为transforms里的组件都是对象!不是方法,得建立对象之后再调用对象的方法!
PIL
Ⅰ. 打开图片
使用PIL.Image.open只会调用本地图片浏览器打开图片
所以要用matplotlib.pyplot.imshow()
来打开PIL导入的图片(一般
import matplotlib.pyplot as plt
)注意先要将PIL.Image导入的图片转化为narray格式 (np.array)(导入进来的时候是PIL.Image.Image对象)
注意
open
: 打开并识别所提供的图像文件。不过,使用这函数的时候,真正的图像数据在你进行数据处理之前并没有被读取出来。可使用load
函数进行强制加载。 mode 参数可以省略,但它只能是 “r” 值。如果想要迭代打开多张图片每张都显示,则必须
plt.imshow(img) #多一句show plt.show()
也可以使用
PIL.Image.open
加载图片from PIL import Image img = Image.open(iMagePath)
注意读取单个图片进入神经网络训练的时候
imga = torch.unsqueeze(imga, 0) imga = imga.cuda()
Ⅱ. plt.imshow()不能同时显示多张图片
如果想要显示多张,可以在每个plt.imshow()后面加一个plt.show()
Ⅲ. 图片处理部分
transform=transforms.Compose(
[transforms.Resize(256), #resize并不会改变长宽比例! 重要问题1
transforms.CenterCrop(256), #resize后要crop!
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
#然后可以用这个来处理单个PIL图片
img = transform(img)
#这样即应用了变换
#也可以用以下这个来处理数据集
trainset = torchvision.datasets.ImageFolder(train_dir, transform=transform)
Ⅳ. 应用图片数据预处理 transforms模块
生成的transforms直接作用在PIL上
# 比如这段定义了变换之后
transform=transforms.Compose(
[transforms.Resize(256),
transforms.CenterCrop(256),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
#如果是用loader来加载的话就是
trainset = torchvision.datasets.ImageFolder(train_dir, transform=transform)
#如果是直接应用到一个图片上的话就是
img = Image.open(picPath)
imga = transform(img)
Ⅴ. resize与toTensor的先后顺序
transform=transforms.Compose(
[transforms.Resize(256),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
# 这里面的变换是有顺序的!
# transforms.Resize()是图片变换,是作用在PIL.Images上的!所以一定要在ToTensor之前!
图片变换》ToTensor》归一化
transforms.Resize()
works on PIL.Images
, so just swap the order of your transformations and your code should run.
否则会报错TypeError: img should be PIL Image. Got
Ⅵ. resize和crop的区别
resize并不会改变长宽比例!是等比缩放的
crop是在图中挖出一部分(裁剪) 这个才会改变比例!
resize后要crop!
transforms.Resize(256)
transforms.CenterCrop(256)
crop 有很多种方式 待学习
crop和resize的作用范围都是PIL而不是tensor
ImageFolder
图片数据集定义
trainset = torchvision.datasets.ImageFolder(root=train_dir)
train_dir中要有子文件夹! 按照子文件夹读取标签!
imageFolder打标签的问题:和文件夹的顺序有关
【按照各子文件夹的顺序打上0.1.2…. 且是刷新后的顺序! 所以最好是直接0,1,2,3给子文件夹命名,自己清楚里面代表的是啥就是了!】
所以如果说两个文件夹内的子文件夹都是一样的话,相同分类打的标签就是一样的
ImageFolder两个重要属性: ①class_to_idx ②classes
关于输入网络的图片尺寸
输入图片尺寸必须一样是因为在出特征提取层,进入全连接层的时候,全连接层是固定的,所以如果只是要提取出特征的话,并不要求输入图片尺寸一样!
有个很好玩的FCN(全卷积网络)就是这样