首页 > 解决方案 > 带有展开和折叠功能的 pytorch 滑动窗口

问题描述

我正在尝试使用一个简单的 ssd,它是在 300x300 数据上训练的,带有带注释的边界框。如果我手动裁剪图像,它可以正常工作,但对于全尺寸图像,它会失败(显然),因为将大图像调整为 300x300 会删除许多视觉特征。

我认为好的旧滑动窗口可以在这里工作,但是我在通过检测重建图像时遇到了一些问题,并且必须承认我对如何处理有点无能为力,到目前为止我所拥有的是:

起初,我试过这个:

chips = F.unfold(img_t.data, kernel_size=300)

遵循堆栈溢出的一些示例,但这给了我错误Input Error: Only 4D input Tensors are supported (got 3D)

因此,在进行了更多谷歌搜索后,我发现了一些可行的方法:

        patch_w = 300
        patch_h = 300
        patches = img_t.data.unfold(0, 3, 3).unfold(1, patch_w, patch_h).unfold(2, patch_w, patch_h)
#Visualise small part:
        fig = plt.figure(figsize=(4, 4))
        fig.tight_layout()
        plt.subplots_adjust(left=0.1, bottom=0.1, right=0.9, top=0.9, wspace=0.01, hspace=0.01)
        for i in range(4):
            for j in range(4):
                inp = transp(patches[0][i][j])
                inp = np.array(inp)

                ax = fig.add_subplot(4, 4, ((i*4)+j)+1, xticks=[], yticks=[])
                plt.imshow(inp)
        plt.show()

然后我把它喂给patches我的探测器,它看起来或多或少没问题,但没有重叠(物体可以被切成碎片并遗漏),更重要的是,我无法在unfold不被异常淹没的情况下逆转这个过程。

我并不坚持使用折叠/展开组合来完成任务,因为我真正想要的是能够以一种能够保留尽可能多的信息、标记检测和重建图像的方式将大图像输入网络带有来自较小补丁的边界框。

我想出的是:

new_im = Image.new("RGB", (300*dims[1], 300*dims[0]))
idx = 0
for i in range(dims[0]):
    for j in range(dims[1]):
        new_im.paste(tiles[idx], (j*300, i*300))
        idx += 1
new_im.show()

它将图像重建回来,但是以一种非常人为的方式,检测器注释图像裁剪并将它们作为图像列表返回,我在这里重建它既丑陋又低效。

经过一番摆弄,我让它工作了,但是有一个 pytorch 的特殊性。它添加补丁的重叠部分而不是平均它们(见图)。我该如何解决?我意识到标准化在这里不会做任何事情,因为它也会标准化好的像素,所以它只需要平均重叠像素。 马赛克错误 另外,请注意图像被错误地裁剪。重现的简单代码:

def fold_unfold(img_path):
    transt = transforms.Compose([transforms.ToTensor(),
    # transforms.Normalize(mean=[0.5,0.5, 0.5], std=[0.5, 0.5, 0.5])
        ])

    transp = transforms.Compose([
        # transforms.Normalize(mean=[0.5,0.5, 0.5], std=[0.5, 0.5, 0.5]),
        transforms.ToPILImage()
    ])
    img_t = transt(Image.open(img_path))
    img_t = img_t.unsqueeze(0)
    kernel = 300
    stride = 200
    img_shape = img_t.shape
    B, C, H, W = img_shape
    # number of pixels missing:
    pad_w = W % kernel
    pad_h = H % kernel
    # Padding the **INPUT** image with missing pixels:
    img_t = F.pad(input=img_t, pad=(pad_w//2, pad_w-pad_w//2, pad_h//2, pad_h-pad_h//2), mode='constant', value=0)
    img_shape = img_t.shape
    B, C, H, W = img_shape
    print("\n-----input shape: ", img_shape)

    patches = img_t.unfold(3, kernel, stride).unfold(2, kernel, stride).permute(0,1,2,3,5,4)

    print("\n-----patches shape:", patches.shape)
    # reshape output to match F.fold input
    patches = patches.contiguous().view(B, C, -1, kernel*kernel)
    print("\n", patches.shape) # [B, C, nb_patches_all, kernel_size*kernel_size]
    patches = patches.permute(0, 1, 3, 2) 
    print("\n", patches.shape) # [B, C, kernel_size*kernel_size, nb_patches_all]
    patches = patches.contiguous().view(B, C*kernel*kernel, -1)
    print("\n", patches.shape) # [B, C*prod(kernel_size), L] as expected by Fold
    # https://pytorch.org/docs/stable/nn.html#torch.nn.Fold

    output = F.fold(patches, output_size=(H, W), kernel_size=kernel, stride=stride)
    # mask that mimics the original folding:
    recovery_mask = F.fold(torch.ones_like(patches), output_size=(H,W), kernel_size=kernel, stride=stride)
    output = output/recovery_mask

    print(output.shape) # [B, C, H, W]
    aspil = transp(output[0])
    aspil.show()

仍然,图像被裁剪了很多,所以仍然有问题: 仍然存在种植问题

最后,完成裁剪代码更新到工作版本问题来自 pytorch 展开的方式。从张量展开的方法不会自动归零,而是通过切断不适合的部分来停止裁剪。我通过在裁剪之前对张量进行零填充来解决它。

标签: pythonpytorchsliding-window

解决方案


我以这种方式发布,以便其他人在遇到类似问题时更容易找到解决方案。

主要亮点:

  • pytorch 展开将裁剪掉不适合使用的滑动窗口的部分图像。(例如,对于 300x300 图像和 100x100 窗口,什么都不会被裁剪,但是对于 290x290 图像和相同窗口,裁剪将很好......裁剪出原始图像的最后 90 行和列。解决方案是先行对图像进行零填充匹配滑动窗口的大小
  • 输入图像的大小会在零填充后发生变化(这并不奇怪),但在重建原始图像时很容易忘记这一点。
  • 理想情况下,您最终可能希望将图像裁剪为原始大小,但是通过滑动窗口方法,我的应用程序在图像周围保持填充更有意义,这样我的检测器的中心也可以应用于图像的边缘.
  • 展开:我找不到两者之间的实际区别patches = img_t.unfold(3, kernel, stride).unfold(2, kernel, stride).permute(0,1,2,3,5,4)patches = img_t.unfold(2, kernel, stride).unfold(3, kernel, stride)因此欢迎对此进行解释。
  • 必须对图像张量进行多次整形,然后才能将其折叠回原始(填充!)图像。
  • 归一化——不是在图像变换的意义上,而是恢复滑动窗口重叠的效果。我发现 pytorch 的另一个特性是它在折叠重叠的补丁时将张量粘贴到另一个上的方式。它不是取重叠区域的平均值,而是将它们加在一起。这可以通过重叠掩码的形式恢复。这具有生成的补丁的精确形状,每个点的值为 1。折叠后,每个像素/点的值将等于堆叠折叠的数量。最终,这是对重叠颜色进行平均的分母。

最终对我有用的代码:

import torch
from torchvision.transforms import transforms
import torch.nn.functional as F
from PIL import Image

img_path = 'filename.jpg'


def fold_unfold(img_path):
    transt = transforms.Compose([transforms.ToTensor(),
       # transforms.Normalize(mean=[0.5,0.5, 0.5], std=[0.5, 0.5, 0.5])
    ])

    transp = transforms.Compose([
        # transforms.Normalize(mean=[0.5,0.5, 0.5], std=[0.5, 0.5, 0.5]),
        transforms.ToPILImage()
    ])
    img_t = transt(Image.open(img_path))
    img_t = img_t.unsqueeze(0)
    kernel = 300
    stride = 200  # smaller than kernel will lead to overlap
    img_shape = img_t.shape
    B, C, H, W = img_shape  # Batch size, here 1, channels (3), height, width
    # number of pixels missing in each dimension:
    pad_w = W % kernel
    pad_h = H % kernel
    # Padding the **INPUT** image with missing pixels:
    img_t = F.pad(input=img_t, pad=(pad_w//2, pad_w-pad_w//2,
                                    pad_h//2, pad_h-pad_h//2), mode='constant', value=0)
    img_shape = img_t.shape
    # UPDATE the shape information to account for padding
    B, C, H, W = img_shape
    print("\n----- input shape: ", img_shape)

    patches = img_t.unfold(3, kernel, stride).unfold(2, kernel, stride).permute(0, 1, 2, 3, 5, 4)

    print("\n----- patches shape:", patches.shape)
    # reshape output to match F.fold input
    patches = patches.contiguous().view(B, C, -1, kernel*kernel)
    print("\n", patches.shape) # [B, C, nb_patches_all, kernel_size*kernel_size]
    patches = patches.permute(0, 1, 3, 2) 
    print("\n", patches.shape) # [B, C, kernel_size*kernel_size, nb_patches_all]
    patches = patches.contiguous().view(B, C*kernel*kernel, -1)
    print("\n", patches.shape) # [B, C*prod(kernel_size), L] as expected by Fold
    # https://pytorch.org/docs/stable/nn.html#torch.nn.Fold

    output = F.fold(patches, output_size=(H, W),
                    kernel_size=kernel, stride=stride)
    # mask that mimics the original folding:
    recovery_mask = F.fold(torch.ones_like(patches), output_size=(
        H, W), kernel_size=kernel, stride=stride)
    output = output/recovery_mask

    print(output.shape)  # [B, C, H, W]
    aspil = transp(output[0])
    aspil.show()


fold_unfold(img_path)

推荐阅读