Programming Computer Vision with Python (学习笔记三)
概要
原書對(duì)于PCA的講解只有一小節(jié),一筆帶過的感覺,但我發(fā)現(xiàn)PCA是一個(gè)很重要的基礎(chǔ)知識(shí)點(diǎn),在機(jī)器機(jī)視覺、人臉識(shí)別以及一些高級(jí)圖像處理技術(shù)時(shí)都被經(jīng)常用到,所以本人自行對(duì)PCA進(jìn)行了更深入的學(xué)習(xí)。
PCA是什么
PCA(Principal Component Analysis,主成分分析或主元分析)是一種算法,PCA的結(jié)果是用盡可能少的特征數(shù)據(jù)來表達(dá)最多的原始圖像的本質(zhì)結(jié)構(gòu)特征。即使它會(huì)丟失一部分原始圖像的特征表達(dá),但它仍然是很有用的處理技巧,也很常用,特別在計(jì)算機(jī)視覺和人臉識(shí)別方面。
假設(shè)我們有一個(gè)二維數(shù)據(jù)集,它在平面上的分布如下圖:
如果我們想要用一個(gè)一維向量來表達(dá)此數(shù)據(jù)集,就會(huì)丟失一部分此數(shù)據(jù)集的信息,但我們的目標(biāo)是讓求得的這個(gè)一維向量可以盡可能多地保留這個(gè)數(shù)據(jù)集的特征信息,那么這個(gè)求解過程就是PCA。
通過PCA我們可以找到若干個(gè)1維向量,如圖:
直觀上看出,向量u1是數(shù)據(jù)集變化的主方向,而u2是次方向,u1比u2保留了更多的數(shù)據(jù)集的結(jié)構(gòu)特征,所以我們選擇u1作為主成分,并把原數(shù)據(jù)集投影到u1上就可以得出對(duì)原數(shù)據(jù)集的一維近似重構(gòu):
以上只是一種直觀的示例,把二維數(shù)據(jù)降為用1維來表示,當(dāng)然,PCA通常是應(yīng)用在高維數(shù)據(jù)集上。
PCA解決什么問題
假設(shè)我們有10張100 × 100像素的灰度人臉圖,我們目標(biāo)是要計(jì)算這10張圖的主成分來作為人臉特征,這樣就可以基于這個(gè)‘特征臉’進(jìn)行人臉匹配和識(shí)別。但即使一個(gè)100 × 100像素的灰度圖像維度就達(dá)到10,000維,10張圖像的線性表示可以達(dá)到100,000維,如此高維的數(shù)據(jù)帶來幾個(gè)問題:
-
對(duì)高維數(shù)據(jù)集進(jìn)行分析處理的計(jì)算量是巨大的,消耗資源太大,時(shí)間太長
-
高維數(shù)據(jù)包含了大量冗余和噪聲數(shù)據(jù),會(huì)降低圖像識(shí)別率
所以通常對(duì)于高維數(shù)據(jù)集,首先需要對(duì)其進(jìn)行降維運(yùn)算,以低維向量表達(dá)原數(shù)據(jù)集最多最主要的結(jié)構(gòu)特征。從而將高維圖像識(shí)別問題轉(zhuǎn)化為低維特征向量的識(shí)別問題,大大降低了計(jì)算復(fù)雜度,同時(shí)也減少了冗余信息所造成的識(shí)別誤差。PCA其實(shí)就是最常用的一種降維算法。
PAC也可用于高維數(shù)據(jù)壓縮、高維數(shù)據(jù)可視化(轉(zhuǎn)二維或三維后就可以畫圖顯示)等方面,也是其它很多圖像處理算法的預(yù)處理步驟。
PCA的計(jì)算
關(guān)于PCA,網(wǎng)上一搜還是不少的,但我仔細(xì)看了幾篇文章之后,發(fā)現(xiàn)這些文章講的跟書上講的有些地方不一致,甚至連計(jì)算時(shí)的公式都不一樣,這讓我產(chǎn)生了很多困惑。所以我想最重要的還是要理解PCA的數(shù)學(xué)原理,數(shù)學(xué)原理才是根,掌握了數(shù)學(xué)原理,再來寫代碼。恰好找到一篇文章專門講PCA數(shù)學(xué)原理,作者的數(shù)學(xué)功底和邏輯表達(dá)能力非常棒,讓我很容易看明白。另外,我也找到一篇老外寫的文章(見底部參考文章),這兩篇文章對(duì)PCA的計(jì)算描述是一致的,所以我決定在這兩篇文章的基礎(chǔ)上,結(jié)合書上的示例代碼進(jìn)行學(xué)習(xí)和驗(yàn)證。
本文不是要要把PCA的數(shù)學(xué)原理及推導(dǎo)寫出來,而是通過理解PCA的數(shù)學(xué)原理,總結(jié)PCA的計(jì)算步驟,因?yàn)橛?jì)算步驟是代碼實(shí)現(xiàn)的必備知識(shí)。
PCA的計(jì)算過程涉及到幾個(gè)很重要的數(shù)學(xué)知識(shí)點(diǎn):
-
零均值化
-
矩陣的轉(zhuǎn)置及乘法
-
協(xié)方差與協(xié)方差矩陣
-
特征值及特征向量
現(xiàn)在來看PCA的計(jì)算步驟:
1)將原始數(shù)據(jù)按列組成d行n列矩陣X
重要說明:d對(duì)應(yīng)的就是數(shù)據(jù)的字段(或叫變量、特征、維,下稱’維‘),而n表示n條記錄(或叫樣本、觀察值,下稱’樣本‘),即每1列對(duì)應(yīng)1個(gè)樣本,之所以行和列這樣安排,是為了與數(shù)學(xué)公式保持一致,很多文章對(duì)這一點(diǎn)都沒有明確的說明,導(dǎo)致計(jì)算的方式各有不同,讓人產(chǎn)生不必要的困惑
2)將X的每個(gè)維(行)進(jìn)行零均值化,即將行的每個(gè)元素減去這一行的均值
3)求出X的協(xié)方差矩陣C,即 X 乘 X的轉(zhuǎn)置
4)求出C所有的特征值及對(duì)應(yīng)的特征向量
5)將特征向量按對(duì)應(yīng)特征值大小從上到下按行排列成矩陣,取前k行組成矩陣E
6)Y=EX即為降維到k維后的數(shù)據(jù)
下面用一個(gè)例子來驗(yàn)證一下這個(gè)計(jì)算過程。
黑白圖像的PCA實(shí)現(xiàn)
書中的例子是用PCA計(jì)算特征臉(人臉識(shí)別中的一步),它應(yīng)用在多張圖片上面。為直觀起見,我用另外一個(gè)例子——對(duì)單張黑白圖像進(jìn)行PCA,相當(dāng)于把二維圖像降為一維。
把圖像轉(zhuǎn)成二維矩陣
這張圖像是原書封面:
下面代碼是將圖中‘怪魚’部分截取出來,并轉(zhuǎn)成黑白圖像顯示:
from PIL import Image pim = Image.open('cover.png').crop((110,360,460,675)).convert('1') pim.show() 效果如圖:
之所以截取這部分的圖片,是因?yàn)槲覀兇蟾拍懿碌竭@幅圖像降到一維后,其一維表示的向量應(yīng)該跟怪魚的方向大概一致。
使用黑白圖像是因?yàn)楹邳c(diǎn)才是我們關(guān)心的數(shù)據(jù),因?yàn)槭沁@些黑點(diǎn)描繪了圖像,每個(gè)黑點(diǎn)有唯一確定的行和列位置,對(duì)應(yīng)平面上的(x,y)坐標(biāo),于是我們就可以得到此圖像的 2乘n 矩陣表示:第一行表示x維,第二行表示y維,每一列表示一個(gè)點(diǎn)。參考代碼:
import numpy as np import matplotlib.pyplot as plt from PIL import Imageim = np.array(Image.open('cover.png').crop((110,360,460,675)).resize((256,230)).convert('L')) n,m = im.shape[0:2] points = [] for i in range(n):for j in range(m):if im[i,j] < 128.0: #把小于128的灰度值當(dāng)作黑點(diǎn)取出來points.append([float(j), float(n) - float(i)]) #坐標(biāo)轉(zhuǎn)換一下im_X = np.mat(points).T; #轉(zhuǎn)置之后,行表示維度(x和y),每列表示一個(gè)點(diǎn)(樣本) print 'im_X=',im_X,'shape=',im_X.shape現(xiàn)在,我們按上面說明的計(jì)算步驟來實(shí)現(xiàn)PCA:
def pca(X, k=1): #降為k維d,n = X.shapemean_X = np.mean(X, axis=1) #axis為0表示計(jì)算每列的均值,為1表示計(jì)算每行均值print 'mean_X=',mean_XX = X - mean_X#計(jì)算不同維度間的協(xié)方差,而不是樣本間的協(xié)方差,方法1:#C = np.cov(X, rowvar=1) #計(jì)算協(xié)方差,rowvar為0則X的行表示樣本,列表示特征/維度#方法2:C = np.dot(X, X.T)e,EV = np.linalg.eig(np.mat(C)) #求協(xié)方差的特征值和特征向量print 'C=',Cprint 'e=',eprint 'EV=',EVe_idx = np.argsort(-e)[:k] #獲取前k個(gè)最大的特征值對(duì)應(yīng)的下標(biāo)(注:這里使用對(duì)負(fù)e排序的技巧,反而讓原本最大的排在前面)EV_main = EV[:,e_idx] #獲取特征值(下標(biāo))對(duì)應(yīng)的特征向量,作為主成分print 'e_idx=',e_idx,'EV_main=',EV_main low_X = np.dot(EV_main.T, X) #這就是我們要的原始數(shù)據(jù)集在主成分上的投影結(jié)果return low_X, EV_main, mean_XOK,現(xiàn)在我們調(diào)用此PCA函數(shù),并把原圖像和投影到一維向量后的結(jié)果也描繪出來:
low_X, EV_main, mean_X = pca(im_X) print "low_X=",low_X print "EV_main=",EV_main recon_X = np.dot(EV_main, low_X) + mean_X #把投影結(jié)果重構(gòu)為二維表示,以便可以畫出來直觀的看到 print "recon_X.shape=",recon_X.shapefig = plt.figure() ax = fig.add_subplot(111) ax.scatter(im_X[0].A[0], im_X[1].A[0],s=1,alpha=0.5) ax.scatter(recon_X[0].A[0], recon_X[1].A[0],marker='o',s=100,c='blue',edgecolors='white') plt.show()畫散點(diǎn)圖函數(shù)pyplot.scatter說明:
matplotlib.pyplot.scatter(x, y, ...) x: 數(shù)組,樣本點(diǎn)在X軸上的坐標(biāo)集 y: 數(shù)組,樣本點(diǎn)在Y軸上的坐標(biāo)集 s: 表示畫出來的點(diǎn)的縮放大小 c: 表示畫出來的點(diǎn)(小圓圈)的內(nèi)部顏色 edgecolors: 表示小圓圈的邊緣顏色運(yùn)行以上代碼打印:
im_X= [[ 23. 24. 25. ..., 215. 216. 217.][ 230. 230. 230. ..., 5. 5. 5.]] shape= (2, 19124) mean_X= [[ 133.8574566 ][ 123.75941226]] C= [[ 2951.65745054 -1202.25277635][-1202.25277635 3142.71830026]] e= [ 1841.14567037 4253.23008043] EV= [[-0.73457806 0.67852419][-0.67852419 -0.73457806]] e_idx= [1] EV_main= [[ 0.67852419][-0.73457806]] low_X= [[-153.26147057 -152.58294638 -151.90442219 ..., 142.29523704142.97376123 143.65228541]] EV_main= [[ 0.67852419][-0.73457806]] recon_X.shape= (2, 19124) 并顯示如下圖像:
從圖中看出,向量的方向跟位置與我們目測的比較一致。向量上的大量藍(lán)色圓點(diǎn)(白色邊緣)表示二維數(shù)據(jù)在其上的投影。
小結(jié)
以上實(shí)現(xiàn)的PCA算法,跟我參考的兩篇文章所講的原理一致。但跟書中的PCA計(jì)算方法有一定的不同,但也因?yàn)槭褂玫睦硬灰粯?#xff0c;對(duì)原始數(shù)據(jù)集的定義不一樣導(dǎo)致的,由于避免文章過長,這些放在后面再講。
至此,通過對(duì)PCA的計(jì)算過程的學(xué)習(xí),了解了一些線性代數(shù)知識(shí)、numpy和pyplot模塊的一些接口的用法,接下來我打算做點(diǎn)更有興趣的事情——就是使用PCA來實(shí)現(xiàn)人臉識(shí)別。
參考鏈接:
PCA的數(shù)學(xué)原理
A Tutorial on Principal Component Analysis
NumPy函數(shù)索引
總結(jié)
以上是生活随笔為你收集整理的Programming Computer Vision with Python (学习笔记三)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Programming Computer
- 下一篇: Programming Computer