为什么使用 PIL 和 pytorch 对图像进行双线性缩放会产生不同的结果?

问题描述 投票:0回答:3

为了将图像提供给 pytorch 网络,我首先需要将其缩小到某个固定大小。首先,我使用 PIL.Image.resize() 方法完成此操作,并将插值模式设置为 BILINEAR。然后我认为首先将一批图像转换为 pytorch 张量,然后使用 torch.nn.function.interpolate() 函数在 GPU 上一次缩放整个张量(也是“双线性”插值模式)会更方便。这会导致模型精度下降,因为现在在推理过程中,一种缩放类型(火炬)与训练期间使用的缩放类型(PIL)不同。之后,我比较了两种视觉缩小方法,发现它们产生不同的结果。枕头缩小看起来更顺畅。尽管这些方法都是双线性的,但它们在幕后是否执行不同的操作?如果是这样,我也很好奇是否有一种方法可以通过火炬张量缩放实现与枕头图像缩放相同的结果?

原始图像(众所周知的Lenna图像)

枕头比例图像:

Pillow scaled image

火炬缩放图像:

Torch scaled image

平均通道绝对差图:

Mean channel absolute difference map

演示代码:

import numpy as np
from PIL import Image
import torch
import torch.nn.functional as F
from torchvision import transforms
import matplotlib.pyplot as plt

pil_to_torch = transforms.ToTensor()
res_shape = (128, 128)


pil_img = Image.open('Lenna.png')
torch_img = pil_to_torch(pil_img)

pil_image_scaled = pil_img.resize(res_shape, Image.BILINEAR)
torch_img_scaled = F.interpolate(torch_img.unsqueeze(0), res_shape, mode='bilinear').squeeze(0)

pil_image_scaled_on_torch = pil_to_torch(pil_image_scaled)
relative_diff = torch.abs((pil_image_scaled_on_torch - torch_img_scaled) / pil_image_scaled_on_torch).mean().item()
print('relative pixel diff:', relative_diff)

pil_image_scaled_numpy = pil_image_scaled_on_torch.cpu().numpy().transpose([1, 2, 0])
torch_img_scaled_numpy = torch_img_scaled.cpu().numpy().transpose([1, 2, 0])
plt.imsave('pil_scaled.png', pil_image_scaled_numpy)
plt.imsave('torch_scaled.png', torch_img_scaled_numpy)
plt.imsave('mean_diff.png', np.abs(pil_image_scaled_numpy - torch_img_scaled_numpy).mean(-1))

Python 3.6.6,要求:

cycler==0.10.0
kiwisolver==1.1.0
matplotlib==3.2.1
numpy==1.18.2
Pillow==7.0.0
pyparsing==2.4.6
python-dateutil==2.8.1
six==1.14.0
torch==1.4.0
torchvision==0.5.0
python image-processing pytorch python-imaging-library
3个回答
9
投票

“双线性插值”是一种插值方法。

但是缩小图像不一定只能使用插值来完成。

可以简单地将图像重新采样为较低的采样率,使用插值方法计算与旧样本不一致的新样本。但这会导致混叠(当图像中的较高频率分量无法以较低采样密度表示时,就会出现混叠现象,将这些较高频率的能量“混叠”到较低频率分量上;也就是说,新的低频分量出现在重采样后的图像)。

为了避免混叠,一些库在重采样之前应用低通滤波器(去除无法以较低采样频率表示的高频)。这些库中的子采样算法不仅仅是插值。

您看到的差异是因为这两个库采用不同的方法,一个尝试通过低通滤波来避免混叠,另一个则不然。

要在 Torch 中获得与 Pillow 中相同的结果,您需要自己显式地对图像进行低通滤波。为了获得相同的结果,您必须准确地弄清楚 Pillow 如何过滤图像,有不同的方法和不同的可能参数设置。查看源代码是了解它们到底做什么的最好方法。


2
投票

来自文档

torch.nn.functional.interpolate
antialias=True
结合使用可以得到与 Pillow 的实现相匹配的结果。


0
投票

虽然这是5年前的问题,但我仍然认为有必要给出一个明确的解释:Cris Luengo的回答方向是准确的。我已经阅读了 Pillow resample 的源代码。当Pillow使用双线性滤波器进行下采样时,虽然权重计算仍然遵循双线性公式,但权重窗口不仅仅是2x2,而是随着缩放比例而变化。也就是说,目标像素的信息来自于比附近四个像素更多的像素。

© www.soinside.com 2019 - 2024. All rights reserved.