[計算機視覺基礎(chǔ)] Numpy

\color{red}{計算機視覺類文章會不定期的做更新,歡迎關(guān)注}

Somethings To Know

無論是 ML 還是 CV,Numpy 都是極其重要、極其基礎(chǔ)的 Python 發(fā)行庫。尤其是圖片處理,通過鼎鼎大名的 opencv 庫加載的圖片或者視頻,都是由 Numpy 庫中的數(shù)據(jù)類型組成??梢哉f,對 Numpy 有一個充分的認識,對于后面入門、進階 CV 都是事半功倍之事。

俗話說的好,世間萬物存在都是有道理的。學(xué)習(xí) Numpy 也必須追根溯源,了解它為什么被巨佬造出來,使用場景是什么,解決了什么問題。

NumPy 是用 Python 進行科學(xué)計算的基礎(chǔ)軟件包??吹竭@里我心生疑問:為什么在 Python 語言中需要存在這么一個無法替代的軟件包,會不會和 Python 語言本身存在的問題有關(guān)??茖W(xué)計算,無論是機器學(xué)習(xí)數(shù)以萬計的特征和迭代次數(shù),還是圖片、視頻處理,都是海量的數(shù)據(jù)處理和計算。當(dāng)今計算機硬件飛速發(fā)展,雖說超線程,高性能顯卡層出不窮,但軟件程序本身的運行速度,永遠是第一個需要被優(yōu)化的,而 Python 作為解釋性語言,運行速度并不能稱得上迅速,而且,作為動態(tài)語言,每一個變量的加載都需要判斷類型,速度會比編譯型語言慢上很多。就如 List 而言,我們可以將各種不同類型的對象存入其中

a = list()
a.append(1)
a.append("hello world")
a.append({"key":"value"})
a.append(None)

當(dāng)我們使用這個 List 的時候,對于每一個元素,都需要讀取對象的類型,增減引用計數(shù),獲得尺寸,最后才是值,對于數(shù)字1,Python 仍然要使用 32 bit 的存儲空間存放(這也算是動態(tài)類型的代價吧),并且對于一個 List 對象,里面的元素在內(nèi)存中并不是按地址順序存放,這對后來內(nèi)存空間的訪問,無疑是再一次的降速。
而 Numpy 中的一些數(shù)據(jù)類型,可以指定集合中元素的類型到 int8 ,并且在內(nèi)存中連續(xù)存放,這也大大提升了日后訪問元素的速度。當(dāng)然 Numpy 的高速并不僅僅如此,作為科學(xué)計算工具,底層通過編譯型語言 C 以及 Fortran實現(xiàn),并且對于矩陣計算進行了高度的優(yōu)化,使用了 Intel MKL(全稱Intel Math Kernel Library,提供經(jīng)過高度優(yōu)化和大量線程化處理的數(shù)學(xué)例程,面向性能要求極高的科學(xué)、工程及金融等領(lǐng)域的應(yīng)用)等等??傊琋umpy 并不是一個建立在 Python 應(yīng)用層之上的軟件包,可以說,它是能夠供給 Python 使用的另一門語言技術(shù)。
看下面這個例子:

import numpy as np
a = np.array([1,2,3])
b = np.array([4,5,6])
a * b 

output:

array([ 4, 10, 18])

Numpy 為科學(xué)計算而生,這里的乘法即為針對a、b兩個數(shù)組的各個元素依次做乘法。視為向量,其實這就是向量乘法。

說了 Numpy 的一些有點和解決的問題,那么它目前已經(jīng)有哪些知名的使用場景呢?

  1. OpenCV
  2. 矩陣、向量計算(類似 MATLAB)
  3. Matplotlib
  4. Pandas
  5. ML

是的,都是鼎鼎大名,如雷貫耳的AI,CV,數(shù)據(jù)處理相關(guān)方向的庫。掌握 Numpy 對于進一步邁入這些領(lǐng)域,有著不可替代的作用。好了,撤了那么多價值,下面該開始進入正題了。

The Basic

定義一個 Numpy array:

a = np.array([1,2,3])
print(a)
print(type(a))
print(a.shape)
print(a.dtype)
print(a.ndim)

output:

[1 2 3]
<class 'numpy.ndarray'>
(3,)
int64
1

這是一個一維的 Vector,擁有 3 個 元素,每個元素是有 int64 構(gòu)成。
下面是定義一個二維對象:

a = np.array([[1,2,3], [4,5,6]])
print(a)
print(type(a))
print(a.shape)
print(a.dtype)

output:

[[1 2 3]
 [4 5 6]]
<class 'numpy.ndarray'>
(2, 3)
int64
2

shape 代表數(shù)組的形狀,這里是 2 rows x 3 columns,了解矩陣概念的可以將此對應(yīng)于矩陣的行列,依然是由 int64 構(gòu)成。
前面我們提到, Numpy 的一個優(yōu)點就是他的非動態(tài)類型,我們完全可以指定數(shù)組中每一個元素所使用的類型,例如:

a = np.array([1,2,3], dtype='int8')

通過構(gòu)造函數(shù)中指定 dtype 為 int8,可以將數(shù)組的每一個元素限定為只占8個bit的空間,這對于已知范圍的巨量數(shù)組,是空間的極大減少(例如對于BGR 圖片對象而言,每個像素的值為 0 ~ 255, 那么我們完全可以指定 dtype 為 uint8 已達到充分節(jié)約空間,加速訪問的效果)。

Vary Size

對于一個 ndarray,存在著各種大小,下面通過示例進行羅列:

a = np.array([[1,2,3], [4,5,6]], dtype='int16')
print(a.ndim) # 維度
print(a.shape) # 形狀
print(a.shape[0]) # 行
print(a.shape[1]) # 列
print(a.itemsize) # 每個元素的字節(jié)數(shù)
print(a.size) # 元素數(shù)
print(a.nbytes) # 元素數(shù) * 每個元素的字節(jié)數(shù)

output:

2
(2, 3)
2
3
2
6
12

Index

ndarray 的索引簡單直接,直接看例子:

a = np.array([[1,2,3,4,5,6,7,8], [9,10,11,12,13,14,15,16]], dtype='int16')
print(a[1, 2]) # 獲取第二行第三列對應(yīng)的元素
print(a[0,:]) # 獲取第一行
print(a[:,1]) # 獲取第二列
print(a[1,1:4]) # 獲取第二行下標在 1 <= index < 4 的元素
print(a[1,1:6:2]) # 獲取第二行下標在 1 <= index < 6 的元素, step 為2

output:

11
[1 2 3 4 5 6 7 8]
[ 2 10]
[10 11 12]
[10 12 14]

Change

就如索引一樣,改變 ndarray 的元素也是簡單并且方法多,直接看例子:

a = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], dtype='int16')
print(a)
print("\n修改某一個元素:")
a[0,1,1] = 15
print(a)
print("\n修改某一維元素:")
a[:,1,:] = [[21,22,23],[24,25,26]]
print(a)

output:

[[[ 1  2  3]
  [ 4  5  6]]

 [[ 7  8  9]
  [10 11 12]]]

修改某一個元素:
[[[ 1  2  3]
  [ 4 15  6]]

 [[ 7  8  9]
  [10 11 12]]]

修改某一維元素:
[[[ 1  2  3]
  [21 22 23]]

 [[ 7  8  9]
  [24 25 26]]]

Some Special Matric

因為 Numpy 與矩陣計算息息相關(guān),庫也同時提供了一些方便使用的構(gòu)造方法構(gòu)造多維特殊的矩陣:

from pprint import pprint
a = np.array([[1,2,3], [4,5,6]])
b = np.array([[1,2,3]])

pprint(np.zeros((2,2), dtype='uint8')) # 構(gòu)造一個 2 x 2,以指定類型 0 值填充的數(shù)組
pprint(np.zeros_like(a, dtype='uint8')) # 構(gòu)造一個與 a 同形,以指定類型 0 值填充的數(shù)組
pprint(np.ones((2,2), dtype='uint8')) # 構(gòu)造一個 2 x 2,以指定類型 1 值填充的數(shù)組
pprint(np.ones_like(a, dtype='uint8')) # 構(gòu)造一個與 a 同形,以指定類型 1 值填充的數(shù)組
pprint(np.identity(3)) # 構(gòu)造一個 3 x 3 的單位矩陣
pprint(np.full((2,2), 3, dtype='uint8')) # 構(gòu)造一個 2 x 2,以指定類型 3 值填充的數(shù)組
pprint(np.full_like(a, 3, dtype='uint8')) # 構(gòu)造一個與 a 同形,以指定類型 3 值填充的數(shù)組
pprint(np.random.rand(3,2)) # 構(gòu)造一個由隨機數(shù)組成的 3 x 2 矩陣
pprint(np.random.random_sample(a.shape)) # 構(gòu)造一個與 a 同形,由隨機數(shù)組成矩陣
pprint(np.random.randint(1,8, size=a.shape)) # 構(gòu)造一個與 a 同形,由范圍1~8的隨機數(shù)字填充的矩陣
pprint(np.repeat(b, 3, axis=0)) # 復(fù)制行3次
pprint(np.repeat(b, 3, axis=1)) # 復(fù)制列3次

output:

array([[0, 0],
       [0, 0]], dtype=uint8)
array([[0, 0, 0],
       [0, 0, 0]], dtype=uint8)
array([[1, 1],
       [1, 1]], dtype=uint8)
array([[1, 1, 1],
       [1, 1, 1]], dtype=uint8)
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])
array([[3, 3],
       [3, 3]], dtype=uint8)
array([[3, 3, 3],
       [3, 3, 3]], dtype=uint8)
array([[0.94942891, 0.54141901],
       [0.21244184, 0.29175129],
       [0.37956357, 0.77184397]])
array([[0.62711458, 0.42293761, 0.64249381],
       [0.68692789, 0.4358814 , 0.22340343]])
array([[2, 6, 1],
       [2, 4, 5]])
array([[1, 2, 3],
       [1, 2, 3],
       [1, 2, 3]])
array([[1, 1, 1, 2, 2, 2, 3, 3, 3]])

View and Copy

首先,這是一個重要的概念。如果掌握不透徹,可能會使你的代碼陷入無法預(yù)料的錯誤中。
視圖,顧名思義,它只是查看數(shù)組數(shù)據(jù)的另一種方式。這意味著視圖數(shù)組與原數(shù)組的數(shù)據(jù)是共享的,你可以通過選擇原始數(shù)組的一部分來創(chuàng)建視圖,也可以通過更改 dtype(或兩者的組合)來創(chuàng)建視圖。
Slice 是最為常見的數(shù)組視圖的場景:

a = np.array([0,1,2,3,4,5,6,7])
b = a[1:3]
pprint(b)
a[2] = 22
pprint(b) 

output:

array([1, 2])
array([1, 22])

這里要充分的體會數(shù)據(jù)共享的含義,視圖與原數(shù)組的數(shù)據(jù)在內(nèi)存中是同片空間,但視圖有屬于自己的數(shù)據(jù)類型選擇,相當(dāng)于用不同的顯微鏡倍數(shù)看玻片上的樣本,不同的放大倍數(shù)看到的也不同:

a = np.array([0,-1,2,-3,4,-5,6,-7], dtype='int8')
pprint(a)
b = a.view('uint8')
pprint(b)
a[2] = 22
pprint(b)

output:

array([ 0, -1,  2, -3,  4, -5,  6, -7], dtype=int8)
array([  0, 255,   2, 253,   4, 251,   6, 249], dtype=uint8)
array([  0, 255,  22, 253,   4, 251,   6, 249], dtype=uint8)

這里通過 int8 類型寫入的數(shù)組,通過構(gòu)造出 uint8 的視圖,通過視圖觀察到的數(shù)組發(fā)生了變化,負數(shù)都變?yōu)闊o符號數(shù)(-1變?yōu)?55,-3變?yōu)?53等)。其實內(nèi)存中的數(shù)據(jù)同屬一份,只是使用了不同的視圖。
Copy 即為深拷貝,相當(dāng)于新申請了一片內(nèi)存存放相同數(shù)據(jù)的拷貝。

a = np.array([1,2,3,4,5])
b = a.copy()
print(id(a))
print(id(b))
b[1] = -2
a

output:

4788660816
4784398864
array([1, 2, 3, 4, 5])

Math

使用 Numpy 計算數(shù)據(jù)通常是必經(jīng)之路。簡單的四則運算通俗易懂:

a = np.array([1,2,3,4,5])
b = np.ones(5, dtype='uint8')
pprint(a + 2) # 元素范圍的加法
pprint(a * 2) # 元素范圍的乘法
pprint(a ** 2) # 元素范圍的乘方
pprint(np.sin(a)) # 素范圍的 sin
pprint(a + b) # 兩個 array 相加

output:

array([3, 4, 5, 6, 7])
array([ 2,  4,  6,  8, 10])
array([ 1,  4,  9, 16, 25])
array([ 0.84147098,  0.90929743,  0.14112001, -0.7568025 , -0.95892427])
array([2, 3, 4, 5, 6])

Linear algebra

矩陣的各種計算依賴于線性代數(shù)的各種名詞和方法,行列式,SVD,特征值,特征向量。本文并不想展開這些概念和使用,這需要很多的基礎(chǔ)知識,Numpy 的文檔可供參考:https://numpy.org/doc/stable/reference/routines.linalg.html

Statistics

有數(shù)據(jù)就一定有統(tǒng)計的價值,Numpy 自然也能根據(jù)數(shù)據(jù)進行各種統(tǒng)計學(xué)的處理:

a = np.array([[1,2,3],[4,5,6]])
pprint(np.min(a)) # 如果需要知道行列的最值,可以通過指定 axis
pprint(np.min(a, axis=0)) # 輸出成行,即為統(tǒng)計列的最小值
pprint(np.min(a, axis=1)) # 輸出成列,即為統(tǒng)計行的最小值
pprint(np.max(a))
pprint(np.sum(a))
pprint(np.mean(a))

output:

1
array([1, 2, 3])
array([1, 4])
6
21
3.5

Stack

分為垂直堆疊和水平堆疊:

a = np.array([1,2,3,4])
b = np.array([5,6,7,8])
pprint(np.vstack((a,b))) # 可以理解為垂直堆起來
pprint(np.hstack((a,b))) # 可以理解為水平堆起來

output:

array([[1, 2, 3, 4],
       [5, 6, 7, 8]])
array([1, 2, 3, 4, 5, 6, 7, 8])

File

從文件中讀取數(shù)據(jù)到數(shù)組

data = np.genfromtxt("a.txt", delimiter=',') # csv 文件

Bool Masking and Advanced Indexing

有點類似對 Numpy 數(shù)組進行查找,過濾,獲得所需的數(shù)據(jù)。

a = np.random.randint(1,99, size=(3,10))
pprint(a)
pprint(a > 50) # 針對每一個元素判斷是否大于50
pprint((a > 50).size) # 大于 50 的元素個數(shù)
pprint(np.any(a > 50, axis=0)) # 每列是否都有大于50
pprint(np.all(a > 50, axis=0)) # 每列是否都全大于50
pprint(a[a > 50]) # 輸出所有大于50的元素

output:

array([[34, 40, 10, 74, 35, 79, 60, 70, 96, 89],
       [87, 55, 95, 80, 72, 70, 82, 96, 88,  2],
       [ 5, 74, 98, 22, 16, 88, 93, 97, 47, 70]])
array([[False, False, False,  True, False,  True,  True,  True,  True,
         True],
       [ True,  True,  True,  True,  True,  True,  True,  True,  True,
        False],
       [False,  True,  True, False, False,  True,  True,  True, False,
         True]])
30
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
        True])
array([False, False, False, False, False,  True,  True,  True, False,
       False])
array([74, 79, 60, 70, 96, 89, 87, 55, 95, 80, 72, 70, 82, 96, 88, 74, 98,
       88, 93, 97, 70])

Last Words

blu blu 講了那么多瑣碎的基礎(chǔ)知識點,這些也許只占 Numpy Big Picture 的0.01%都不到,在使用的過程中不停地去積累,逐漸的撥開云霧,見青天吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容