TORCHVISION.TRANSFORMS的图像预处理

TORCHVISION.TRANSFORMS

本文相关代码下载:https://github.com/hanlinshi/test_TORCHVISION.TRANSFORMS
TORCHVISION.TRANSFORMS变换是常见的图像变换。它们可以用Compose链接在一起。此外,还有torchvision.transforms.functional模块。函数转换提供对转换的细粒度控制。如果您必须构建一个更复杂的转换管道(例如,在分段任务的情况下),这很有用。

所有变换都接受PIL图像、Tensor图像或一批Tensor图像作为输入。Tensor图像是一种(C,H,W)形状的tensor,其中C是多个通道,H和W是图像的高度和宽度。一批Tensor图像是(B,C,H,W)形状的tensor,其中B是批中的若干图像。对一批Tensor图像应用的确定性或随机变换对该批的所有图像进行相同的变换。

从v0.8.0版后,所有随机转换都使用torch默认随机生成器来采样随机参数。这是一个破坏向后兼容性的更改,用户应将随机状态设置为:

# Previous versions
# import random
# random.seed(12)
# Now
import torch
torch.manual_seed(17)

请记住,torch随机生成器和Python随机生成器的相同种子不会产生相同的结果。

可脚本化转换
为了编写转换脚本,请使用torch.nn.Sequential,而不是Compose。

transforms = torch.nn.Sequential(
    transforms.CenterCrop(10),
    transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
)
scripted_transforms = torch.jit.script(transforms)

确保只使用可编写脚本转换,即torch.Tensor,不需要lambda函数PIL.Image

任何自定义转换要与torch.jit.script一起使用,它们应该源于torch.nn.模块.

一、变换的组合

1 变换的组合:transforms.Compose

torchvision.transforms.Compose(transforms) 主要作用是串联多个图片变换的操作。
transforms是一个Transform对象列表,Transform对象对图像进行操作,如裁剪、翻转等变换操作。
Compose会将transforms列表遍历操作一次。

transforms.Compose([
     transforms.CenterCrop(10),
     transforms.ToTensor(),
 ])

下图是要操作的原图
原图

二、PIL图象与torch.*Tensor的都可以变换

* 裁剪–Crop

1 中心裁剪:transforms.CenterCrop

CLASS torchvision.transforms.CenterCrop(size)
中心位置裁剪给定图像。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数.

参数

  • size(sequence 或 int)---- 裁剪后的大小。如果size是一个int而不是像(h,w)这样的序列,则会进行一个方形裁剪(size,size)。如果提供一个长度为1的元组size=(h,)或列表size=[h],它将被解释为(size[0],size[0])
    –也就是四种写法: size=(h,w) ; size=h ; size=(h,) ; size=[h].

返回
剪切后的图像
返回类型:PIL 图像或Tensor

下图是四种写法中心裁剪后的图片

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/img.jpeg') # 原图大小695,391
sizes = [224, (224, 224), (224,), [224]]
for i, size in enumerate(sizes):
     transform = transforms.CenterCrop(size)
     imgt = transform(img)
     imgt.save('./data/center_crop_' + str(i) + '.jpg')

在这里插入图片描述

2 填充后随机裁剪:transforms.RandomCrop

CLASS torchvision.transforms.RandomCrop(size, padding=None, pad_if_needed=False, fill=0, padding_mode='constant')
随机位置裁剪给定图像。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数。

参数

  • size(sequence 或 int)---- 裁剪后的大小。如果size是一个int而不是像(h,w)这样的序列,则会进行一个方形裁剪(size,size)。如果提供一个长度为1的元组size=(h,)或列表size=[h],它将被解释为(size[0],size[0]).
    – 也就是四种写法: size=(h,w) ; size=h ; size=(h,); size=[h].
  • padding(int 或 sequence,可选)---- 图像每个边填充长度(单位是像素)。 默认值为“无”。如果提供了一个int,则用于填充所有边界。如果提供长度为2的元组,则分别在左/右和上/下填充。如果提供了长度为4的元组,则这是分别用于左边框、上边框、右边框和下边框的填充。在torchscript模式下,由于不支持单个int,请使用长度为1的元组或列表:[padding,]
    – 也就是七种写法: padding=p; padding=(p,); padding=(p1,p2); padding=(p1,p2,p3,p4); padding=[p]; padding=[p1,p2]; padding=[p1,p2,p3,p4]
    – 这里的p是指上下左右边界用fill的值填充p个像素的长度。
  • pad_if_needed(boolean)---- 如果图像小于所需大小,它将填充图像,以避免引发异常。由于裁剪是在填充之后完成的,所以填充看起来像是在随机偏移处完成的。
    如果pad_if_needed为False(默认值), 图像padding后的大小 < size,就会报错;如果pad_if_needed为True,图像padding后的大小 < size,则会继续padding到size大小后,再裁剪,就不会报错了
  • fill (int 或 tuple) ---- 常量填充的像素填充值。 默认值为0。如果一个元组的长度为3,则分别用于填充R、G、B通道。此值仅在填充模式为常量时使用.
    – 也就是两种写法: fill=f; fill=(f1,f2,f3).
  • padding_mode (str) ---- 填充类型. 应为:constant常数、edge边、reflect反射或symmetric对称。默认值为常量。张量输入尚不支持模式对称。
    - constant 常数,用常数值进行填充,这个常数值由fill指定。
    - edge 边, 用图像最边缘的值进行填充。
    - reflect 反射,用图像的反射进行填充(不重复最边缘的值),例如,使用反射模型在[1, 2, 3, 4]的两边填充2个元素的结果为[3, 2, 1, 2, 3, 4, 3, 2]
    - symmetric 对称,用图像的对称进行填充(重复最边缘的值),例如,使用对称模型在[1, 2, 3, 4]的两边填充2个元素的结果为[2, 1, 1, 2, 3, 4, 4, 3]
    如果使用了edge,reflect,symmetric这三种模式,fill的值就会被无视
    从结果上看reflect和symmetric这两种模式没有什么大的区别,不过我私以为这可能是设计上的问题,应该有一种模式是重复,而不是镜像方式。(不过也可能是我的实验有问题)
    图像padding后的大小 < size时,选择pad_if_needed为True和自己padding图像得到的结果还是有区别的,没有去研究系统是怎么自动padding的。感兴趣的可以研究试一试。

返回
剪切后的图像
返回类型:PIL 图像或Tensor

下图是随机裁剪四种size写法得到图片,注意因为随机所以每次得到的图片都可能不一样。

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/img.jpeg')  # 原图大小695,391
sizes = [224, (224, 224), (224,) ,[224]] 
for i, size in enumerate(sizes):
    transform = transforms.RandomCrop(size)
    imgt = transform(img)
    imgt.save('./data/random_crop_s' +str(i)+ '.jpeg')

在这里插入图片描述
下图是默认值填充后裁剪

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/img.jpeg')  # 原图大小695,391
size = 400
paddings = [50,(50,),(50, 100),(50, 100, 150, 200),
            [50],[50, 100],[50, 100, 150, 200]]
             # padding = 4 (pad_if_needed=False会报错,True不会);
for i, padding in enumerate(paddings):
    transform = transforms.RandomCrop(size,
                padding=padding,pad_if_needed=False)
    imgt = transform(img)
    imgt.save('./data/random_crop_p' + str(i)+'.jpeg')

在这里插入图片描述

下图是指定颜色填充裁剪,为了显示白色填充,所以背景颜色涂成黑色。

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/img.jpeg')  # 原图大小695,391
size = 500
padding = 100
fills = [255,(0,255,255)]
for i, fill in enumerate(fills):
    transform = transforms.RandomCrop(size,
             padding=padding, fill=fill)
    imgt = transform(img)
    imgt.save('./data/random_crop_f'+str(i)+'.jpeg')

在这里插入图片描述

下图是制定模式填充。看出其实reflect填充和symmetric填充没有什么区别.

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg')  # 图片大小是 256*256   
size = 400
padding = 100
fill = (255,0,255)  
padding_modes = ["constant","edge" ,"reflect" ,"symmetric"]
for i, padding_mode in enumerate(padding_modes):
    transform = transforms.RandomCrop(size, padding=padding,
           pad_if_needed=True, padding_mode=padding_mode)
    imgt = transform(img)
    imgt.save('./data/random_crop_m'+str(i)+'.jpeg')

在这里插入图片描述

下图是裁剪出更大的size进行观察
在这里插入图片描述

3 (已弃用)缩放后随机裁剪: transforms.RandomSizedCrop

CLASS torchvision.transforms.RandomSizedCrop(*args, **kwargs)

-注意:此转换被弃用,取而代之的是RandomResizedCrop。

4 缩放后随机裁剪: transforms.RandomResizedCrop

CLASStorchvision.transforms.RandomResizedCrop(size, scale=(0.08, 1.0), ratio=(0.75, 1.3333333333333333), interpolation=2)
将给定图像裁剪为随机大小和长宽比。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数。
先将原始图片重置为原始尺寸的随机倍数(默认值:从0.08到1.0)以及随机调整长宽比(默认值:从3/4到4/3),然后进行裁剪。最终被调整到给定的大小。这通常用于训练初始网络。

参数

  • size(sequence 或 int)---- 裁剪后的大小。如果size是一个int而不是像(h,w)这样的序列,则会进行一个方形裁剪(size,size)。如果提供一个长度为1的元组size=(h,)或列表size=[h],它将被解释为(size[0],size[0])
    – 也就是四种写法: size=(h,w) ; size=h ; size=(h,); size=[h].
  • scale (python:float的元祖) ---- 原始尺寸缩放比率取值。
  • ratio (python:float的元祖) ---- 缩放后的长宽比范围取值。
  • interpolation (int) ---- filters(过滤器)定义的所需插值枚举。
    默认为PIL.Image.BILINEAR. (双线性插值)。如果输入是Tensor对象,仅支持PIL.Image.NEAREST, PIL.Image.BILINEAR 以及 PIL.Image.BICUBIC
    – 取值0到5六个分别是(实验结果猜测的,以后有时间再研究这几个插值):PIL.Image.NEAREST(0), PIL.Image.ANTIALIAS(1), PIL.Image.BILINEAR(2), PIL.Image.BICUBIC(3), PIL.Image.BOX(4), PIL.Image.HAMMING(5)

返回
随机剪裁和重置大小的图像
返回类型:PIL图像或Tenso

默认模式的随机缩放裁剪。

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 图片大小是 256*256
sizes = [224,(224,),[224]]  
interpolation = 0
for i,size in enumerate(sizes):
    transform = transforms.RandomResizedCrop(size, 
                  interpolation=interpolation)
    imgt = transform(img)
    imgt.save('./data/random_resize_crop_s'+str(i)+'.jpeg')

在这里插入图片描述

下图是扩大一倍的填充,看不出什么,其实还是有差别的。因为这里缩小了所以看不出来。可以自己实验。

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 图片大小是 256*256
size = 512  
interpolations = [0, 1, 2, 3, 4, 5]
scale = (2.0, 2.0)
ratio = (1.0, 1.0)
for i,interpolation in enumerate(interpolations):
    transform = transforms.RandomResizedCrop(size, scale=scale,
              ratio=ratio, interpolation=interpolation)
    imgt = transform(img)
    imgt.save('./data/random_resize_crop_i'+str(i)+'.jpeg')

在这里插入图片描述

5 五张裁剪:transforms.FiveCrop

CLASS torchvision.transforms.FiveCrop(size)
给定图像的四个角和中心裁剪。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数。

参数

  • size(sequence 或 int)---- 裁剪后的大小。如果size是一个int而不是像(h,w)这样的序列,则会进行一个方形裁剪(size,size)。如果提供一个长度为1的元组size=(h,)或列表size=[h],它将被解释为(size[0],size[0])
    – 也就是四种写法: size=(h,w) ; size=h ; size=(h,); size=[h].

返回
五张裁剪的图像
返回类型:PIL图像或Tenso

这个转换返回的是一组图片的元组(四个角和中心共五张图片)。因此经过这个转换后所得到的数据作为输入数据,和直接由数据集的返回得到的数据量不一样。假设从数据集中拿出batch数为8,经过这个变换以后数据量就为8*5=40了。
请参阅下面的示例,了解如何处理此问题。

  • 在数据预处理中
transform = Compose([
        FiveCrop(size), # 得到一个PIL图片数组
        Lambda(lambda crops: torch.stack([ToTensor()(crop) for crop in crops])) # 返回一个4D tensor
   ])
  • 在测试数据中
input, target = batch # input 是一个 5d tensor, target 是 2d
bs, ncrops, c, h, w = input.size()
result = model(input.view(-1, c, h, w)) # 融合 batch size 和 ncrops 变成4d
result_avg = result.view(bs, ncrops, -1).mean(1) # 拆结果

下图是一张图一次操作得到的五张结果。

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 图片大小是 256*256
size = 100  
transform = transforms.FiveCrop(size)
imgt = transform(img)
for i, imgt in enumerate(imgt):
   imgt.save('./data/five_crop_'+str(i)+'.jpeg')

在这里插入图片描述

6 十张裁剪: transforms.TenCrop

CLASS torchvision.transforms.TenCrop(size, vertical_flip=False)
给定图像的四个角和中心裁剪,以及这些裁剪的翻转(默认情况下使用水平翻转)图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数。

参数

  • size(sequence 或 int)---- 裁剪后的大小。如果size是一个int而不是像(h,w)这样的序列,则会进行一个方形裁剪(size,size)。如果提供一个长度为1的元组size=(h,)或列表size=[h],它将被解释为(size[0],size[0])
    – 也就是四种写法: size=(h,w) ; size=h ; size=(h,); size=[h].
  • vertical_flip (bool) ---- 使用垂直翻转代替水平翻转,默认False,不代替。

返回
十张裁剪的图像
返回类型:PIL图像或Tenso

这个转换返回的是一组图片的元组(四个角和中心以及他们的翻转共十张图片)。因此经过这个转换后所得到的数据作为输入数据,和直接由数据集的返回得到的数据量不一样。假设从数据集中拿出batch数为8,经过这个变换以后数据量就为8*10=80了。
请参阅下面的示例,了解如何处理此问题。

  • 在数据预处理中
transform = Compose([
        TenCrop(size), # 得到一个PIL图片数组
        Lambda(lambda crops: torch.stack([ToTensor()(crop) for crop in crops])) # 返回一个4D tensor
   ])
  • 在测试数据中
input, target = batch # input 是一个 5d tensor, target 是 2d
bs, ncrops, c, h, w = input.size()
result = model(input.view(-1, c, h, w)) # 融合 batch size 和 ncrops 变成4d
result_avg = result.view(bs, ncrops, -1).mean(1) # 拆结果

下图是一张图一次操作得到的十张结果。

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 图片大小是 256*256
size = 100  
transform = transforms.TenCrop(size)
imgts = transform(img)
for i, imgt in enumerate(imgts):
    imgt.save('./data/ten_crop_'+str(i)+'.jpeg'

在这里插入图片描述

* 翻转和旋转 – Flip and Rotation

1 依概率p水平翻转transforms.RandomHorizontalFlip

CLASS torchvision.transforms.RandomHorizontalFlip(p=0.5)
以给定的概率p, 水平翻转给定的图像。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数。

参数

  • p (float) –图像被翻转的概率。默认值为0.5
    –实验结论:p小于0的被视为0,p大于1的被视为1;

返回
水平翻转后的图像
返回类型:PIL图像或Tenso

下图左边是原图,右边是水平翻转后的原图

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 图片大小是 256*256
transform = transforms.RandomHorizontalFlip(p=1)
imgt = transform(img)
imgt.save('./data/horizontal_flip.jpeg')

在这里插入图片描述

2 依概率p垂直翻转transforms.RandomVerticalFlip

CLASS torchvision.transforms.RandomVerticalFlip(p=0.5)
以给定的概率p, 垂直翻转给定的图像。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数。

参数

  • p (float) ---- 图像被翻转的概率。默认值为0.5
    –实验结论:p小于0的被视为0,p大于1的被视为1;

返回
垂直翻转后的图像
返回类型:PIL图像或Tenso

下图左边是原图,右边是垂直翻转后的原图

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 图片大小是 256*256
transform = transforms.RandomVerticalFlip(p=1)
imgt = transform(img)
imgt.save('./data/vertical_flip.jpeg')

在这里插入图片描述

3 随机旋转:transforms.RandomRotation

CLASS torchvision.transforms.RandomRotation(degrees, resample=0, expand=False, center=None, fill=None)
按角度旋转图像。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数。

参数

  • degrees (sequence or float or int) –旋转角度的范围。如果角度是一个数字而不是类似(min,max)的序列,则度数的范围将是(-degrees,+degrees)
    –两种写法:degrees=d; degrees=(min,max);
  • resample (int, 可选) ---- 重采样过滤器(可选项)。默认值是0,更多过滤器的详细见链接。如果省略,或者图像具有模式“1”或“P”(这里不懂),则将其设置为PIL.Image.NEAREST.如果输入是Tensor对象,仅支持PIL.Image.NEAREST, PIL.Image.BILINEAR以及PIL.Image.BICUBIC。
    –取值0到5六个分别是(实验结果猜测的,以后有时间再研究这几个插值):PIL.Image.NEAREST(0), PIL.Image.ANTIALIAS(1), PIL.Image.BILINEAR(2), PIL.Image.BICUBIC(3), PIL.Image.BOX(4), PIL.Image.HAMMING(5)
    – 貌似只支持0,2,3三种过滤器。
  • expand (bool, 可选) ---- 扩展标志(可选项)。默认值是false.如果为true,则展开输出以使其足够大以容纳整个旋转图像。如果为false或省略,则使输出图像与输入图像的大小相同。请注意,expand标志假定围绕中心旋转而不平移。
  • center (list or tuple, 可选) ---- 旋转中心(可选项),(x,y)。原点在左上角。默认值为图像的中心。
    –两种写法:center=[x,y]; center=(x,y)
  • fill (3-tuple or int) ---- 旋转图像外部区域的像素填充值。如果是int或float,则该值用于所有外部区域。默认填充值为0。此选项仅适用于Pillow>=5.2.0. Tensor输入不支持此选项。因此Tensor输出图像中变换外部区域的填充值始终为0。
    – 两种写法:fill=x; fill=(x,y,z)

返回
随机旋转后的图像
返回类型:PIL图像或Tenso

下图是随机随机旋转

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 图片大小是 256*256
degrees = 30
for i in range(30):
   transform = transforms.RandomRotation(degrees=degrees)
   imgt = transform(img)
   imgt.save('./data/random_rotation_' + str(i) + ".jpeg")

在这里插入图片描述
下图是可支持的三种过滤器模式:

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 图片大小是 256*256
degrees = 30
resamples = [0,2,3]
for i,resample in enumerate(resamples)
    transform = transforms.RandomRotation(degrees=degrees,
                                   resample=resample)
    imgt = transform(img)
    imgt.save('./data/random_rotation_r'+str(i)+'.jpeg')

在这里插入图片描述
下图c.可以看出为True的时候图像大小变大了。

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 图片大小是 256*256
degrees = 45
expands = [True,False]
for i, expand in enumerate(expands):
    transform = transforms.RandomRotation(degrees=degrees, 
                         expand=expand)
    imgt = transform(img)
    imgt.save('./data/random_rotation_e'+str(i)+'.jpeg')

在这里插入图片描述
下图是是Center设置为(10,10)的时候,左边是expand为True,右边是expand为False。因为截的是缩略图,所以看起来都一样大,其实左边的图片每一张大小都不一样。而右边的每一张都和原图一样大。

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 图片大小是 256*256
degrees = 45
expands = [True,False]
center = (10,10)
for j,expand in enumerate(expands):
    for i in range(9):
        transform = transforms.RandomRotation(degrees=degrees,
                          expand=expand, center=center)
        imgt = transform(img)
        imgt.save('./data/random_rotation_c' +str(j)+'_'+ str(i)+'.jpeg')

在这里插入图片描述
下图是fill填写不一样的值。左边fill=123,右边fill=(0,255,255)

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 图片大小是 256*256
degrees = 34.5
fills = [123,(0,255,255)]
center = (100,100)
for i, fill in enumerate(fills):
     transform = transforms.RandomRotation(degrees=degrees,
                          fill=fill,center=center)
     imgt = transform(img)
     imgt.save('./data/random_rotation_f'+str(i)+'.jpeg')

在这里插入图片描述

* 其他图像变换

1 重置大小:transforms.Resize

CLASS torchvision.transforms.Resize(size, interpolation=2)
重置给定图片的大小。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数。

参数

  • size (sequence or int) ----所需的输出大小。如果size是一个类似(h,w)的序列,则输出大小将与此匹配。如果size为int,则图像的较小边将与该数字匹配。例如,如果height>width,则图像将被重新缩放为(size*height/width,size)。在torchscript模式下,由于不支持单个int填充,请使用长度为1的元组或列表:[size,]。
    – 两种写法:size=s; size=(h,w)
  • interpolation (int) ---- filters(过滤器)定义的所需插值枚举。
    默认为PIL.Image.BILINEAR. (双线性插值)。如果输入是Tensor对象,仅支持PIL.Image.NEAREST, PIL.Image.BILINEAR 以及 PIL.Image.BICUBIC
    – 取值0到5六个分别是(实验结果猜测的,以后有时间再研究这几个插值):PIL.Image.NEAREST(0), PIL.Image.ANTIALIAS(1), PIL.Image.BILINEAR(2), PIL.Image.BICUBIC(3), PIL.Image.BOX(4), PIL.Image.HAMMING(5)

返回
重置大小后的图像
返回类型:PIL图像或Tenso

下图是重置大小

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/img.jpeg') # 原图大小695,391
sizes = [150,(150, 150)]
for i, size in enumerate(sizes):
    transform = transforms.Resize(size)
    imgt = transform(img)
    imgt.save('./data/resize_s'+str(i)+'.jpeg')

在这里插入图片描述
下图是重置大小的六种不同的插值

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/img.jpeg') # 原图大小695,391
size = (150, 150)
interpolations = [0,1,2,3,4,5]
for i, interpolation in enumerate(interpolations):
    transform = transforms.Resize(size=size, 
                      interpolation=interpolation)
    imgt = transform(img)
    imgt.save('./data/resize_i'+str(i)+'.jpeg')

在这里插入图片描述

2 (已弃用)重置大小:transforms.Scale

CLASS torchvision.transforms.Scale(*args, **kwargs)

-注意:此转换被弃用,取而代之的是Resize。

3 填充:transforms.Pad

CLASS torchvision.transforms.Pad(padding, fill=0, padding_mode='constant')
用给定的填充值填充给定图像的边缘。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数。

参数

  • padding(int 或 sequence,可选)---- 图像每个边填充长度(单位是像素)。 默认值为“无”。如果提供了一个int,则用于填充所有边界。如果提供长度为2的元组,则分别在左/右和上/下填充。如果提供了长度为4的元组,则这是分别用于左边框、上边框、右边框和下边框的填充。在torchscript模式下,由于不支持单个int,请使用长度为1的元组或列表:[padding,]
    – 也就是七种写法: padding=p; padding=(p,); padding=(p1,p2); padding=(p1,p2,p3,p4); padding=[p]; padding=[p1,p2]; padding=[p1,p2,p3,p4]
    – 这里的p是指上下左右边界用fill的值填充p个像素的长度。
  • fill (int 或 tuple) ---- 常量填充的像素填充值。 默认值为0。如果一个元组的长度为3,则分别用于填充R、G、B通道。此值仅在填充模式为常量时使用.
    – 也就是两种写法: fill=f; fill=(f1,f2,f3).
  • padding_mode (str) ---- 填充类型. 应为:constant常数、edge边、reflect反射或symmetric对称。默认值为常量。张量输入尚不支持模式对称。
    - constant 常数,用常数值进行填充,这个常数值由fill指定。
    - edge 边, 用图像最边缘的值进行填充。
    - reflect 反射,用图像的反射进行填充(不重复最边缘的值),例如,使用反射模型在[1, 2, 3, 4]的两边填充2个元素的结果为[3, 2, 1, 2, 3, 4, 3, 2]
    - symmetric 对称,用图像的对称进行填充(重复最边缘的值),例如,使用对称模型在[1, 2, 3, 4]的两边填充2个元素的结果为[2, 1, 1, 2, 3, 4, 4, 3]
    如果使用了edge,reflect,symmetric这三种模式,fill的值就会被无视
    从结果上看reflect和symmetric这两种模式没有什么大的区别,不过我私以为这可能是设计上的问题,应该有一种模式是重复,而不是镜像方式。(不过也可能是我的实验有问题)
    图像padding后的大小 < size时,选择pad_if_needed为True和自己padding图像得到的结果还是有区别的,没有去研究系统是怎么自动padding的。感兴趣的可以研究试一试。

返回
填充后的图像
返回类型:PIL图像或Tenso

  • padding参数
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg')
paddings = [50,(50,),(50, 100),(50, 100, 150, 200),
           [50], [50, 100], [50, 100, 150, 200]]
for i, padding in enumerate(paddings):
    transform = transforms.Pad(padding=padding)
    imgt = transform(img)
    imgt.save('./data/pad_' + str(i) + '.jpeg')

在这里插入图片描述

  • fill参数
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg')
padding = 50
fills = [255, (0,255,255)]
for i, fill in enumerate(fills):
    transform = transforms.Pad(padding=padding, fill=fill)
    imgt = transform(img)
    imgt.save('./data/pad_f' + str(i) + '.jpeg')

在这里插入图片描述

  • padding_mode参数

reflect 和 symmetric还是有点不同的,因为是缩略图所以看不出来。自己运行代码后,通过快速切换两张图片,在动态视觉上可以比较明显的看出不同。

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg')
padding = 256*2
fill = (255,0,255)
padding_modes = ["constant", "edge", "reflect", "symmetric"]
for i, padding_mode in enumerate(padding_modes):
    transform = transforms.Pad(padding=padding, fill=fill,padding_mode=padding_mode)
    imgt = transform(img)
    imgt.save('./data/pad_m' + str(i) + '.jpeg')

在这里插入图片描述

4 修改亮度、对比度、饱和度、色调:transforms.ColorJitter

CLASS torchvision.transforms.ColorJitter(brightness=0, contrast=0, saturation=0, hue=0)
随机改变图像的亮度、对比度和饱和度。

参数

  • brightness(float or tuple of python:float (min, max)) ---- 亮度的偏差程度。亮度系数从[max(0,1-brightness),1+brightness]或给定的[min,max]中随机的选择某个值。应是非负数。
  • contrast(float or tuple of python:float (min, max)) ---- 对比度的偏差程度。。对比度系数从[max(0,1-contrast),1+contrast]或给定的[min,max]中随机的选择某个值。应是非负数。
  • saturation(float or tuple of python:float (min, max)) ---- 饱和的偏差程度。饱和系数从[max(0,1-saturation),1+saturation]或给定的[min,max]中随机的选择某个值。应是非负数。
  • hue(float or tuple of python:float (min, max)) ---- 色调的偏差程度。色调系数从[-hue,hue]或给定的[min,max]随机的选择某个值。应具有0<=hue<=0.5或-0.5<=min<=max<=0.5。

返回
色彩变换后的图像
返回类型:PIL图像或Tenso

  • brightness 亮度
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg')
transform = transforms.ColorJitter(brightness=2, 
                 contrast=0, saturation=0, hue=0)
for i in range(6):
    imgt = transform(img)
    imgt.save('./data/colorjitter_b'+str(i)+'.jpeg')

在这里插入图片描述

  • contrast 对比度
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg')
transform = transforms.ColorJitter(brightness=0, 
                  contrast=2, saturation=0, hue=0)
for i in range(6):
    imgt = transform(img)
    imgt.save('./data/colorjitter_c'+str(i)+'.jpeg')

在这里插入图片描述

  • saturation 饱和
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg')
transform = transforms.ColorJitter(brightness=0, 
                 contrast=0, saturation=2, hue=0)
for i in range(6):
    imgt = transform(img)
    imgt.save('./data/colorjitter_s'+str(i)+'.jpeg')

在这里插入图片描述

  • hue 色调
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg')
transform = transforms.ColorJitter(brightness=0, contrast=0, 
                    saturation=0, hue=0.5)
for i in range(6):
    imgt = transform(img)
    imgt.save('./data/colorjitter_h'+str(i)+'.jpeg')

在这里插入图片描述

5 仿射变换:transforms.RandomAffine

CLASS torchvision.transforms.RandomAffine(degrees, translate=None, scale=None, shear=None, resample=0, fillcolor=0)
图像保持中心不变的随机仿射变换。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数.

参数

  • degrees (sequence or float or int) ---- 旋转,角度选择的范围。如果度数是一个数字而不是类似(min,max)的序列,则度数的范围是(-degrees,+degrees)。设置为0可禁用旋转。
  • translate (tuple, 可选项) ---- 平移,水平和垂直平移的最大绝对分数元组。例如translate=(a,b),a,b取值范围[0,1].则水平偏移在范围 - img_width * a<dx<img_width * a 范围内随机采样,垂直偏移在范围 - img_height * b<dy<img_height * b范围内随机采样。默认情况下不进行平移。
  • scale (tuple, 可选项) ---- 缩放,比例系数选择区间,例如(a,b)a<=b,且取值范围(0,正无穷),那么比例从a<=scale<=b的范围内随机抽样,默认情况下保持原始比例。
  • shear (sequence or float or int, optional) ---- 对角轴翻转的选择范围。如果“shear”是一个数字,则将进行与可与x轴平行的剪切的对角翻转,范围为(-shear,+shear)。如果shear”是一个2个值的元组或列表,则将进行与可与x轴平行的剪切的对角翻转,范围在(shear[0],shear[1])。如果剪切是一个4个值的元组或列表,则将在范围(shear[0],shear[1])进行x轴剪切以及在范围(shear[2],shear[3])进行y轴剪切的对角翻转。默认情况下不会应用的对角翻转。
  • resample (int, 可选项) ---- 重采样过滤器(可选项)。缩放时候采用的算法。更多过滤器的详细见链接。如果省略,或者图像具有模式“1”或“P”(这里不懂),则将其设置为PIL.Image.NEAREST.如果输入是Tensor对象,仅支持PIL.Image.NEAREST, PIL.Image.BILINEAR以及PIL.Image.BICUBIC。
    –取值0到5六个分别是(实验结果猜测的,以后有时间再研究这几个插值):PIL.Image.NEAREST(0), PIL.Image.ANTIALIAS(1), PIL.Image.BILINEAR(2), PIL.Image.BICUBIC(3), PIL.Image.BOX(4), PIL.Image.HAMMING(5)
  • fillcolor (tuple or int) ---- 变换图像输出中外部区域的可选填充颜色(RGB图像为元组,灰度为int)(Pillow>=5.0.0)。此输入不支持Tensor。输出的变换图像中外部区域的填充值始终为0。旋转,翻转,平移,缩小时候要填充的颜色

返回
仿射变换后的图像
返回类型:PIL图像或Tenso

  • 旋转
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
degreeses = [0, 30, 400, (-30, 60)]  
for i, degrees in enumerate(degreeses):
    transform = transforms.RandomAffine(degrees)
    imgt = transform(img)
    imgt.save('./data/random_affine_d' + str(i) + '.jpg')

在这里插入图片描述

  • 平移
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
degrees = 0
translates = [(0,0.5),(0.5,0),(1,1)]
for i, translate in enumerate(translates):
    transform = transforms.RandomAffine(degrees, translate=translate)
    imgt = transform(img)
    imgt.save('./data/random_affine_t' + str(i) + '.jpg')

在这里插入图片描述

  • 缩放
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
degrees = 0
scales = [(0.1,0.2),(1,100),(1,1)]
for i, scale in enumerate(scales):
    transform = transforms.RandomAffine(degrees, scale=scale)
    imgt = transform(img)
    imgt.save('./data/random_affine_sc' + str(i) + '.jpg')

在这里插入图片描述

  • 翻转
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
degrees = 0
shears = [30,[-30,30],[0,10,20,30]] 
for i, shear in enumerate(shears):
    transform = transforms.RandomAffine(degrees, shear=shear)
    imgt = transform(img)
    imgt.save('./data/random_affine_sh' + str(i) + '.jpg')

在这里插入图片描述

  • 重采样过滤器方式
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
degrees = 0
scale=(3,3)
resamples = [0,2,3]
for i, resample in enumerate(resamples):
    transform = transforms.RandomAffine(degrees,scale=scale, 
                        resample=resample)
    imgt = transform(img)
    imgt.save('./data/random_affine_r' + str(i) + '.jpg')

在这里插入图片描述

  • 填充颜色
import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
degrees = 0
translate=(0.5,0.5)
fillcolors = [122,(0,234,121)]
for i, fillcolor in enumerate(fillcolors):
    transform = transforms.RandomAffine(degrees,
                  translate=translate, fillcolor=fillcolor)
    imgt = transform(img)
    imgt.save('./data/random_affine_f' + str(i) + '.jpg')
degrees = 0 
scale=(0.5,0.5)
fillcolors = [122,(0,234,121)]
for i, fillcolor in enumerate(fillcolors):
    transform = transforms.RandomAffine(degrees,scale=scale, 
                   fillcolor=fillcolor)
    imgt = transform(img)
    imgt.save('./data/random_affine_f' + str(i+2) + '.jpg')

在这里插入图片描述

6 转灰度图:transforms.Grayscale

CLASS torchvision.transforms.Grayscale(num_output_channels=1)
把图像转成灰度图。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,3,H,W]形状,其中…表示任意数量的前导维数

参数

  • num_output_channels (int) ----(1或3)输出图像所需的通道数

返回
输入的灰度版本。

  • 如果num_output_channels==1:返回的图像为单通道
  • 如果num_output_channels==3:返回的图像是3个通道,r == g == b

返回类型:PIL图像或Tensor

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
num_output_channelses =[1,3]
for i, num_output_channels in enumerate(num_output_channelses):
    transform = transforms.Grayscale(num_output_channels)
    imgt = transform(img)
    imgt.save('./data/grayscale_' + str(i) + '.jpg')

在这里插入图片描述

7 依概率p转为灰度图:transforms.RandomGrayscale

CLASS torchvision.transforms.RandomGrayscale(p=0.1)
以概率为p(默认值为0.1),随机将图像转换为灰度。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,3,H,W]形状,其中…表示任意数量的前导维数.

参数

  • p (float) ---- 图像应转换为灰度的概率。

返回
概率为p,输入图像的灰度版本,概率(1-p)不变。

  • 如果输入图像为1通道,灰度版本为1通道。
  • 如果输入图像为3通道,灰度版本为3通道,r == g == b

返回类型:PIL图像或Tensor

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
for i in range(6):
    transform = transforms.RandomGrayscale(p=0.5)
    imgt = transform(img)
    imgt.save('./data/random_grayscale_' + str(i) + '.jpg')

在这里插入图片描述

8 随机透视变换:transforms.RandomPerspective

CLASS torchvision.transforms.RandomPerspective(distortion_scale=0.5, p=0.5, interpolation=2, fill=0)
以给定的概率对给定图像执行随机透视变换。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,H,W]形状,其中…表示任意数量的前导维数.

参数

  • distortion_scale (float) ---- 控制失真程度的参数,范围从0到1。默认值为0.5。
  • p(float) ---- 图像被变换的概率。默认值为0.5。
  • interpolation (int) ---- filters(过滤器)定义的所需插值枚举。
    默认为PIL.Image.BILINEAR. (双线性插值)。如果输入是Tensor对象,仅支持PIL.Image.NEAREST, PIL.Image.BILINEAR 以及 PIL.Image.BICUBIC
  • fill (n-tuple or int or float) ---- 旋转图像外部区域的像素填充值。如果是int或float,则该值用于所有外部区域。默认填充值为0。此选项仅适用于Pillow>=5.2.0. Tensor输入不支持此选项。因此Tensor输出图像中变换外部区域的填充值始终为0。

返回
随机变换的图像
返回类型:PIL图像或Tenso

默认值

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
for i in range(6):
    transform = transforms.RandomPerspective(distortion_scale=0.5, p=0.5, 
                interpolation=2, fill=0)
    imgt = transform(img)
    imgt.save('./data/random_perspective' + str(i) + '.jpg')

在这里插入图片描述

不同的插值方式

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
interpolations = [0,2,3]
for i,interpolation in enumerate(interpolations):
    transform = transforms.RandomPerspective(distortion_scale=0.5, p=1, 
                interpolation=interpolation, fill=0)
    imgt = transform(img)
    imgt.save('./data/random_perspective_i' + str(i) + '.jpg')

在这里插入图片描述

9 随机高斯模糊:transforms.GaussianBlur

CLASS torchvision.transforms.GaussianBlur(kernel_size, sigma=(0.1, 2.0))
使用随机选择的高斯模糊模糊模糊图像。图像可以是PIL图像或Tensor,在这种情况下,它应该具有[…,C,H,W]形状,其中…表示任意数量的前导维数.

参数

  • kernel_size (int or sequence) ---- 高斯核的大小。
  • sigma(float or tuple of python:float (min, max)) ---- 用于创建内核以执行模糊处理的标准偏差。如果是一个float,sigma是固定的。如果它是float的元组 (min, max),sigma随机地均匀地在给定的范围内选择。

返回
输入图像的高斯模糊版本。
返回类型:PIL图像或Tenso

高斯核的大小

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
kernel_sizes = [1,3,5,7,9,11]
for i, size in enumerate(kernel_sizes):
    transform = transforms.GaussianBlur(kernel_size=size,
                   sigma=(10.0, 10.0))
    imgt = transform(img)
    imgt.save('./data/gaussian_blur_' + str(i) + '.jpg')

在这里插入图片描述

sigma

import torchvision.transforms as transforms
from PIL import Image
img = Image.open('./data/timg.jpeg') # 原图大小256*256
for i in range(6):
    transform = transforms.GaussianBlur(kernel_size=11, 
                           sigma=(0.1, 10.0))
    imgt = transform(img)
    imgt.save('./data/gaussian_blur_s' + str(i) + '.jpg')

在这里插入图片描述

三、只针对PIL图像的变换

1 随机选择:transforms.RandomChoice

CLASS torchvision.transforms.RandomChoice(transforms)
从列表中随机选取的单个变换进行应用。此转换不支持torchscript.

3 随机顺序:transforms.RandomOrder

CLASS torchvision.transforms.RandomOrder(transforms)
以随机顺序应用变换列表。此转换不支持torchscript。

四、只针对torch.*Tensor的变换

1 线性变换:transforms.LinearTransformation

CLASS torchvision.transforms.LinearTransformation(transformation_matrix, mean_vector)
用平方变换矩阵和均值向量变换tensor图像。给定变换矩阵和平均向量,将使torch.*Tensor变平并从中减去平均向量,然后用转换矩阵计算点积,然后将tensor重塑为其原始形状。

参数

  • transformation_matrix (Tensor) – tensor [D x D], D = C x H x W
  • mean_vector (Tensor) – tensor [D], D = C x H x W

返回
变换后的图像。
返回类型:Tensor

应用
白化变换:假设X是列向量零中心数据。然后用torch.mm(X.t(), X),计算数据的阵协方差矩阵 [D x D],再计算该矩阵的SVD ,并将其作为转换矩阵传递。

2 标准化:transforms.Normalize

CLASS torchvision.transforms.Normalize(mean, std, inplace=False)
用均值和标准差归一化张量图像。给定n个通道的平均值:(mean[1],…,mean[n])和std(标准差):(std[1],…,std[n]),此转换将规范化torch.*Tensor的每个通道的输入。即,output[channel] = (input[channel] - mean[channel]) / std[channel]

参数

  • mean (sequence) ---- 每个通道的平均值。
  • std (sequence) ---- 每个通道的标准差。
  • inplace (bool,optional) — 是否覆盖原图片

返回
标准化后的图像。
返回类型:Tensor

*如果有自己的数据集,如何计算mean以及std,可以见脚本:normalize.py compute_mean_std方法。注意的是opencv 读图的顺序BGR; PIL读图的顺序是RGB。
下图是标准化前后的对比图
在这里插入图片描述

下图是inplace取不同的值的图片,可以看出inplace为true替换的是输入图片。如果是false,输入图片不会替换。
输出图片不管是true还false都会改变。
在这里插入图片描述

  • 标准化是通过特征的平均值和标准差,将特征缩放成一个标准的正态分布,缩放后均值为0,方差为1

3 随机区域擦除:transforms.RandomErasing`

CLASS torchvision.transforms.RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace=False)

随机选择图像中的矩形区域并删除其像素。Zhong等人的“随机擦除数据扩充”,见网址:https://arxiv.org/abs/1708.04896

参数

  • p ---- 执行随机擦除操作的概率。
  • scale ---- 擦除区域相对于输入图像的比例范围。
  • ratio — 擦除区域的纵横比范围。
  • value---- 删除值。默认值为0。如果是一个整型,它将被用来清除所有像素。如果是长度为3的元组,则分别用于擦除R、G、B通道。如果“random”的str,则用每个像素用随机值擦除。
  • inplace — 布尔值是否替换原图。默认设置为False。

返回
擦除后的图像。
返回类型:Tensor

transform = transforms.Compose([
	transforms.RandomHorizontalFlip(),
	transforms.ToTensor(),
	transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
	transforms.RandomErasing(),
 ])

4 转换数据类型:transforms.ConvertImageDtype

CLASS torchvision.transforms.ConvertImageDtype(dtype: torch.dtype)
将tensor图像转换为给定的数据类型并相应地缩放值。

参数

  • dtype(torch.dpython:type) ---- 输出的所需数据类型。

从较小的整数数据类型转换为较大的整数数据类型时,最大值不会精确映射。如果来回转换,此不匹配将不起作用。

RuntimeError–尝试强制将torch.float32转换至torch.int32或者torch.int64以及试图将torch.float64转换成torch.int64. 这些转换可能会导致溢出错误,因为浮点数据类型无法在整数数据类型的整个范围内连续存储。

五、数据类型转换变换

1 将数据转换为PILImage:transforms.ToPILImage

CLASS torchvision.transforms.ToPILImage(mode=None)
将 tensor或ndarray转换为PIL图像。此转换不支持torchscript。
将形状C x H x W的 torch.*Tensor 或形状H x W x C的numpy ndarray转换为PIL图像,同时保留值范围。

参数

  • mode (PIL.Image mode) ---- 输入数据的颜色空间和像素深度(可选)。如果mode为None(默认),则对输入数据进行一些假设:-如果输入有4个通道,则假定模式为RGBA。-如果输入有3个通道,则假定模式为RGB。-如果输入有2个通道,则假定模式为LA。-如果输入有1个通道,则模式由数据类型(即int、float、short)决定。

2 将数据转为tensor:transforms.ToTensor

CLASS torchvision.transforms.ToTensor
将PIL图片或者numpy.ndarray转成tensor。这个转换操作不支持torchscript。

如果PIL图片属于模式 (L, LA, P, I, F, RGB, YCbCr, RGBA, CMYK, 1)之一,或者如果numpy.ndarray的dtype = np.uint8,那么PIL或者numpy.ndarray在范围[0, 255]的 (H x W x C)将转换为[0.0, 1.0]范围的 (C x H x W)形状的torch.FloatTensor。
在其他情况下,张量返回时不会缩放。

  • 如果用opencv读图后,转成了tensor后, 再转回来保存的时候,需要转成numpy格式, 然后放大255倍,再用转置改变形状的顺序才可以保存。
  • 如果用PIL也要注意顺序问题。
  • 注意形状顺序和通道顺序是有区别的。ToTensor是做归一化且改变了形状顺序。

六、通用变换

1 Lambda函数:transforms.Lambda

CLASS torchvision.transforms.Lambda(lambd)
应用用户定义的lambda作为转换。此转换不支持torchscript。

参数

  • lambd (function) ---- 要用于转换的Lambda/函数。

2 随机应用变换:transforms.RandomApply

CLASS torchvision.transforms.RandomApply(transforms, p=0.5)
给定概率下随机应用变换列表。

参数

  • transforms (list or tuple or torch.nn.Module) ---- 转换的列表。
  • p(float) ---- 概率。

为了编写转换脚本,请使用torch.nn.ModuleList作为输入,而不是转换的列表/元组,如下所示:

transforms = transforms.RandomApply(torch.nn.ModuleList([
    transforms.ColorJitter(),]), p=0.3)
scripted_transforms = torch.jit.script(transforms)

确保只使用可编写脚本的转换,即torch.Tensor,不需要lambda函数或PIL.Image

七、变换函数

略,有时间再补吧

  • 36
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值