Matplotlib 圖片、等值輪廓線


要使用 Matplotlib 顯示圖片非常簡單,只要指定像素資料就可以了,例如:

import matplotlib.pyplot as plt

plt.title('face') 
plt.imshow([
    [[255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255]], 
    [[255, 255, 255], [255,   0,   0], [255, 255, 255], [255,   0,   0], [255, 255, 255]],
    [[255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255]], 
    [[255, 255, 255], [255,   0,   0], [255,   0,   0], [255,   0,   0], [255, 255, 255]], 
    [[255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255]]
])
plt.show()

其中每個 [255, 255, 255] 結構的資料,代表一個像素的 [R, G, B] 值,這會顯示以下的結果:

Matplotlib 圖片、等值輪廓線

如果只想顯示灰階圖,每個像素指定一個數字代表階數,呼叫 imshow 時指定 cmap(Color Map)為 cm.gray

import matplotlib.pyplot as plt
from matplotlib import cm

plt.title('face') 
plt.imshow([
    [255, 255, 255, 255, 255], 
    [255,   0, 255,   0, 255], 
    [255, 255, 255, 255, 255], 
    [255,   0,   0,   0, 255], 
    [255, 255, 255, 255, 255]
], cmap = cm.gray)
plt.show()

這會顯示以下的結果:

Matplotlib 圖片、等值輪廓線

如果想讀入圖片顯示呢?Python 內建的 PIL 模組提供了圖片載入功能,透過 Image 讀入圖片後的資料,是以 [R, G, B] 形式代表每個像素,直接餵給 imshow 就可以了,例如讀入我的吉祥物圖片並顯示:

from PIL import Image
import matplotlib.pyplot as plt

img = Image.open('caterpillar.jpg')
img.load()

plt.title('caterpillar') 
plt.imshow(img)
plt.show()

這會顯示以下的結果:

Matplotlib 圖片、等值輪廓線

單純只是介紹圖片的讀入與顯示沒什麼太大意義,不如來看個二維 Perlin 雜訊的應用吧!基本原理與〈NumPy 與 Perlin 雜訊〉中談到相同,只不過二維 Perlin 雜訊在每個座標點會生成一個梯度,四個座標點的梯度,決定了四個座標點間的雜訊變化。

就結論而言,以下的程式將雜訊作為灰階度,並交給 imshow 顯示:

from math import floor
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm

# Perlin noise 實作
def blending(t):
    return 6 * (t ** 5) - 15 * (t ** 4) + 10 * (t ** 3)

def lerp(g1, g2, t):
    return g1 + t * (g2 - g1)

def grad2(hashvalue, dx, dy):
    return [dy, dx + dy, dx, dx - dy, -dy, -dx - dy, -dx, -dx + dy][hashvalue % 8];

rand_table = np.random.randint(255, size = 256).tolist()
def _perlin2(x, y):
    xi = floor(x)
    yi = floor(y)

    aa = rand_table[
        (rand_table[xi % 256] + yi) % 256
    ]
    ba = rand_table[
        (rand_table[(xi + 1) % 256] + yi) % 256
    ]
    ab = rand_table[
        (rand_table[xi % 256] + yi + 1) % 256
    ]
    bb = rand_table[
        (rand_table[(xi + 1) % 256] + yi + 1) % 256
    ]

    dx = x - xi
    dy = y - yi    

    u = blending(dx)
    v = blending(dy)

    g1 = lerp(grad2(aa, dx, dy), grad2(ba, dx - 1, dy), u)
    g2 = lerp(grad2(ab, dx, dy - 1), grad2(bb, dx - 1, dy - 1), u)

    return lerp(g1, g2, v)
_perlin2 = np.frompyfunc(_perlin2, 2, 1)

def perlin2(x, y):
    cx, cy = np.meshgrid(x, y)
    return _perlin2(cx, cy).astype(np.float)

# 將雜訊當成灰階度顯示為圖片

width = 500
x = np.arange(width) / 100
y = np.arange(width) / 100
noise = (perlin2(x, y) + 1) * 125

plt.title('Perlin noise') 
plt.imshow(noise, cmap = cm.gray)
plt.show()

就 NumPy 的認識本身來說,這邊的範例需要知道的有兩個部份,一是 np.meshgrid 的使用,因為範例中的 perlin2 接受兩個 NumPy 陣列,這兩個陣列要用來計算出每個像素座標,由於 _perlin2 被向量化了,你必須提供兩個匹配的陣列,讓 _perlin2 每次抓對來進行運算,抓對的結果要代表像素座標,透過 np.meshgrid,就可以將兩個軸的陣列,轉換為抓對用的陣列。

另一個要知道的部份是,rand_tablenp.random.randint(255, size = 256).tolist(),也就是它是個 list,為什麼不使用 NumPy 陣列呢?因為 _perlin2 每次會對 rand_table 指定單一索引,對於這種情況,使用 NumPy 陣列反而緩慢(NumPy 陣列可以快速,是每次呼叫時,能處理一組連續資料的場合),透過 list 會是比較好的選擇。

以上範例的執行結果會是:

Matplotlib 圖片、等值輪廓線

若你是第一次看到二維的 Perlin 雜訊以灰階圖表示,可能會想這是什麼神秘圖案?若將灰階數 0 作為低點,灰階數 255 作為高點,這就相當於起伏的地形。

談到地形,就會想到等高線,可以將等值的點連接起來,這可以透過 Matplotlib 的 contour 函式來繪製,將以上範例的 plt.imshow(noise, cmap = cm.gray) 改為:

plt.contour(noise)

就可以顯示以下的圖案:

Matplotlib 圖片、等值輪廓線

等值線的層次,可以用 levels 來控制,可以指定數字決定層次數,或者指定類陣列(array-like)物件,決定哪些等值下要繪製輪廓線,例如設個 25 階:

plt.contour(noise, levels = 25)

就會看到更密集的等高線了:

Matplotlib 圖片、等值輪廓線