图像教程#

一个关于使用 Matplotlib 绘制图像的简短教程.

启动命令#

首先,让我们启动 IPython. 它是对标准 Python 提示符的极好增强,并且与 Matplotlib 的结合特别好. 直接在 shell 中或使用 Jupyter Notebook(其中 IPython 作为一个运行内核)启动 IPython.

启动 IPython 后,我们现在需要连接到 GUI 事件循环. 这告诉 IPython 在哪里(以及如何)显示绘图. 要连接到 GUI 循环,请在 IPython 提示符下执行 %matplotlib magic. 有关这具体做什么的更多详细信息,请参见 IPython's documentation on GUI event loops .

如果你使用 Jupyter Notebook,相同的命令可用,但人们通常使用 %matplotlib magic 的特定参数:

In [1]: %matplotlib inline

这将启用内联绘图,其中绘图图形将出现在你的 notebook 中. 这对交互性有重要的影响. 对于内联绘图,低于输出绘图的单元格中的命令将不会影响该绘图. 例如,无法从创建绘图的单元格下方的单元格更改颜色图. 但是,对于诸如 Qt 之类的其他后端,它们会打开一个单独的窗口,低于创建绘图的单元格将更改绘图 - 它是内存中的活动对象.

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

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]]], shape=(375, 500, 3), dtype=uint8)

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

将 numpy 数组绘制为图像#

因此,您的数据在一个 numpy 数组中(无论是通过导入还是生成).让我们渲染它.在 Matplotlib 中,这是使用 imshow() 函数执行的.在这里,我们将获取绘图对象.此对象使您可以轻松地从提示符处操作绘图.

imgplot = plt.imshow(img)
images

您也可以绘制任何 numpy 数组.

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

伪彩色可能是增强对比度并更轻松地可视化数据的有用工具.当使用投影仪进行数据演示时,这尤其有用 - 它们的对比度通常很差.

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

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 命令不会更改来自较早单元格的绘图.

还有许多其他可用的颜色图方案.请参阅 list and images of the colormaps .

颜色标尺参考#

了解颜色代表什么值很有帮助.我们可以通过向图中添加颜色条来实现:

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() 任何插值参数.

让我们试试其他的.这是 "bilinear":

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

以及 bicubic:

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

双三次插值通常用于放大照片 - 人们往往喜欢模糊而不是像素化.

该脚本的总运行时间:(0 分 4.312 秒)

Gallery generated by Sphinx-Gallery