图像教程#

使用 Matplotlib 绘制图像的简短教程。

启动命令#

首先,让我们启动 IPython。它是标准 Python 提示符的一个非常优秀的增强工具,并且它与 Matplotlib 的结合尤其好。可以通过在 shell 中直接启动 IPython,或者使用 Jupyter Notebook(其中 IPython 作为运行内核)来启动 IPython。

启动 IPython 后,我们现在需要连接到 GUI 事件循环。这告诉 IPython 在哪里(以及如何)显示图表。要连接到 GUI 循环,请在 IPython 提示符下执行 %matplotlib 魔法。有关此操作的具体细节,请参阅 IPython 关于 GUI 事件循环的文档

如果你在使用 Jupyter Notebook,同样的命令是可用的,但人们通常会使用 %matplotlib 魔法的一个特定参数:

In [1]: %matplotlib inline

这会开启内联绘图功能,绘图图形将出现在您的笔记本中。这对交互性有重要影响。对于内联绘图,输出绘图单元格下方的单元格中的命令不会影响该绘图。例如,从创建绘图的单元格下方的单元格中更改颜色映射是不可能的。然而,对于其他后端,如Qt,它们会打开一个单独的窗口,创建绘图的单元格下方的单元格将改变绘图——它在内存中是一个活动的对象。

本教程将使用 Matplotlib 的隐式绘图接口,pyplot。这个接口维护全局状态,对于快速轻松地试验各种绘图设置非常有用。另一种选择是显式接口,更适合大型应用程序开发。有关隐式和显式接口之间权衡的解释,请参见 Matplotlib 应用程序接口 (APIs)快速入门指南 以开始使用显式接口。现在,让我们继续使用隐式方法:

from PIL import Image

import matplotlib.pyplot as plt
import numpy as np

将图像数据导入到 Numpy 数组中#

Matplotlib 依赖 Pillow 库来加载图像数据。

这是我们将要处理的图像:

../_images/stinkbug.png

这是一张24位的RGB PNG图像(R、G、B各8位)。根据你获取数据的地方,你最可能遇到的其它类型的图像是RGBA图像,它们允许透明度,或者是单通道的灰度(亮度)图像。下载 stinkbug.png 到你的电脑上,以便继续本教程的其余部分。

我们使用 Pillow 打开一张图片(通过 PIL.Image.open),并立即将 PIL.Image.Image 对象转换为一个 8 位(dtype=uint8)的 numpy 数组。

img = np.asarray(Image.open('../../doc/_static/stinkbug.png'))
print(repr(img))
array([[[104, 104, 104],
        [104, 104, 104],
        [104, 104, 104],
        ...,
        [109, 109, 109],
        [109, 109, 109],
        [109, 109, 109]],

       [[105, 105, 105],
        [105, 105, 105],
        [105, 105, 105],
        ...,
        [109, 109, 109],
        [109, 109, 109],
        [109, 109, 109]],

       [[107, 107, 107],
        [106, 106, 106],
        [106, 106, 106],
        ...,
        [110, 110, 110],
        [110, 110, 110],
        [110, 110, 110]],

       ...,

       [[112, 112, 112],
        [111, 111, 111],
        [110, 110, 110],
        ...,
        [116, 116, 116],
        [115, 115, 115],
        [115, 115, 115]],

       [[113, 113, 113],
        [113, 113, 113],
        [112, 112, 112],
        ...,
        [115, 115, 115],
        [114, 114, 114],
        [114, 114, 114]],

       [[113, 113, 113],
        [115, 115, 115],
        [115, 115, 115],
        ...,
        [114, 114, 114],
        [114, 114, 114],
        [113, 113, 113]]], dtype=uint8)

每个内部列表代表一个像素。在这里,对于RGB图像,有3个值。由于它是黑白图像,R、G和B的值都相似。RGBA图像(其中A代表alpha,即透明度)在每个内部列表中有4个值,而简单的亮度图像只有一个值(因此它只是一个二维数组,而不是三维数组)。对于RGB和RGBA图像,Matplotlib支持float32和uint8数据类型。对于灰度图像,Matplotlib仅支持float32。如果您的数组数据不符合这些描述之一,您需要对其进行重新缩放。

将 numpy 数组绘制为图像#

因此,你已经将数据存储在一个 numpy 数组中(无论是通过导入数据,还是通过生成数据)。让我们来渲染它。在 Matplotlib 中,这是通过使用 imshow() 函数来完成的。在这里,我们将获取绘图对象。这个对象为你提供了一种从提示符轻松操作绘图的方法。

imgplot = plt.imshow(img)
images

你也可以绘制任何 numpy 数组。

将伪彩色方案应用于图像绘图#

伪彩色可以是一个增强对比度和更容易可视化数据的实用工具。这在使用投影仪展示数据时尤其有用——它们的对比度通常很差。

伪彩色仅与单通道、灰度、亮度图像相关。我们目前有一个RGB图像。由于R、G和B都相似(如上或查看您的数据所示),我们可以使用数组切片选择数据的一个通道(您可以在 Numpy教程 中阅读更多内容):

lum_img = img[:, :, 0]
plt.imshow(lum_img)
images

现在,对于一个亮度(2D,无颜色)图像,默认的色图(也称为查找表,LUT)被应用。默认的色图称为viridis。还有许多其他可供选择。

plt.imshow(lum_img, cmap="hot")
images

请注意,您也可以使用 set_cmap() 方法更改现有绘图对象的色图:

imgplot = plt.imshow(lum_img)
imgplot.set_cmap('nipy_spectral')
images

备注

然而,请记住,在使用内联后端的 Jupyter Notebook 中,你不能对已经渲染的图表进行更改。如果你在一个单元格中创建了 imgplot,你不能在后面的单元格中对其调用 set_cmap() 并期望早先的图表发生变化。请确保你在同一个单元格中输入这些命令。plt 命令不会改变之前单元格中的图表。

还有许多其他的色图方案可用。请参阅 色图列表和图像

颜色标尺参考#

了解一种颜色代表的值是很有帮助的。我们可以通过在图形中添加一个颜色条来实现这一点:

imgplot = plt.imshow(lum_img)
plt.colorbar()
images

检查特定数据范围#

有时你想增强图像的对比度,或在特定区域扩展对比度,同时牺牲那些变化不大或不重要的颜色的细节。一个好的工具是直方图。要创建图像数据的直方图,我们使用 hist() 函数。

plt.hist(lum_img.ravel(), bins=range(256), fc='k', ec='k')
images

通常,图像的“有趣”部分位于峰值附近,通过裁剪峰值上方和/或下方的区域,您可以获得额外的对比度。在我们的直方图中,看起来高端没有太多有用的信息(图像中没有很多白色物体)。让我们调整上限,以便我们有效地“放大”直方图的一部分。我们通过设置 clim,即颜色映射限制来实现这一点。

这可以通过在调用 imshow 时传递一个 clim 关键字参数来实现。

plt.imshow(lum_img, clim=(0, 175))
images

这也可以通过调用返回的图像绘图对象的 set_clim() 方法来完成,但在使用 Jupyter Notebook 时,请确保在绘图命令的同一单元格中进行操作 - 它不会更改之前单元格中的绘图。

imgplot = plt.imshow(lum_img)
imgplot.set_clim(0, 175)
images

数组插值方案#

插值计算根据不同的数学方案来确定一个像素的“应该”颜色或值。当你调整图像大小时,这种情况经常发生。像素数量会改变,但你希望保持相同的信息。由于像素是离散的,因此会有缺失的空间。插值就是如何填充这些空间的方法。这就是为什么当你放大图像时,图像有时会显得像素化。当原始图像和放大图像之间的差异越大时,这种效果越明显。让我们缩小图像。我们实际上是在丢弃像素,只保留少数几个。现在当我们绘制它时,这些数据会被放大到屏幕上的大小。旧的像素不再存在,计算机必须绘制新的像素来填充这些空间。

我们将使用 Pillow 库,该库用于加载图像,同时也用于调整图像大小。

img = Image.open('../../doc/_static/stinkbug.png')
img.thumbnail((64, 64))  # resizes image in-place
imgplot = plt.imshow(img)
images

这里我们使用了默认的插值方法(“nearest”),因为我们没有给 imshow() 提供任何插值参数。

让我们尝试一些其他的。这里是“双线性”:

imgplot = plt.imshow(img, interpolation="bilinear")
images

和双三次插值:

imgplot = plt.imshow(img, interpolation="bicubic")
images

双三次插值在放大照片时常被使用 - 人们往往更喜欢模糊而不是像素化。

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

由 Sphinx-Gallery 生成的图库