PyTorch:如何将相同的随机变换应用于多张图片?

12

我正在编写一个简单的数据集转换工具,其中包含许多图片对。作为数据增强,我希望对每个对应的图片都应用一些随机变换,但是该图片对中的图像应以相同的方式进行变换。 例如,给定一对两张图片 AB,如果将 A 水平翻转,则 B 必须以同样的方式翻转,接下来的另一对 CD 应与 AB 有所不同,但 CD 应以相同的方式进行变换。我是这样尝试的:

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

img_a = Image.open("sample_ajpg") # note that two images have the same size
img_b = Image.open("sample_b.png")
img_c, img_d = Image.open("sample_c.jpg"), Image.open("sample_d.png")

transform = transforms.RandomChoice(
    [transforms.RandomHorizontalFlip(), 
     transforms.RandomVerticalFlip()]
)
random.seed(0)
display(transform(img_a))
display(transform(img_b))

random.seed(1)
display(transform(img_c))
display(transform(img_d))

然而,上述代码并没有选择相同的转换方式,根据我的测试结果,它取决于调用 transform 的次数。

是否有任何方法可以强制transforms.RandomChoice在指定时使用相同的转换方式?

5个回答

11
通常的解决方法是在第一张图像上应用变换,获取该变换的参数,然后使用这些参数在剩余的图像上应用确定性变换。然而,在这种情况下,RandomChoice无法提供一个API来获取应用变换的参数,因为它涉及到可变数量的变换。
在这些情况下,我通常会对原始函数进行覆盖实现。
看一下torchvision的实现,非常简单:
class RandomChoice(RandomTransforms):
    def __call__(self, img):
        t = random.choice(self.transforms)
        return t(img)

这里有两个可能的解决方案。
你可以在__init__中从转换列表中进行采样,而不是在__call__中进行采样:
```python import random import torchvision.transforms as T
class RandomChoice(torch.nn.Module): def __init__(self): super().__init__() self.t = random.choice(self.transforms)
def __call__(self, img): return self.t(img) ```
因此,你可以这样做:
```python transform = RandomChoice([ T.RandomHorizontalFlip(), T.RandomVerticalFlip() ]) display(transform(img_a)) # img_a和img_b都会有相同的变换 display(transform(img_b))
transform = RandomChoice([ T.RandomHorizontalFlip(), T.RandomVerticalFlip() ]) display(transform(img_c)) # img_c和img_d都会有相同的变换 display(transform(img_d)) ```
或者更好的是,批量转换图像:
```python import random import torchvision.transforms as T
class RandomChoice(torch.nn.Module): def __init__(self, transforms): super().__init__() self.transforms = transforms
def __call__(self, imgs): t = random.choice(self.transforms) return [t(img) for img in imgs] ```
这样可以实现以下功能:
```python transform = RandomChoice([ T.RandomHorizontalFlip(), T.RandomVerticalFlip() ])
img_at, img_bt = transform([img_a, img_b]) display(img_at) # img_a和img_b都将应用相同的变换 display(img_bt) # img_a和img_b都将应用相同的变换
img_ct, img_dt = transform([img_c, img_d]) display(img_ct) # img_c和img_d都将应用相同的变换 display(img_dt) # img_c和img_d都将应用相同的变换 ```

只是为了澄清,在你的回答中 transform = transforms.RandomChoice([ 实际上是 transform = RandomChoice([,对吗? - TFC
抱歉,应该是 T.RandomChoice(),因为我将 torchvision.transforms 导入为 T - Ivan
我同意在可能的情况下进行批量转换似乎是最好的解决方案。 - Addison Klinke
4
我认为应该使用RandomChoice()而不是T.RandomChoice(),否则它会调用torchvision.transforms的RandomChoice类。此外,当我尝试使用RandomRotate方法时,它并不起作用。因为它只从你列出的变换列表中随机选择一个变换,而不是在这些变换中进行随机选择。例如,如果您有一对需要以相同方式增强的图像,则此方法不起作用,因为它们仍然可能被随机旋转。 - Cagla Sozen

4
我意识到OP请求使用torchvision解决方案,而我认为@Ivan的答案很好地解决了这个问题。
然而,对于那些没有特定数据增强库限制的人,我想指出Albumentations似乎可以通过在同一转换中允许用户传递多个源图像、框等来很好地处理这些情况。返回值结构化为字典。
import albumentations as A

transform = A.Compose(
    transforms=[
        A.VerticalFlip(p=0.5),
        A.HorizontalFlip(p=0.5)],
    additional_targets={'image0': 'image', 'image1': 'image'}
)
transformed = transform(image=image, image0=image0, image1=image1)

现在您可以访问transformed['image0']transformed['image1']等,它们都将应用随机参数。

这个的torchvision.transforms等价物是什么? - NelsonGon
我不知道是否有一个与torchvision相当的库,所以我建议使用Albumentations。 - Addison Klinke

3

参考随机变换输入和目标?我认为这可能是最干净的方法。在应用任何转换之前保存随机状态,然后仅为每个后续调用恢复它。

t = transforms.RandomRotation(degrees=360)
state = torch.get_rng_state()
x = t(x)
torch.set_rng_state(state)
y = t(y)

1

简单来说,将PyTorch中的随机化部分放入if语句中。 下面的代码使用vflip。类似地,还可以使用水平或其他变换。

import random
import torchvision.transforms.functional as TF

if random.random() > 0.5:
    image = TF.vflip(image)
    mask  = TF.vflip(mask)

这个问题在PyTorch 论坛 中已经讨论过。官方GitHub存储库页面上讨论了几种解决方案的优缺点。PyTorch维护者建议使用以下简单方法:
不要使用torchvision.transforms.RandomVerticalFlip(p=1),而是使用torchvision.transforms.functional.vflip 函数式转换为您提供了对转换管道的精细控制。与上面的转换相反,函数式转换不包含其参数的随机数生成器。这意味着您必须指定/生成所有参数,但可以重用函数式转换。

虽然这段代码可能解决了问题,但是包括解释它如何以及为什么解决了问题将有助于提高您的帖子质量,并可能导致更多的赞。请记住,您正在回答未来读者的问题,而不仅仅是现在提问的人。请[编辑]您的答案以添加解释并指出适用的限制和假设。 - Adrian Mole
@AdrianMole 感谢您的建议。我已经添加了解释 :-) - Abhi25t
这个答案被低估了。如果你可以使用随机数,为什么要改变整个类?对于诸如随机裁剪之类的事情,这也是适用的:只需使用随机整数作为“top”和“left”参数调用torchvision.transforms.crop()(确保它们在 [0,orig_size-target_size[范围内)。 - joba2ca

0

我不知道有没有一个函数可以修复随机输出。 也许尝试不同的逻辑,比如自己创建随机化来能够重复使用相同的转换。 逻辑:

  • 生成一个随机数
  • 基于这个数字在两张图片上应用一个变换
  • 生成另一个随机数
  • 对另外两张图片做同样的操作 尝试这个:
import random
import numpy as np
import torchvision.transforms as transforms
from PIL import Image

img_a = Image.open("sample_ajpg") # note that two images have the same size
img_b = Image.open("sample_b.png")
img_c, img_d = Image.open("sample_c.jpg"), Image.open("sample_d.png")

if random.random() > 0.5:
        image_a_flipped = transforms.functional_pil.vflip(img_a)
        image_b_flipped = transforms.functional_pil.vflip(img_b)
else:
    image_a_flipped = transforms.functional_pil.hflip(img_a)
    image_b_flipped = transforms.functional_pil.hflip(img_b)

if random.random() > 0.5:
        image_c_flipped = transforms.functional_pil.vflip(img_c)
        image_d_flipped = transforms.functional_pil.vflip(img_d)
else:
    image_c_flipped = transforms.functional_pil.hflip(img_c)
    image_d_flipped = transforms.functional_pil.hflip(img_d)
    
display(image_a_flipped)
display(image_b_flipped)

display(image_c_flipped)
display(image_d_flipped)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接