目录9
文章/Matplotlib/第 19 章
图像显示
Matplotlib · 第 19 / 43 章
imshow() —— 显示图像 / 二维数组
imshow() 把二维数组铺成像素网格,一格一个颜色。名字来自「显示图像」,但日常更常用来看矩阵——因为它是所有二维渲染里最快的一种(整块位图一次性贴上去,不是逐个 Patch)。
Axes.imshow(
X, # 二维数组 → 伪彩色;三维 (M,N,3)/(M,N,4) → 真彩色
cmap=None, # 颜色映射,只对二维数组生效
norm=None, # 归一化对象,如 LogNorm() / TwoSlopeNorm(vcenter=0)
aspect=None, # ★ 'equal'(默认,格子必为正方形)/ 'auto'(铺满)
interpolation=None, # 放大时的插值:'nearest' / 'bilinear' / 'bicubic'
alpha=None,
vmin=None, vmax=None, # 色阶两端;多图对比必须固定
origin=None, # ★ 'upper'(默认,第 0 行在顶)/ 'lower'
extent=None, # ★ 物理坐标 [left, right, bottom, top]
interpolation_stage=None, # 'data' / 'rgba',一般不用
filternorm=True, filterrad=4.0,
resample=None, # 大数据量时设 False 提速
url=None,
data=None,
**kwargs
)| 参数 | 一句话 | 默认值的影响 |
|---|---|---|
X |
二维或三维数组 | 三维按 RGB(A) 处理 |
cmap |
颜色映射 | 三维数组时被忽略 |
aspect |
格子形状 | 'equal':非方阵会拉成细长条 |
interpolation |
放大插值 | None → 走 rcParam 'auto' |
origin |
原点位置 | 'upper':第 0 行在顶部 |
extent |
坐标范围 | 默认按索引,像素中心落在整数上 |
vmin / vmax |
色阶范围 | 不给就取数组 min/max |
alpha |
透明度 | 叠在别的图上时用 |
resample |
重采样 | 大图设 False 明显更快 |
origin:第 0 行在顶还是在底
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(42)
Z = rng.random((6, 6))
fig, ax = plt.subplots()
im = ax.imshow(Z)
fig.colorbar(im, ax=ax)
plt.show()
ax.imshow(Z) # 默认 'upper':第 0 行画在顶部,y 轴向下增长
ax.imshow(Z, origin="lower") # 第 0 行画在底部,y 轴向上增长判据很清楚:把数组当矩阵(行 = 样本序号、列 = 特征)→ upper;把数组当空间场(行 = y 坐标)→ lower。
默认 upper 是图像惯例,但它会让 y 轴刻度从上往下读——这也是「为什么我的热力图 y 轴是反的」的唯一原因。
extent:把索引换成真实坐标
默认坐标是索引,而且像素中心落在整数上:
im = ax.imshow(Z) # Z 是 6×6
print(im.get_extent())
# 输出: [-0.5, 5.5, 5.5, -0.5]四个数是 (left, right, bottom, top)。left/right 比列数差半个格子,是为了让像素中心落在整数上(第 0 列中心在 x=0)。
对 6×6 数组,bottom = 5.5 而 top = -0.5——bottom > top 就是 origin='upper' 的坐标表达:y 轴从上往下。换成 4×6 数组,它变成 [-0.5, 5.5, 3.5, -0.5],只有 bottom 跟着行数变。
换成真实单位:
ax.imshow(Z, extent=[0, 6, 0, 60], aspect="auto") # x 是 0~6,y 是 0~60extent 的四个值顺序是 [左, 右, 下, 上]。写成 [xmin, xmax, ymin, ymax] 恰好也是这个顺序,但如果 bottom > top,matplotlib 会理解成翻转 y 轴,不会报错——这是把 extent 写错却看不出问题的常见原因。

aspect:格子被不被拉变形
ax.imshow(Z) # 默认 'equal':一格必须正方形 → 非方阵变细长条
ax.imshow(Z, aspect="auto") # 铺满子图,格子被拉成矩形对一个 5×30 的矩阵,'equal' 会把图压成一条扁带;'auto' 才能看清每一格。看数值关系(热力图、相关性矩阵)用 auto;看图像本身(照片、地图)用 equal。
'equal' 不是写死在函数里的,来自 rcParam image.aspect:
print(plt.rcParams["image.aspect"])
# 输出: equal改 rcParams 就能全局改默认,但更推荐逐图传参——不然换个文件画图,行为就变了。
interpolation:放大时怎么补
ax.imshow(Z, interpolation="nearest") # 方块锐利,一格就是一格
ax.imshow(Z, interpolation="bilinear") # 双线性,颜色互相渗透
ax.imshow(Z, interpolation="bicubic") # 更平滑,边缘发虚默认是 None → 走 rcParam 'auto',规则是「数据比屏幕小时用 nearest,否则用 antialiased」。所以小矩阵放大显示时,默认看着就是方块。
两个必须记住的点:
- 插值只影响显示,不改数据。
im.get_array()拿到的还是原数组。 - 插值在导出 PDF/SVG 时同样生效(它是渲染期行为),所以别指望「导出就自动锐利」。
vmin / vmax:色阶的两端
ax.imshow(Z) # 自动取 min/max
ax.imshow(Z, vmin=0.2, vmax=0.8) # 超出部分饱和到两端颜色
ax.imshow(Z, vmin=0, vmax=1) # 固定到「理论范围」,多图可比多子图对比时必须固定 vmin/vmax,否则同一个蓝色在不同子图里代表不同数值,读者会得出错误结论。后置修改用 im.set_clim(vmin, vmax)。
三维数组:真彩色,cmap 失效
rgb = rng.random((20, 30, 3))
ax.imshow(rgb) # 当成 RGB 图像,cmap 被忽略
rgba = rng.random((20, 30, 4))
ax.imshow(rgba) # 第 4 通道是 alpha浮点数组的取值必须落在 [0, 1];超出的会被截断(不报错)。整数数组则按 uint8 解释(0~255)。
返回对象
im = ax.imshow(Z)
print(type(im).__name__)
# 输出: AxesImage
fig.colorbar(im, ax=ax) # 直接传,不要传 im.get_array()
print(im.get_clim())
# 输出: (0.04380376578722878, 0.9756223516367559) ← 就是 Z 的 min/max
im.set_clim(0, 1) # 改色阶(= 改对比度)
im.set_cmap("jet") # 改配色
print(im.get_extent())
# 输出: [-0.5, 5.5, 5.5, -0.5]
im.set_data(Z * 2) # 换数据:动画里比重复 imshow 快得多
plt.show()AxesImage 一个对象管整张位图。做动画/交互时用 set_data() + fig.canvas.draw_idle(),不要每帧都 imshow——后者会不断新建 Artist,几分钟就卡死。
注意事项
origin默认'upper',第 0 行在顶部,y 轴向下——和plot完全相反。aspect默认'equal',非方阵矩阵会被拉成细长条,这是「热力图为什么这么小」的原因。extent的顺序是[左, 右, 下, 上],bottom > top表示 y 轴翻转,不报错。interpolation只影响显示,get_array()拿到的永远是原数据。- 三维数组走 RGB(A) 模式,此时
cmap/vmin/vmax全部失效。 - 大数组导出 PDF/SVG 会生成巨大的矢量文件,加
rasterized=True把它压成位图。 colorbar传的是im这个 Artist,不是数组。
综合示例:热力图 / 相关系数矩阵 / RGB 图
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False
rng = np.random.default_rng(3)
field = np.sin(np.add.outer(np.linspace(0, 6, 40), np.linspace(0, 9, 60)))
fig, axes = plt.subplots(1, 3, figsize=(15, 4.2))
# ---- (a) 场:origin='lower' + extent 换成真实坐标 ----
im0 = axes[0].imshow(field, origin="lower", extent=[0, 9, 0, 6],
aspect="auto", cmap="RdBu_r", vmin=-1, vmax=1)
axes[0].set_xlabel("x (m)")
axes[0].set_ylabel("y (m)")
fig.colorbar(im0, ax=axes[0])
axes[0].set_title("(a) origin='lower' + extent")
# ---- (b) 相关系数矩阵:aspect='auto',格子看得清 ----
corr = np.array([[1.00, 0.72, -0.35, 0.18],
[0.72, 1.00, -0.21, 0.44],
[-0.35, -0.21, 1.00, -0.60],
[0.18, 0.44, -0.60, 1.00]])
names = ["价格", "销量", "评分", "库存"]
im1 = axes[1].imshow(corr, cmap="RdBu_r", vmin=-1, vmax=1, aspect="auto")
axes[1].set_xticks(range(4), names, rotation=30)
axes[1].set_yticks(range(4), names)
for i in range(4):
for j in range(4):
axes[1].text(j, i, f"{corr[i, j]:.2f}", ha="center", va="center",
fontsize=9, color="white" if abs(corr[i, j]) > 0.6 else "black")
fig.colorbar(im1, ax=axes[1])
axes[1].set_title("(b) 相关系数矩阵(对称固定色阶)")
# ---- (c) 三维数组 = 真彩色 ----
rgb = np.zeros((60, 90, 3))
rgb[:, :45, 0] = np.linspace(0.2, 1.0, 45) # 左半:红渐变
rgb[:, 45:, 2] = np.linspace(0.2, 1.0, 45) # 右半:蓝渐变
rgb[25:35, :, 1] = 0.85 # 中间绿条纹
axes[2].imshow(rgb, interpolation="nearest")
axes[2].set_title("(c) (M,N,3) 真彩色(cmap 无效)")
plt.tight_layout()
plt.show()(b) 是 imshow 最高频的实战用法:对称色阶(vmin=-1, vmax=1 配发散色图 RdBu_r)加上格内数字标注,一张图同时给出形状和数值。