6.3. 建立陣列¶
這些頁面其餘部分的每個範例,一開始都已經手握一個 ndarray。本頁就是該陣列如何產生的目錄。建構函式共有四大類:
從 Python 可迭代物件 —— 一般的字面量/list/tuple 形式。
以指定形狀預先填入 —— 全零、全一、某個常數值、單位矩陣。
以序列方式產生 —— 範圍式或等距分佈的值。
包裝 RAM 中已存在的緩衝區 —— 周邊裝置的情況。
每個建構函式都接受 dtype= 關鍵字,預設為 float。感測器資料幾乎總是需要比預設更小的 dtype。
以下每個範例都以這段開始::
from ulab import numpy as np
6.3.1. 從 Python 可迭代物件¶
array() 會從任何由數字組成的可迭代物件建構一個 ndarray::
a = np.array([1, 2, 3, 4])
print(a)
輸出::
array([1.0, 2.0, 3.0, 4.0], dtype=float)
巢狀可迭代物件會產生多維陣列。內層的可迭代物件長度必須全部相同,否則會引發 ValueError::
m = np.array([[1, 2, 3],
[4, 5, 6]], dtype=np.uint8)
既有的 ndarray 也是有效的輸入;array() 一定會複製。若想在不需要複製時避免複製,請使用 asarray()::
b = np.asarray(a, dtype=np.float) # same dtype -> no copy
6.3.2. 以指定形狀預先填入¶
當已知目標形狀但內容尚未確定時,可先行配置緩衝區,稍後再寫入:
zeros()—— 填滿零。ones()—— 填滿一。full()—— 填滿某個指定值。eye()—— 類似單位矩陣的N乘M矩陣,在第k條對角線上為一。diag()—— 由向量產生的對角矩陣,或某矩陣的對角線。
np.zeros((3, 3)) # 3x3 of zeros
np.ones(5, dtype=np.uint8) # length-5 vector of ones
np.full((2, 3), 7, dtype=np.int8) # 2x3, all 7
np.eye(4) # 4x4 identity
np.diag([1, 2, 3]) # 3x3, [1, 2, 3] on the diagonal
shape 引數可以是單一整數(用於 1 維陣列)或一個 tuple。
6.3.3. 以序列方式產生¶
arange()—— 像內建的range()一樣產生等距分佈的值,但一律回傳ndarray::np.arange(0, 10, 2) # array([0, 2, 4, 6, 8])linspace()—— 在兩個界限之間產生num個等距分佈的點,當endpoint=True時包含上界::np.linspace(0, 1, num=11) # 0.0, 0.1, ..., 1.0logspace()—— 幾何級數分佈的點。start與stop是 指數,而非端點;結果從base ** start跑到base ** stop::np.logspace(0, 3, num=4) # 1.0, 10.0, 100.0, 1000.0meshgrid()—— 從兩個 1 維陣列建構兩個座標矩陣,讓逐像素函式f(x, y)能在一次向量化呼叫中於整個網格上求值。給定一個長度為W的 x 向量和一個長度為H的 y 向量,meshgrid會回傳兩個H乘W的矩陣:X是沿每一列重複的 x 向量,Y是橫跨每一欄重複的 y 向量,因此X[i, j]是第i列第j欄該儲存格的 x 座標,Y[i, j]是其 y 座標::x = np.arange(4) # [0, 1, 2, 3] y = np.arange(3) # [0, 1, 2] X, Y = np.meshgrid(x, y) # X = [[0, 1, 2, 3], # [0, 1, 2, 3], # [0, 1, 2, 3]] # Y = [[0, 0, 0, 0], # [1, 1, 1, 1], # [2, 2, 2, 2]]
接著
f(X, Y)便能在一個運算式中於網格的每個儲存格上對該函式求值。例如,在一個(H, W)影格上的「距中心距離」對應圖,就是針對meshgrid()回傳的矩陣計算np.sqrt((X - cx)**2 + (Y - cy)**2)。
6.3.4. 連接¶
concatenate() 會沿著一個既有的軸連接一個 tuple 的陣列::
a = np.array([[1, 2], [3, 4]], dtype=np.uint8)
b = np.array([[5, 6]], dtype=np.uint8)
np.concatenate((a, b), axis=0)
# array([[1, 2], [3, 4], [5, 6]], dtype=uint8)
所有輸入必須共用相同的 dtype 與 ndim,並且在除了連接軸以外的每一個軸上都相符。concatenate() 會配置一個足以容納每個輸入的全新陣列並把資料複製進去,因此它是一次性連接既有陣列的正確工具;它不適合用在串流迴圈內,在那種情況下,正確的模式是一次性預先配置目的地,再透過切片指定寫入其中。
6.3.5. 包裝既有的緩衝區¶
相機上最有用的建構函式是 frombuffer()。它會把一個既有的類位元組緩衝區重新解讀為 1 維的 ndarray,完全不複製 任何一個位元組::
buf = bytearray(8)
audio = np.frombuffer(buf, dtype=np.int16)
# 4 int16 samples, sharing memory with buf
透過 audio 的寫入會反映在 buf 中,反之亦然。所選的 dtype 必須能整除緩衝區長度。
offset= 會跳過緩衝區開頭的標頭;count= 則限制讀取多少個元素::
np.frombuffer(buf, dtype=np.uint8, offset=2, count=4)
每當周邊裝置交給應用程式一個原始緩衝區時,這就是正確的建構函式 —— 例如 bytearray 中的 ADC 取樣值、從 SPI 拉取的酬載。周邊裝置寫入的位元組就是該陣列。
當周邊裝置以相機 CPU 並非原生讀取的位元組順序寫入多位元組值時,byteswap() 會反轉每個元素的位元組順序,使數值能正確讀取。它預設會回傳一個新陣列;傳入 inplace=True 則會就地修改來源。
frombuffer() 只處理 numpy 本身定義的 dtype。對於產生 32 位元整數取樣值的周邊裝置,from_int32_buffer() 及其相關函式會一次性地轉換為 float。
6.3.6. 列印截斷¶
列印一個大型陣列時只會顯示其開頭與結尾的少數幾個元素,中間以 ... 表示,這樣 IDE 終端機就不會被成千上萬個數值塞滿::
>>> print(np.arange(1000, dtype=np.uint16))
array([0, 1, 2, ..., 997, 998, 999], dtype=uint16)
set_printoptions() 可在除錯需要看到整個緩衝區時覆寫這些門檻::
np.set_printoptions(threshold=2000) # print up to 2000 elements in full
np.set_printoptions(edgeitems=10) # 10 items at each end, not 3
get_printoptions() 會以 dict 形式讀回目前的設定。