注意
转到末尾下载完整的示例代码。
图像重采样#
图像由分配了颜色值的离散像素表示,无论是在屏幕上还是在图像文件中。当用户使用数据数组调用 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 都有一个默认的“抗锯齿”,建议用于大多数情况,如下所述。请注意,如果图像被下采样或上采样,则此默认行为会有所不同,如下所述。
下采样和适度上采样#
当下采样数据时,我们通常希望通过首先平滑图像然后再对其进行子采样来消除锯齿。在 Matplotlib 中,我们可以在将数据映射到颜色之前执行平滑处理,或者可以在 RGB(A) 图像像素上执行平滑处理。这些之间的差异如下所示,并使用 interpolation_stage 关键字参数控制。
以下图像从 450 个数据像素下采样到大约 125 个像素或 250 个像素(具体取决于您的显示器)。基础图像的左侧具有交替的 +1、-1 条纹,其余图像具有变化的波长(啁啾)图案。如果我们放大,我们可以看到这个细节而无需任何下采样
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()
汉宁滤波器平滑了基础数据,因此每个新像素都是原始基础像素的加权平均值。这大大减少了莫尔图案。但是,当 interpolation_stage 设置为“data”时,它还在图像中引入了原始数据中不存在的白色区域,无论是在图像左侧的交替条带中,还是在图像中间的大圆的红色和蓝色之间的边界中。在“rgba”阶段的插值具有不同的伪影,交替的条带呈现出紫色阴影;即使紫色不在原始颜色图中,但当蓝色和红色条纹彼此靠近时,我们也会感知到这种颜色。
插值 关键字参数的默认值为 'auto',如果图像的缩小或放大倍数小于 3 倍,则会选择 Hanning 滤波器。默认的 interpolation_stage 关键字参数也是 'auto',对于缩小或放大倍数小于 3 倍的图像,默认使用 '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 空间中,滤波器可能会导致插值产生不在颜色映射中的颜色。在下面的示例中,请注意,当插值为 'rgba' 时,会出现红色作为插值伪影。因此,当放大倍数大于 3 倍时,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 的数据根本不会被重采样。如果您下载此图像并在图像查看器中放大,您应该可以看到左侧的各个条纹(请注意,如果您使用的是非 hiDPI 或“视网膜”屏幕,则 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 分钟 11.438 秒)