Programming Computer Vision with Python (學習筆記二)

首先介紹跟圖像處理、顯示有關兩個庫:NumPy和Matplotlib,而後介紹加強圖像對比度的實現原理。html

NumPy

NumPy是Python用於科學計算的基礎庫,提供了一些頗有用的概念,如:N維數組對象,可用於表示向量、矩陣、圖像數據等,另外還包含了線性代數及其運算函數。NumPy的數組對象在本書示例中會被大量使用,它能夠做諸如矩陣乘法、變換、向量乘法和正態化等運算,咱們經過這些運算來實現圖像對齊、圖像分類、圖像扭轉等。
這是一個基礎庫,一般不須要額外安裝。python

N維數組在NumPy中對應的數據類型是ndarry,有時使用別名array(即numpy.array)。但要注意的是,它與Python的內置類型array是兩回事,不要混淆,Python內置array類型只處理一維數組,其功能遠不及ndarray。ndarray中的全部元素的存儲類型是同樣的,下面對ndarray一些重要的屬性進行說明:shell

  • ndarray.ndim
    數組維度api

  • ndarray.shape
    對於一個n×m矩陣,shape返回元組(n,m)數組

  • ndarray.size
    數組的全部元素個數函數

ndarray.dtype
數組元素的數據類型ui

  • ndarray.itemsize
    數據中每一個元素的類型長度(單位byte)spa

  • ndarray.data
    包含數組全部元素的buffer,一般咱們只是使用數組下標來獲取元素的值3d

構造
用Python的數組表示來構造ndarray,很直觀:code

In [3]: import numpy as np

In [5]: a = np.array([[0,1,2], 
                      [3,4,5]]) 

In [6]: a.shape
Out[6]: (2, 3)

In [7]: a.ndim
Out[7]: 2

In [8]: a.dtype.name
Out[8]: 'int64'

In [9]: a.itemsize
Out[9]: 8

In [10]: a.size
Out[10]: 6

In [11]: type(a)
Out[11]: numpy.ndarray

構建dnarray時能夠指定元素的類型:

In [12]: b = np.array([0,1,2],dtype=int16)

In [13]: b.itemsize
Out[13]: 2

咱們最經常使用的是想把一幅圖像轉爲np.array表示,而PIL的Image類能夠處理大部分的圖像格式,因此從Image轉爲np.array頗有用,如:

from PIL import Image
import numpy as np
im = np.array(Image.open('Selection_001.png'))

注:Image對象之因此能直接轉爲ndarray類型,是由於Image類實現了ndarray的data和shape等接口。

其它一些有用的構造方法:

  • np.zeros( (n, m) ) 構建n乘m數組,其中元素初始化爲0

  • np.ones( (n, m) ) 同上,但元素初始化爲1

  • np.empty( (n, m) ) 同上,但元素不做初始化

  • np.arange([start,] stop[, step,], dtype=None) 構建1維數組,元素的值從start到stop,增長步長爲step

In [75]: np.arange(5)
Out[75]: array([0, 1, 2, 3, 4])

In [76]: np.arange(5, 10)
Out[76]: array([5, 6, 7, 8, 9])

In [77]: np.arange(5, 10, 2)
Out[77]: array([5, 7, 9])
  • np.linspace( start, stop, item_count ) 構建1維數組,元素從start到stop,元素個數爲item_count,因此元素的增長步長是自動計算的: (to - from) / (item_count - 1)

In [63]: np.linspace(5,10,2)
Out[63]: array([  5.,  10.])

In [64]: np.linspace(5,10,3)
Out[64]: array([  5. ,   7.5,  10. ])

In [65]: np.linspace(5,10,4)
Out[65]: array([  5.        ,   6.66666667,   8.33333333,  10.        ])

In [66]: np.linspace(5,10,5)
Out[66]: array([  5.  ,   6.25,   7.5 ,   8.75,  10.  ])

基本運算
兩個數組的+-<>*運算,做用於兩個數組相對應位置的元素,結果是一個新數組:

In [22]: a
Out[22]: 
array([[1, 2, 3],
       [4, 5, 6]])

In [23]: b
Out[23]: 
array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])

In [24]: a + b
Out[24]: 
array([[ 2.,  3.,  4.],
       [ 5.,  6.,  7.]])

In [25]: a - b
Out[25]: 
array([[ 0.,  1.,  2.],
       [ 3.,  4.,  5.]])

In [26]: a < b
Out[26]: 
array([[False, False, False],
       [False, False, False]], dtype=bool)

In [30]: c
Out[30]: 
array([[1, 1, 1],
       [2, 2, 2]])

In [31]: a * c
Out[31]: 
array([[ 1,  2,  3],
       [ 8, 10, 12]])

數組A與B的乘積:A.dot(B)np.dot(A, B)
+=*=等運算符產生的結果,直接修改調用數組自身,而不是返回新數組。
其它一些有用的運算操做:np.sin, np.cos, np.exp(指數), np.sqrt(開方)等。

下標訪問

In [45]: a
Out[45]: 
array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])

In [46]: a[2,3]  #訪問行下標爲2,列下標爲3的元素
Out[46]: 23

In [47]: a[0:5, 1] #訪問行下標從0到5(不含),列下標爲1的元素
Out[47]: array([ 1, 11, 21, 31, 41])

In [50]: a[:, 1] #訪問全部行,但列下標爲1的元素
Out[50]: array([ 1, 11, 21, 31, 41])

In [51]: a[1:3] #訪問行下標從1到3(不含)的元素
Out[51]: 
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])

In [52]: a[-1] #訪問最後一行
Out[52]: array([40, 41, 42, 43])

In [2]: x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [3]: x[1:7:2]  #指定起始、結束(不含)以及步長
Out[3]: array([1, 3, 5])

變形
展開爲一維數組:

In [53]: a = np.array([[1,2],[3,4]])  #2乘2數組

In [54]: a
Out[54]: 
array([[1, 2],
       [3, 4]])

In [57]: b = a.ravel()  #展開爲1維數組,返回新數組

In [58]: b
Out[58]: array([1, 2, 3, 4])

In [59]: b.reshape(2, 2) #變形爲2乘2數組,返回新數組
Out[59]: 
array([[1, 2],
       [3, 4]])

In [60]: b.resize(2, 2)  #變形爲2乘2數組,直接修改自己

In [61]: b
Out[61]: 
array([[1, 2],
       [3, 4]])

有了以上的瞭解,咱們來看看實際的應用例子。先讀取一張圖片,把它轉爲ndarray類型,再看其數組屬性:

In [88]: from PIL import Image
In [89]: import numpy as np

In [91]: im = np.array(Image.open('Selection_001.png'))  #用PIL.Image讀取圖像,並轉爲ndarray數組
In [92]: print im.shape, im.dtype
(240, 568, 3) uint8  #表示圖像數據240行,568列,顏色通道數3,以uint8類型存儲

In [93]: im_l = np.array(Image.open('Selection_001.png').convert('L'))  #轉爲灰度圖像
In [94]: print im_l.shape, im_l.dtype
(240, 568) uint8  #灰度圖像沒有顏色通道信息

矩陣
class numpy.matrix(data, dtype=None, copy=True)
從data中構造一個矩陣對象,data能夠是ndarray也能夠是字符串,若data爲ndarray,則copy表示是否複製data來構造。

In [4]: np.matrix('1 2; 3 4')
Out[4]: 
matrix([[1, 2],
        [3, 4]])

In [5]: np.matrix([[1, 2], [3, 4]])
Out[5]: 
matrix([[1, 2],
        [3, 4]])

還可使用如下兩個函數來構造矩陣:
numpy.mat(data, dtype=None)或numpy.asmatrix(data, dtype=None),兩個只是名字不同,都至關於numpy.matrix(data, copy=False)。

矩陣類提供了一些矩陣運算的方便的接口,如:
getT:返回轉置矩陣
getI: 返回可逆矩陣的逆矩陣
getH:返回共軛轉置矩陣
getA:返回矩陣的ndarray

Matplotlib

Matplotlib是一個用於科學計算及製圖方面的強大的開源庫,支持不少常見的圖形圖表,如:
matplot示例圖表

雖然Matplotlib功能很強大,咱們可能只是用到它不多的一些接口,好比畫圖像的輪廓和灰度圖像的柱狀圖。
安裝Matplotlib

sudo apt-get install python-matplotlib

pylab和pyplot
爲簡化畫圖工做,Matplotlib的pyplot模塊提供了與MATLAB類似的接口,而且能夠跟IPython配合使用。
須要注意的是,書中的代碼示例使用的是Matplotlib.pylab這個模塊:

from PIL import Image
from pylab import *
im = array(Image.open('empire.jpg'))  #讀圖並轉爲ndarray
imshow(im)

根據Matplotlib官網上的pyplot和pylab的關係說明得知:使用pylab只是爲了import時方便起見,import pylab至關於import了pyplot和numpy模塊中大部分的接口,雖然有些例子還這樣用,但已經不被推薦使用,而是推薦使用pyplot。另外,pyplot模塊內置了狀態機,它能自動生成必要的圖例和座標軸等信息,能夠簡化畫圖代碼。

灰度變換(GrayLevel Transformation)

對圖像進行灰度變換的目的是爲了:

  • 改善畫質,使圖像更加清晰

  • 有選擇地突出圖像中感興趣的特徵或抑制圖像中某些不須要的特徵,使圖像與視覺響應特性相匹配

  • 改變圖像的直方圖分佈,增長圖像對比度

最簡單的灰度變換就是反轉顏色,示例:

In [88]: from PIL import Image
In [89]: import numpy as np
In [90]: import matplotlib.pyplot as plt

In [97]: im = np.array(Image.open('cover.png').convert('L'))
In [98]: plt.gray()  #不加的話,顯示出來的圖像會有顏色
In [100]: plt.imshow(im)
In [102]: plt.show()

In [103]: im2 = 255 - im
In [104]: plt.imshow(im2)
In [105]: plt.show()

反轉前:
圖片描述
反轉後:
圖片描述

直方圖均衡化(histogram equalization)

灰度變換的一個頗有用的例子就是直方圖均衡化,這裏的直方圖指圖像的灰度直方圖,由於咱們要示例的是灰度圖像,每一個像素用8bit表示,值從0到255,共有256個灰度級。但一般的圖像像素值,都沒有徹底佔用這256個級別,不少像素的灰度值集中在一塊兒,這樣致使灰度之間的變化不明顯,若是咱們把圖像的灰度級按比例拉伸到256級,可使得像素灰度級差距增大,從而使圖像看起來更清晰,對比度更強一些。直方圖均衡化就是爲了達到這個目的,均衡化後的圖像,像素落在每一個灰度級上的個數是相等的。並且原圖像的第i個灰度累積和(即落在[0,i]區間全部像素個數)與均衡化後的第i個灰度累積和相等,即原圖像累積和按0到255的比例進行變換。因此下面將使用累積分佈函數(cumulative distribution function,簡稱cdf)

直方圖數據的統計將藉助numpy.histogram函數來得到:

numpy.histogram(a, bins=10, range=None, normed=False, weights=None, density=None)

傳入數組及直方圖的柱的數目(柱也可由X軸點的系列指定),統計落在各個柱區間的元素的個數。

參數:
a: 數組,須要扁平化
bins: bin指的是直方圖中的「柱」,取值對應X軸上的區間[x,y),此參數可選,傳入int表示等寬柱的數量,也支持非等寬柱的設置
range:(float, float),可選,指定柱的最低和最高值
normed:bool,可選,NumPy1.6棄用,建議使用density參數
density:bool,可選,False表示函數返回的是落在每一個柱區間的元素的數量,若爲True,函數返回的是由`機率密度分佈函數`對每一個柱計算出來的值

返回值:
hist:ndarray,如density參數所說
bin_edges:柱的邊界數組,length(hist) + 1,即X軸上柱之間的分割點造成的數組

示例:
In [8]: a = np.array([0,1,2,3,4])
In [9]: np.histogram(a, 5)
Out[9]: (array([1, 1, 1, 1, 1]), #a中落在如下各個區間的元素的個數
array([ 0. ,  0.8,  1.6,  2.4,  3.2,  4. ])) #柱的邊界(區間),自動均分

In [10]: np.histogram(a, 5, density=True)
Out[10]: (array([ 0.25,  0.25,  0.25,  0.25,  0.25]), #機率密度分佈
 array([ 0. ,  0.8,  1.6,  2.4,  3.2,  4. ]))

而累積和的計算須要用到numpy.cumsum函數:

numpy.cumsum(a, axis=None, dtype=None, out=None)
示例:
In [21]: a = np.array([1,2,3,4,5,6])
In [22]: np.cumsum(a)
Out[22]: array([ 1,  3,  6, 10, 15, 21])

如今來寫一個函數實現直方圖均衡化:

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

def histeq(im,nbr_bins=256):
    imhist,bins = np.histogram(im.flatten(),nbr_bins,density=True) #對每一個元素求機率密度
    cdf = imhist.cumsum() #對機率密度數組求累積和
    cdf = 255 * cdf / cdf[-1] #累積和變換到0-255區間
    im2 = np.interp(im.flatten(),bins[:-1],cdf) #線性插值
    return im2.reshape(im.shape), cdf #還原圖像維度

im = np.array(Image.open('hist-sample.jpg').convert('L'))
im2,cdf = histeq(im)

plt.gray()
plt.subplot(221) #2行2列,第1個圖
plt.imshow(im)
plt.subplot(222) #2行2列,第2個圖
plt.hist([x for x in im.flatten() if x < 250], 128)
plt.subplot(223)
plt.imshow(im2)
plt.subplot(224)
plt.hist([x for x in im2.flatten() if x < 250], 128)
plt.show()

效果對好比下,上面的是原圖及直方圖,下面的是均衡化後的圖及直方圖:
圖片描述

明顯看出,均衡化後的圖對比度要更強一些。

多圖像平均法(Averaging Images)

多圖像平均法是一個用於降噪和美化圖片的簡單方法。假設多張圖像具備相同尺寸,一個計算方法就是把全部圖像的數據相加起來再除以圖像數目從而獲得圖像的平均值。這個操做使用ndarray的+=/=運算符就能夠完成。
另外一個實現的方法就是使用numpy.mean()函數,放在後面再講。

小結

下一個筆記內容講圖像的主成分分析(PCA)

相關文章
相關標籤/搜索