注意
转到末尾下载完整的示例代码。
图像重采样#
图像由分配有颜色值的离散像素表示,无论是在屏幕上还是在图像文件中。当用户使用数据数组调用imshow
时,数据数组的大小很少能精确匹配图中分配给图像的像素数量,因此Matplotlib会重采样或缩放数据或图像以适应。如果数据数组大于渲染图中分配的像素数量,则图像将被“下采样”,图像信息将丢失。相反,如果数据数组小于输出像素数量,则每个数据点将获得多个像素,图像将被“上采样”。
在下图中,第一个数据数组的大小为 (450, 450),但在图中由远少于此的像素表示,因此进行了下采样。第二个数据数组的大小为 (4, 4),由远多于此的像素表示,因此进行了上采样。
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(1, 2, figsize=(4, 2))
# First we generate a 450x450 pixel image with varying frequency content:
N = 450
x = np.arange(N) / N - 0.5
y = np.arange(N) / N - 0.5
aa = np.ones((N, N))
aa[::2, :] = -1
X, Y = np.meshgrid(x, y)
R = np.sqrt(X**2 + Y**2)
f0 = 5
k = 100
a = np.sin(np.pi * 2 * (f0 * R + k * R**2 / 2))
# make the left hand side of this
a[:int(N / 2), :][R[:int(N / 2), :] < 0.4] = -1
a[:int(N / 2), :][R[:int(N / 2), :] < 0.3] = 1
aa[:, int(N / 3):] = a[:, int(N / 3):]
alarge = aa
axs[0].imshow(alarge, cmap='RdBu_r')
axs[0].set_title('(450, 450) Down-sampled', fontsize='medium')
np.random.seed(19680801+9)
asmall = np.random.rand(4, 4)
axs[1].imshow(asmall, cmap='viridis')
axs[1].set_title('(4, 4) Up-sampled', fontsize='medium')

Matplotlib 的 imshow
方法有两个关键字参数,允许用户控制如何执行重采样。interpolation 关键字参数允许选择用于重采样的核,在下采样时进行抗锯齿过滤,或者在上采样时平滑像素。interpolation_stage 关键字参数决定此平滑核是应用于底层数据,还是应用于 RGBA 像素。
interpolation_stage='rgba'
: 数据 -> 归一化 -> RGBA -> 插值/重采样
interpolation_stage='data'
: 数据 -> 插值/重采样 -> 归一化 -> RGBA
对于这两个关键字参数,Matplotlib 都有一个默认的“抗锯齿”("antialiased"),这在大多数情况下都推荐使用,并将在下文描述。请注意,如果图像正在进行下采样或上采样,此默认值的行为会有所不同,如下文所述。
下采样和适度上采样#
当下采样数据时,我们通常希望通过先平滑图像再对其进行子采样来消除锯齿。在 Matplotlib 中,我们可以在将数据映射到颜色之前进行平滑处理,或者在 RGB(A) 图像像素上进行平滑处理。这些差异如下所示,并由 interpolation_stage 关键字参数控制。
以下图像从 450 个数据像素下采样到大约 125 个或 250 个像素(取决于您的显示器)。底层图像在左侧有交替的 +1、-1 条纹,在图像的其余部分有一个可变波长(Chirp)模式。如果我们放大,可以在没有任何下采样的情况下看到这个细节。
fig, ax = plt.subplots(figsize=(4, 4), layout='compressed')
ax.imshow(alarge, interpolation='nearest', cmap='RdBu_r')
ax.set_xlim(100, 200)
ax.set_ylim(275, 175)
ax.set_title('Zoom')

如果进行下采样,最简单的算法是使用最近邻插值对数据进行抽取。我们可以在数据空间或 RGBA 空间中执行此操作。

最近邻插值在数据空间和 RGBA 空间中是相同的,并且都呈现莫尔条纹,因为高频数据正在被下采样并表现为低频模式。我们可以在渲染图像之前通过应用抗锯齿滤波器来减少莫尔条纹。
fig, axs = plt.subplots(1, 2, figsize=(5, 2.7), layout='compressed')
for ax, interp, space in zip(axs.flat, ['hanning', 'hanning'],
['data', 'rgba']):
ax.imshow(alarge, interpolation=interp, interpolation_stage=space,
cmap='RdBu_r')
ax.set_title(f"interpolation='{interp}'\nstage='{space}'")
plt.show()

Hanning 滤波器平滑底层数据,使得每个新像素都是原始底层像素的加权平均值。这大大减少了莫尔条纹。然而,当 interpolation_stage 设置为 'data' 时,它也会在图像中引入原始数据中没有的白色区域,无论是在图像左侧的交替条带中,还是在图像中部大圆的红色和蓝色边界处。在 'rgba' 阶段的插值有不同的伪影,交替条带呈现紫色;尽管紫色不在原始颜色图中,但当蓝色和红色条纹彼此接近时,我们就会感知到这种颜色。
interpolation 关键字参数的默认值是 'auto',它会在图像下采样或上采样因子小于三时选择 Hanning 滤波器。默认的 interpolation_stage 关键字参数也是 'auto',对于下采样或上采样因子小于三的图像,它默认为 'rgba' 插值。
即使是上采样,也需要抗锯齿滤波。下图将 450 个数据像素上采样到 530 个渲染像素。您可能会注意到一些网格状的线条伪影,这些伪影源于必须生成的额外像素。由于插值是“最近邻”('nearest'),它们与相邻的像素行相同,因此会在局部拉伸图像,使其看起来失真。
fig, ax = plt.subplots(figsize=(6.8, 6.8))
ax.imshow(alarge, interpolation='nearest', cmap='grey')
ax.set_title("up-sampled by factor a 1.17, interpolation='nearest'")

更好的抗锯齿算法可以减少这种效果
fig, ax = plt.subplots(figsize=(6.8, 6.8))
ax.imshow(alarge, interpolation='auto', cmap='grey')
ax.set_title("up-sampled by factor a 1.17, interpolation='auto'")

除了默认的 'hanning' 抗锯齿,imshow
还支持多种不同的插值算法,这些算法根据底层数据的不同可能会表现得更好或更差。

最后一个例子说明了在使用非平凡插值核时,在 RGBA 阶段执行抗锯齿的必要性。在下文中,前 100 行的数据正好是 0.0,内圆中的数据正好是 2.0。如果我们在 'data' 空间中执行 interpolation_stage 并使用抗锯齿滤波器(第一个面板),那么浮点不精确性会使某些数据值略小于零或略大于 2.0,从而被分配到下限或上限颜色。如果不定用抗锯齿滤波器(interpolation 设置为 'nearest'),可以避免这种情况,但这会使数据中易受莫尔条纹影响的部分变得更糟(第二个面板)。因此,对于大多数下采样情况,我们推荐使用默认的 interpolation 'hanning'/'auto' 和 interpolation_stage 'rgba'/'auto'(最后一个面板)。
a = alarge + 1
cmap = plt.get_cmap('RdBu_r')
cmap.set_under('yellow')
cmap.set_over('limegreen')
fig, axs = plt.subplots(1, 3, figsize=(7, 3), layout='constrained')
for ax, interp, space in zip(axs.flat,
['hanning', 'nearest', 'hanning', ],
['data', 'data', 'rgba']):
im = ax.imshow(a, interpolation=interp, interpolation_stage=space,
cmap=cmap, vmin=0, vmax=2)
title = f"interpolation='{interp}'\nstage='{space}'"
if ax == axs[2]:
title += '\nDefault'
ax.set_title(title, fontsize='medium')
fig.colorbar(im, ax=axs, extend='both', shrink=0.8)

上采样#
如果进行上采样,我们可以用许多图像或屏幕像素来表示一个数据像素。在以下示例中,我们大幅度过采样了小型数据矩阵。
np.random.seed(19680801+9)
a = np.random.rand(4, 4)
fig, axs = plt.subplots(1, 2, figsize=(6.5, 4), layout='compressed')
axs[0].imshow(asmall, cmap='viridis')
axs[0].set_title("interpolation='auto'\nstage='auto'")
axs[1].imshow(asmall, cmap='viridis', interpolation="nearest",
interpolation_stage="data")
axs[1].set_title("interpolation='nearest'\nstage='data'")
plt.show()

如果需要,可以使用 interpolation 关键字参数来平滑像素。然而,这几乎总是在数据空间中完成更好,而不是在 RGBA 空间中,因为在该空间中,滤镜可能导致插值结果出现不在颜色图中的颜色。在以下示例中,请注意当插值为 'rgba' 时,会出现红色作为插值伪影。因此,当上采样因子大于三时,interpolation_stage 的默认 'auto' 选项设置为与 'data' 相同。
fig, axs = plt.subplots(1, 2, figsize=(6.5, 4), layout='compressed')
im = axs[0].imshow(a, cmap='viridis', interpolation='sinc', interpolation_stage='data')
axs[0].set_title("interpolation='sinc'\nstage='data'\n(default for upsampling)")
axs[1].imshow(a, cmap='viridis', interpolation='sinc', interpolation_stage='rgba')
axs[1].set_title("interpolation='sinc'\nstage='rgba'")
fig.colorbar(im, ax=axs, shrink=0.7, extend='both')

避免重采样#
在创建图像时可以避免对数据进行重采样。一种方法是简单地保存为矢量后端(pdf, eps, svg)并使用 interpolation='none'
。矢量后端允许嵌入图像,但请注意,某些矢量图像查看器可能会平滑图像像素。
第二种方法是精确匹配坐标轴大小与数据大小。下图精确为 2 英寸 x 2 英寸,如果 dpi 为 200,则 400x400 的数据完全不会被重采样。如果您下载此图像并在图像查看器中放大,您应该能看到左侧的单个条纹(请注意,如果您使用的是非高 DPI 或“视网膜”屏幕,HTML 可能会提供 100x100 版本的图像,这将进行下采样)。
fig = plt.figure(figsize=(2, 2))
ax = fig.add_axes([0, 0, 1, 1])
ax.imshow(aa[:400, :400], cmap='RdBu_r', interpolation='nearest')
plt.show()

脚本总运行时间: (0 分 10.892 秒)