1 图像教程
matplotlib可以简单地处理并显示图像,当然,它不是一个专业的图像处理库,所以也不要拿它来与opencv进行比较。
2 启动命令
首先,让我们启动IPython。它是对标准python提示符的最出色的增强,它与matplotlib的关系密切。直接在shell上启动IPython,或者使用Jupyter Notebook(其中IPython作为正在运行的内核)。
随着IPython的启动,我们现在需要连接到GUI事件循环。这告诉IPython在哪里(以及如何)显示绘图。要连接到GUI循环,请在IPython提示符下执行%matplotlib魔法。有关其确切作用的更多详细信息,请参阅IPythonu关于GUI事件循环的文档。
如果你使用的是Jupyter Notebook(或者Jupyter Lab),则可以使用相同的命令,但人们通常使用%matplotlib魔术的特定参数:
%matplotlib inline
以下是在jupyter lab中的实际操作情况
这将打开内联绘图,其中绘图图形将显示在笔记本中。这对交互性具有重要意义。对于内联绘图,输出绘图的单元格下方单元格中的命令不会影响绘图。例如,无法从创建绘图的单元格下方的单元格更改颜色映射表。但是,对于打开单独窗口的其他后端,例如Qt,创建绘图的单元格正文的单元格将更改绘图——它是内存中的活动对象。
本教程将使用matplotlib的隐式绘图接口pyplot。此界面保持全局状态,对于快速轻松地尝试各种绘图设置非常有用,另一种选择是显式的,它更适合大型应用程序开发。有关隐式接口和显式接口之间的权衡说明,请参阅matplotlib应用程序接口(API)和开始使用显示接口的快速入门指南。现在,让我们继续使用隐式方法:
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
3 将图像数据导入Numpy数组
matplotlib 通过Pillow库来载入图像数据。
我们用来演示的图像是这一张:
这是一个24位RGB PNG格式的图像(R、G、B各8位)。根据你获取数据的位置,你最可能遇到的其他类型的图像是允许透明度的RGBA图像或者单通道(灰度)图像。
我们使用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。如果数组数据不符合这些描述之一,则需要重新调整它。
4 将numpy数组像图像一样绘制出来
因此,你将数据放在numpy数组中(通过导入或生成它),让我们渲染它。在matplotlib中,这是使用imshow()函数执行的(有意思的是在opencv中显示图像也是相同名称的函数)。在这里,我们将抓取绘图对象。此对象为你提供了一种从提示语操作绘图的简单方法。
imgplot = plt.imshow(img)
你可以绘制任意的numpy数组(当然不一定是有序的图片,也可能是随机数数组)。
将伪彩色方案应用于图像
伪彩色可以成为增强对比度和更轻松地可视化数据的有用工具。这在使用投影仪学示数据时特别有用——它们的对比度通常很差。
伪彩色仅与单通道、灰度、亮度图像有关。我们目前有一个RGB图像。由于R、G、B都是相同的,我们可以使用数组切片来选择数据的一个通道(你可以在numpy教程中内容):
现在,对于灰度(2D,无颜色)图像,将应用默认颜色图(也称为查找表,LUT)。默认值称为viridis,但还有其他多种配色方案可供选择。
plt.imshow(lum_img, cmap="hot")
请注意,还可以使用 set_cmap()
方法更改现有绘图对象上的颜色图:
imgplot = plt.imshow(lum_img)
imgplot.set_cmap('nipy_spectral')
注意
但是,请记住,在具有内联后端的 Jupyter 笔记本中,无法对已呈现的绘图进行更改。如果在一个单元格中创建 imgplot,则不能在后面的单元格中对其调用set_cmap()
并期望较早的绘图发生变化。确保在一个单元格中一起输入这些命令。plt 命令不会更改早期单元格的绘图。
色标参考
了解颜色代表什么值是有帮助的。我们可以通过为你的图形来添加颜色条来做到这一点:
imgplot = plt.imshow(lum_img)
plt.colorbar()
检视特定的数据分布
有时,你希望增强图像中的对比度,或扩展特定区域的对比度,同时牺牲变化不大或无关紧要的颜色的细节。查找感兴越区域的一个好工具是直方图。为了创建图像数据的直方图,我们使用hist()
函数。
plt.hist(lum_img.ravel(), bins=range(256), fc='k', ec='k')
大多数情况下,图像的“感兴趣”部分在峰值附近,你可以通过剪切峰值上方和/或下方的区域来获得额外的对比度。在我们的直方图中,高端看起来没有太多有用的信息(图像中没有很多白色的东西)。让我们调整上限,以便有效地“放大”直方图的一部分。我们通过设置clim,即颜色图限制来做到这一点。
这可以通过在调用 imshow 时传递 clim 关键字参数来完成。
plt.imshow(lum_img, clim=(0, 175))
这也可以通过调用返回的图像绘图对象的 set_clim() 方法来完成,但请确保在使用 Jupyter Notebook 时在与 plot 命令相同的单元格中执行此操作 ——它不会更改早期单元格的绘图。
imgplot = plt.imshow(lum_img)
imgplot.set_clim(0, 175)
数组插值方案
插值根据不同的数学方案计算像素“应该”的颜色或值。发生这种情况的一个常见位置是调整图像大小时。像素数会发生变化,但你需要相同的信息。由于像素是离散的,因此缺少空间。插值是填充该空间的方式。这就是为什么当你严重压缩图像时,有时会看起来像素化的原因。当原始图像和扩展图像之间的差异较大时,效果更明显。让我们采用我们的图像并缩小它。我们实际上丢弃了像素,只保留了少数几个像素。现在,当我们绘制它时,这些数据会被放大到屏幕上的大小。旧的像素不再存在,计算机必须绘制像素来填充该空间。
我们将使用用于加载图像的Pillow来调整图像大小。
img = Image.open('../../doc/_static/stinkbug.png')
img.thumbnail((64, 64)) # resizes image in-place
imgplot = plt.imshow(img)
这里我们使用默认的插值“nearest”(即最近邻插值),因为我们没有给 imshow() 任何插值参数。
让我们再看看双线性插值的效果:
imgplot = plt.imshow(img, interpolation="bilinear")
以及双立方:
imgplot = plt.imshow(img, interpolation="bicubic")
缩小图像时经常使用双立方插值——人们往往更能接受模糊的图像而不是严重像素化的图像。