图像预处理:transforms和PIL小结

  2019-1-28 


色图真好玩.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(全卷积网络)就是这样


且听风吟