3.5.md 9.9 KB
Newer Older
W
3,5  
wizardforcel 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
# 3.5 图像教程

> 原文:[Image tutorial](http://matplotlib.org/users/image_tutorial.html)

> 译者:[飞龙](https://github.com/)

> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)

## 启动命令

首先,让我们启动 IPython。 它是 Python 标准提示符的最好的改进,它与 Matplotlib 配合得相当不错。 在 shell 或 IPython Notebook 上都可以启动 IPython。

随着 IPython 启动,我们现在需要连接到 GUI 事件循环。 它告诉 IPython 在哪里(以及如何显示)绘图。 要连接到 GUI 循环,请在 IPython 提示符处执行`%matplotlib`魔法。 在 [IPython 的 GUI 事件循环文档](http://ipython.org/ipython-doc/2/interactive/reference.html#gui-event-loop-support)中有更多的细节。

如果使用 IPython Notebook,可以使用相同的命令,但人们通常以特定参数使用`%matplotlib`

```
In [1]: %matplotlib inline
```

这将打开内联绘图,绘图图形将显示在笔记本中。 这对交互性有很重要的影响。 对于内联绘图,在单元格下方的单元格中输出绘图的命令不会影响绘图。 例如,从创建绘图的单元格下面的单元格更改颜色表是不可能的。 但是,对于其他后端,例如 qt4,它们会打开一个单独的窗口,那些创建绘图的单元格下方的单元格将改变绘图 - 它是一个内存中的活对象。

本教程将使用`matplotlib`的命令式绘图接口`pyplot`。 该接口维护全局状态,并且可用于简单快速地尝试各种绘图设置。 另一种是面向对象的接口,这也非常强大,一般更适合大型应用程序的开发。 如果你想了解面向对象接口,[使用上的常见问题](http://matplotlib.org/faq/usage_faq.html)是一个用于起步的不错的页面。 现在,让我们继续使用命令式方式:

```py
In [2]: import matplotlib.pyplot as plt
In [3]: import matplotlib.image as mpimg
In [4]: import numpy as np
```

## 将图像数据导入到 NumPy 数组

加载图像数据由 Pillow 库提供支持。 本来,`matplotlib`只支持 PNG 图像。 如果本机读取失败,下面显示的命令会回退到 Pillow。

W
fix  
wizardforcel 已提交
35
此示例中使用的图像是 PNG 文件,但是请记住你自己的数据的 Pillow 要求。
W
3,5  
wizardforcel 已提交
36 37 38 39 40

下面是我们要摆弄的图片:

![](http://matplotlib.org/_images/stinkbug.png)

W
fix  
wizardforcel 已提交
41
它是一个 24 位 RGB PNG 图像(每个 R,G,B 为 8 位)。 根据你获取数据的位置,你最有可能遇到的其他类型的图像是 RGBA 图像,拥有透明度或单通道灰度(亮度)的图像。 你可以右键单击它,选择`Save image as`(另存为)为本教程的剩余部分下载到你的计算机。
W
3,5  
wizardforcel 已提交
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

现在我们开始...

```py
In [5]: img=mpimg.imread('stinkbug.png')
Out[5]:
array([[[ 0.40784314,  0.40784314,  0.40784314],
        [ 0.40784314,  0.40784314,  0.40784314],
        [ 0.40784314,  0.40784314,  0.40784314],
        ...,
        [ 0.42745098,  0.42745098,  0.42745098],
        [ 0.42745098,  0.42745098,  0.42745098],
        [ 0.42745098,  0.42745098,  0.42745098]],

       ...,
       [[ 0.44313726,  0.44313726,  0.44313726],
        [ 0.4509804 ,  0.4509804 ,  0.4509804 ],
        [ 0.4509804 ,  0.4509804 ,  0.4509804 ],
        ...,
        [ 0.44705883,  0.44705883,  0.44705883],
        [ 0.44705883,  0.44705883,  0.44705883],
        [ 0.44313726,  0.44313726,  0.44313726]]], dtype=float32)
```

注意这里的`dtype` - `float32`。 Matplotlib 已将每个通道的8位数据重新定标为 0.0 和 1.0 之间的浮点数。 作为旁注,Pillow 可以使用的唯一数据类型是`uint8`。 Matplotlib 绘图可以处理`float32``uint8`,但是对于除 PNG 之外的任何格式的图像,读取/写入仅限于`uint8`数据。 为什么是 8 位呢? 大多数显示器只能渲染每通道 8 位的颜色渐变。 为什么他们只能渲染每通道 8 位呢? 因为这会使所有人的眼睛可以看到。 更多信息请见(从摄影的角度):[Luminous Landscape 位深度教程](http://www.luminous-landscape.com/tutorials/bit-depth.shtml)

W
fix  
wizardforcel 已提交
68
每个内部列表表示一个像素。 这里,对于 RGB 图像,有 3 个值。 由于它是一个黑白图像,R,G 和 B 都是类似的。 RGBA(其中 A 是阿尔法或透明度)对于每个内部列表具有 4 个值,而且简单亮度图像仅具有一个值(因此仅是二维数组,而不是三维数组)。 对于 RGB 和 RGBA 图像,`matplotlib`支持`float32``uint8`数据类型。 对于灰度,`matplotlib`只支持`float32`。 如果你的数组数据不符合这些描述之一,则需要重新缩放它。
W
3,5  
wizardforcel 已提交
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

## 将 NumPy 数组绘制为图像

所以,你将数据保存在一个`numpy`数组(通过导入它,或生成它)。 让我们渲染它吧。 在 Matplotlib 中,这是使用`imshow()`函数执行的。 这里我们将抓取`plot`对象。 这个对象提供了一个简单的方法来从提示符处理绘图。

```py
In [6]: imgplot = plt.imshow(img)
```

![](http://matplotlib.org/_images/image_tutorial-1.png)

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

### 对图像绘图应用伪彩色方案

W
fix  
wizardforcel 已提交
84
伪彩色可以是一个有用的工具,用于增强对比度和更易于可视化你的数据。 这在使用投影仪对你的数据进行演示时尤其有用 - 它们的对比度通常很差。
W
3,5  
wizardforcel 已提交
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108

伪彩色仅与单通道,灰度,亮度图像相关。 我们目前有一个RGB图像。 由于R,G 和 B 都是相似的(见上面或你的数据),我们可以只选择一个通道的数据:

```py
In [7]: lum_img = img[:,:,0]
```

这是数组切片,更多信息请见[NumPy 教程](http://www.scipy.org/Tentative_NumPy_Tutorial)

```py
In [8]: plt.imshow(lum_img)
```

![](http://matplotlib.org/_images/image_tutorial-2.png)

现在,亮度(2D,无颜色)图像应用了默认颜色表(也称为查找表,LUT)。 默认值称为`jet`。 有很多其他方案可以选择。

```py
In [9]: plt.imshow(lum_img, cmap="hot")
```

![](http://matplotlib.org/_images/image_tutorial-3.png)


W
fix  
wizardforcel 已提交
109
请注意,你还可以使用`set_cmap()`方法更改现有绘图对象上的颜色:
W
3,5  
wizardforcel 已提交
110 111 112 113 114 115 116 117 118 119

```py
In [10]: imgplot = plt.imshow(lum_img)
In [11]: imgplot.set_cmap('spectral')
```

![](http://matplotlib.org/_images/image_tutorial-4.png)

> 注

W
fix  
wizardforcel 已提交
120
> 但是,请记住,在带有内联后端的 IPython notebook 中,你不能对已经渲染的绘图进行更改。 如果你在一个单元格中创建了`imgplot`,你不能在以后的单元格中调用`set_cmap()`,并且改变前面的绘图。 请确保你在相同单元格中一起输入这些命令。`plt`命令不会更改先前单元格的绘图。
W
3,5  
wizardforcel 已提交
121 122 123 124 125 126 127 128 129 130 131 132 133 134

有许多可选的其它颜色表,请见[颜色表的列表和图像](http://matplotlib.org/examples/color/colormaps_reference.html)

### 颜色刻度参考

了解颜色代表什么值对我们很有帮助。 我们可以通过添加颜色条来做到这一点。

```py
In [12]: imgplot = plt.imshow(lum_img)
In [13]: plt.colorbar()
```

![](http://matplotlib.org/_images/image_tutorial-5.png)

W
fix  
wizardforcel 已提交
135
这会为你现有的图形添加一个颜色条。 如果你更改并切换到不同的颜色映射,则不会自动更改 - 你必须重新创建绘图,并再次添加颜色条。
W
3,5  
wizardforcel 已提交
136 137 138

### 检查特定数据范围

W
fix  
wizardforcel 已提交
139
有时,你想要增强图像的对比度,或者扩大特定区域的对比度,同时牺牲变化不大,或者无所谓的颜色细节。 找到有趣区域的最好工具是直方图。 要创建我们的图像数据的直方图,我们使用`hist()`函数。
W
3,5  
wizardforcel 已提交
140 141 142 143 144 145 146

```py
In [14]: plt.hist(lum_img.ravel(), bins=256, range=(0.0, 1.0), fc='k', ec='k')
```

![](http://matplotlib.org/_images/image_tutorial-6.png)

W
fix  
wizardforcel 已提交
147
通常,图像的『有趣』部分在峰值附近,你可以通过剪切峰值上方和/或下方的区域获得额外的对比度。 在我们的直方图中,看起来最大值处没有太多有用的信息(图像中有很多不是白色的东西)。 让我们调整上限,以便我们有效地『放大』直方图的一部分。 我们通过将`clim`参数传递给`imshow`来实现。 你也可以通过对图像绘图对象调用`set_clim()`方法来做到这一点,但要确保你在使用 IPython Notebook 的时候,和`plot`命令在相同的单元格中执行 - 它不会改变之前单元格的图。
W
3,5  
wizardforcel 已提交
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

```py
In [15]: imgplot = plt.imshow(lum_img, clim=(0.0, 0.7))
```

![](http://matplotlib.org/_images/image_tutorial-7.png)

### 数组插值方案

插值根据不同的数学方案计算像素『应有』的颜色或值。 发生这种情况的一个常见的场景是调整图像的大小。 像素的数量会发生变化,但你想要相同的信息。 由于像素是离散的,因此存在缺失的空间。 插值就是填补这个空间的方式。 这就是当你放大图像时,你的图像有时会出来看起来像素化的原因。 当原始图像和扩展图像之间的差异较大时,效果更加明显。 让我们加载我们的图像并缩小它。 我们实际上正在丢弃像素,只保留少数几个像素。 现在,当我们绘制它时,数据被放大为你屏幕的大小。 由于旧的像素不再存在,计算机必须绘制像素来填充那个空间。

我们将使用用来加载图像的 Pillow 库来调整图像大小。

```py
In [16]: from PIL import Image
In [17]: img = Image.open('../_static/stinkbug.png')
In [18]: img.thumbnail((64, 64), Image.ANTIALIAS) # resizes image in-place
In [19]: imgplot = plt.imshow(img)
```

![](http://matplotlib.org/_images/image_tutorial-8.png)

这里我们使用默认插值,双线性,因为我们没有向`imshow()`提供任何插值参数。

让我们试试一些其它的东西:

最邻近

```py
In [20]: imgplot = plt.imshow(img, interpolation="nearest")
```

![](http://matplotlib.org/_images/image_tutorial-9.png)

双立方

```py
In [21]: imgplot = plt.imshow(img, interpolation="bicubic")
```

![](http://matplotlib.org/_images/image_tutorial-10.png)

双立方插值通常用于放大照片 - 人们倾向于模糊而不是过度像素化。