別原理與Python實(shí)現(xiàn):從特征臉到門禁系統(tǒng))
簡(jiǎn)介基于Python的PCA人臉識(shí)別算法原理與實(shí)現(xiàn)資源包面向課程設(shè)計(jì)與期末大作業(yè)場(chǎng)景適合希望快速掌握人臉識(shí)別核心流程的Python初學(xué)者。資源圍繞PCA特征臉?lè)椒ㄕ归_(kāi)涵蓋樣本均值計(jì)算、協(xié)方差矩陣構(gòu)建、特征值分解、降維投影及最近鄰匹配等關(guān)鍵環(huán)節(jié)配合可運(yùn)行的Python主程序和說(shuō)明文檔讓讀者既能理解算法推導(dǎo)又能直接看到實(shí)際運(yùn)行效果。包內(nèi)還提供10維、20維、30維、40維等不同主成分維度下的結(jié)果對(duì)比圖直觀反映維度選擇對(duì)識(shí)別精度和重建質(zhì)量的影響。壓縮包共8個(gè)文件以py程序、txt說(shuō)明和png圖像為主整體容量?jī)H257KB結(jié)構(gòu)緊湊無(wú)冗余。目前已有449人學(xué)習(xí)下載適合零基礎(chǔ)學(xué)生作為高分開(kāi)課設(shè)、人臉識(shí)別入門或期末項(xiàng)目答辯的參考模板。1. 為什么人臉識(shí)別入門選了PCA從特征臉說(shuō)起如果你找過(guò)人臉識(shí)別算法相關(guān)資料大概率會(huì)撞見(jiàn)PCA和特征臉Eigenface這個(gè)組合。哪怕現(xiàn)在門禁機(jī)里跑的已經(jīng)是深度學(xué)習(xí)模型PCA在人臉識(shí)別領(lǐng)域仍然是繞不開(kāi)的第一課。原因很現(xiàn)實(shí)它不需要GPU不需要海量標(biāo)注數(shù)據(jù)幾十張灰度圖丟進(jìn)去numpy就能算出人臉主成分然后做識(shí)別。對(duì)于畢設(shè)、課程設(shè)計(jì)、轉(zhuǎn)行練手這是最容易跑通、也最容易解釋清楚的方案。這個(gè)標(biāo)題里的PCA人臉識(shí)別解決的是已知一批標(biāo)注好的人臉照片判斷一張新照片是誰(shuí)的問(wèn)題。它把每一張人臉當(dāng)成高維空間的一個(gè)點(diǎn)用主成分分析找出人臉?lè)植嫉闹鞣较蛟侔阉腥四樛队暗降途S空間最后用最近鄰判斷身份。整個(gè)過(guò)程邏輯清晰參數(shù)少代碼量小尤其適合第一次動(dòng)手做圖像識(shí)別的人。如果你剛接觸Python圖像處理想用一份能跑的代碼理解降維和分類是怎么配合的這個(gè)方向非常合適。2. PCA人臉識(shí)別的核心原理高維空間里找人臉主方向2.1 人臉圖像的本質(zhì)一個(gè)像素就是一個(gè)維度要理解PCA先得接受一個(gè)抽象一張灰度圖就是一個(gè)高維向量。假設(shè)人臉圖統(tǒng)一縮放到64×64像素那么它就有4096個(gè)像素。把每個(gè)像素的亮度值按行拉平就得到一個(gè)4096維的向量。如果有100張人臉那就是100個(gè)點(diǎn)分布在4096維空間里。問(wèn)題在于4096維空間太稀疏了。這些點(diǎn)不可能是均勻撒開(kāi)的它們被人臉的結(jié)構(gòu)約束著眼睛、鼻子、嘴的相對(duì)位置大致固定光照、表情帶來(lái)的變化集中在某些方向上。PCA要做的就是在這4096維空間里找到一組新的坐標(biāo)軸讓數(shù)據(jù)在這些軸上的方差依次遞減——第一個(gè)軸方向信息量最大第二個(gè)其次最后我們只用前幾十個(gè)軸就能近似還原人臉。這就是主成分分析這個(gè)名字的來(lái)歷。2.2 主成分分析與特征臉降維后的人臉基函數(shù)把一個(gè)包含N張人臉、每張M維的數(shù)據(jù)矩陣XN行M列行是人臉列是像素做中心化每列減去均值得到X_c。然后計(jì)算協(xié)方差矩陣 X_c^T * X_c / (N-1)對(duì)它做特征值分解。較大的特征值對(duì)應(yīng)的特征向量就是主成分。用人臉圖像算出來(lái)的主成分如果還原成圖片看起來(lái)就像一張模糊的鬼臉?biāo)员环Q為特征臉Eigenface。所有訓(xùn)練人臉都可以近似表示為均值臉 若干個(gè)特征臉的線性組合。組合系數(shù)就是人臉在低維空間里的坐標(biāo)。關(guān)鍵點(diǎn)這里用的協(xié)方差矩陣維度是M×M也就是像素?cái)?shù)×像素?cái)?shù)。64×64的圖是4096×4096還可以接受如果原圖是200×200那就是40000×40000內(nèi)存直接爆掉。所以工程上常用SVD奇異值分解繞開(kāi)這個(gè)大矩陣或者先把圖像縮小。后面實(shí)現(xiàn)代碼里會(huì)講具體做法。2.3 識(shí)別流程訓(xùn)練與匹配兩段式PCA人臉識(shí)別的完整流程分兩步。訓(xùn)練階段讀入所有標(biāo)注好的人臉圖縮放、灰度化、拉成向量計(jì)算均值臉和特征臉確定保留K個(gè)主成分把每張訓(xùn)練人臉投影到K維子空間得到模板向量。識(shí)別階段對(duì)一張新圖片做同樣的預(yù)處理投影到同一個(gè)子空間得到查詢向量計(jì)算查詢向量與所有模板向量之間的距離取最近的那個(gè)如果距離小于預(yù)設(shè)閾值就判定為對(duì)應(yīng)身份否則認(rèn)為無(wú)法識(shí)別。這里有一個(gè)容易被忽略的前提投影矩陣只能由訓(xùn)練集確定。新來(lái)的圖片不能拿來(lái)重新算特征臉?lè)駝t就泄漏了未知信息對(duì)單張照片做門禁識(shí)別時(shí)也不現(xiàn)實(shí)。所以先固化特征臉參數(shù)再對(duì)每張測(cè)試圖只做矩陣乘法。3. 用Python從零實(shí)現(xiàn)PCA人臉識(shí)別可復(fù)現(xiàn)的最小代碼3.1 數(shù)據(jù)準(zhǔn)備用現(xiàn)有數(shù)據(jù)集還是一個(gè)文件夾最常見(jiàn)的數(shù)據(jù)集是ATT原ORL人臉庫(kù)包含40個(gè)人每人10張112×92灰度圖。如果你手頭沒(méi)有也可以自己建文件夾每個(gè)子文件夾放一個(gè)人的照片要求人臉基本居中、尺度近似。下面這段代碼負(fù)責(zé)加載數(shù)據(jù)返回一個(gè)二維數(shù)組和對(duì)應(yīng)的標(biāo)簽import os import cv2 import numpy as np def load_faces(data_dir, img_size(64, 64)): X [] y [] for person_id, person_name in enumerate(sorted(os.listdir(data_dir))): person_dir os.path.join(data_dir, person_name) if not os.path.isdir(person_dir): continue for img_name in sorted(os.listdir(person_dir)): if not img_name.lower().endswith((.jpg, .png)): continue img_path os.path.join(person_dir, img_name) img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) img cv2.resize(img, img_size) X.append(img.flatten()) # 拉成一維 y.append(person_id) return np.array(X), np.array(y)邏輯說(shuō)明person_id從0開(kāi)始按文件夾名字母序編號(hào)cv2.imread第二個(gè)參數(shù)傳cv2.IMREAD_GRAYSCALE避免讀成三通道后再手動(dòng)轉(zhuǎn)換flatten()默認(rèn)按行拉平所以同一個(gè)人不同圖片要保持相同尺寸。參數(shù)里img_size(64,64)是經(jīng)驗(yàn)值太小丟細(xì)節(jié)太大會(huì)拖慢特征分解。如果數(shù)據(jù)量小32×32也夠用。3.2 核心函數(shù)中心化、特征臉、投影數(shù)據(jù)準(zhǔn)備好了就進(jìn)入PCA核心。這里我采用SVD方式實(shí)現(xiàn)原因后面詳述。主要函數(shù)有三部分計(jì)算均值臉、計(jì)算主成分、將數(shù)據(jù)投影到低維空間。def pca_fit(X, n_components): # X: shape (n_samples, n_features) mean_face X.mean(axis0) X_centered X - mean_face # 用SVD而不是直接算協(xié)方差矩陣的特征分解 U, S, Vt np.linalg.svd(X_centered, full_matricesFalse) # 前n_components個(gè)右奇異向量就是主成分方向 eigenvectors Vt[:n_components] # 投影將中心化后的數(shù)據(jù)轉(zhuǎn)到低維空間 X_projected X_centered.dot(eigenvectors.T) return mean_face, eigenvectors, X_projected def pca_transform(X_new, mean_face, eigenvectors): X_centered X_new - mean_face return X_centered.dot(eigenvectors.T)邏輯說(shuō)明np.linalg.svd(X_centered, full_matricesFalse)對(duì)數(shù)據(jù)矩陣直接做奇異值分解返回的Vt行向量是協(xié)方差矩陣的特征向量對(duì)應(yīng)的奇異值平方與特征值成正比。相比np.linalg.eigh去分解M×M的協(xié)方差矩陣SVD在樣本數(shù)遠(yuǎn)小于像素?cái)?shù)時(shí)更省內(nèi)存、數(shù)值更穩(wěn)定。eigenvectors的每一行是特征臉與公式里的特征向量方向一致。X_projected是訓(xùn)練集在低維空間的坐標(biāo)每行對(duì)應(yīng)一個(gè)人。參數(shù)說(shuō)明n_components就是保留的主成分個(gè)數(shù)。它不能超過(guò)樣本數(shù)N和特征維度M的最小值。SVD模式下Vt最多有min(N,M)行所以n_components必須小于等于訓(xùn)練集圖片張數(shù)。例如用40人×10張400張圖訓(xùn)練最多只能取399個(gè)主成分而實(shí)際我們通常只取幾十個(gè)。3.3 識(shí)別新圖片最近鄰與閾值拒絕有了投影坐標(biāo)識(shí)別就是距離計(jì)算。常見(jiàn)做法是歐氏距離也可以用余弦相似度。這里演示一個(gè)簡(jiǎn)單的最近鄰分類器外加陌生人拒識(shí)的初步思路。def predict(X_query, X_projected, labels, mean_face, eigenvectors, thresholdNone): q pca_transform(X_query.reshape(1, -1), mean_face, eigenvectors) distances np.linalg.norm(X_projected - q, axis1) idx np.argmin(distances) min_dist distances[idx] if threshold is not None and min_dist threshold: return -1, min_dist # -1 表示不在庫(kù)中 return labels[idx], min_dist邏輯說(shuō)明X_query是經(jīng)過(guò)同樣預(yù)處理的新圖片向量pca_transform把它投影到訓(xùn)練好的子空間np.linalg.norm(..., axis1)計(jì)算查詢點(diǎn)與所有訓(xùn)練點(diǎn)之間的歐氏距離。閾值的作用是拒絕庫(kù)里沒(méi)有的人門禁系統(tǒng)里如果來(lái)了一個(gè)陌生人他的投影距離必然較遠(yuǎn)直接返回-1。閾值需要實(shí)測(cè)校準(zhǔn)沒(méi)有萬(wàn)能值我是拿一部分已知人臉求距離分布后取分位數(shù)定的。需要特別說(shuō)明識(shí)別時(shí)不能重新做PCA必須復(fù)用訓(xùn)練好的mean_face和eigenvectors。一旦訓(xùn)練集變動(dòng)就要重新訓(xùn)練并更新投影矩陣否則新舊坐標(biāo)系不一致距離毫無(wú)意義。3.4 參數(shù)選擇保留多少主成分主成分?jǐn)?shù)n_components直接決定信息保留比例和最終準(zhǔn)確率。一般先畫出方差貢獻(xiàn)率曲線再?zèng)Q定保留多少個(gè)主成分。def explained_variance_ratio(s, n_total): # s是SVD返回的奇異值奇異值平方除以總方差就是每個(gè)主成分的貢獻(xiàn)率 variance s ** 2 ratio variance / variance.sum() cumulative np.cumsum(ratio) return cumulative # 使用示例 _, s, _ np.linalg.svd(X_centered, full_matricesFalse) cum explained_variance_ratio(s, X.shape[0]) k np.searchsorted(cum, 0.95) 1 # 保留95%方差的第一個(gè)位置邏輯說(shuō)明searchsorted(cum, 0.95)返回cum中第一個(gè)大于等于0.95的索引1是因?yàn)樗饕龔?開(kāi)始。這樣選出來(lái)的k是保留95%總方差所需的最小主成分?jǐn)?shù)量。實(shí)際經(jīng)驗(yàn)ORL數(shù)據(jù)集上保留前30~50個(gè)主成分通常就能達(dá)到95%以上的識(shí)別率保留太多反而把噪聲和單人特有的細(xì)節(jié)裝進(jìn)來(lái)泛化能力變差。4. 把準(zhǔn)確率做上去參數(shù)調(diào)優(yōu)與數(shù)據(jù)預(yù)處理4.1 圖像尺寸與灰度歸一化決定了特征臉的分辨率PCA的輸入是像素灰度值像素值范圍、圖像尺寸直接改變主成分分布。最常見(jiàn)的做法是把所有人臉縮放到相同尺寸比如64×64。尺寸太小眼睛和嘴巴的信息丟失特征臉會(huì)糊成一片尺寸過(guò)大比如256×256特征維度65536維SVD速度和內(nèi)存都會(huì)上升而識(shí)別率提升有限。灰度歸一化也不能省。不同照片的全局亮度差別很大原圖可能是0~255的灰度但整體偏暗或偏亮。若不處理第一個(gè)主成分往往不是人臉結(jié)構(gòu)而是整體亮度——像一張白板區(qū)分亮臉和暗臉。所以我會(huì)對(duì)每張圖做歸一化def normalize_face(img): img img.astype(np.float32) img (img - img.min()) / (img.max() - img.min() 1e-6) return img邏輯說(shuō)明把每張圖獨(dú)立拉伸到[0,1]區(qū)間消除全局亮度差異。注意這里有個(gè)坑逐圖歸一化適合單張圖亮度不均的場(chǎng)景但如果同一人照片集里本來(lái)就含有不同光照逐圖歸一化會(huì)抹平光照差異反而有利于PCA。如果所有圖來(lái)自同一相機(jī)也可以做全局歸一化用所有圖的同一均值方差兩種都要對(duì)比實(shí)測(cè)后選擇。1e-6防止除以0。4.2 光照、對(duì)齊與直方圖均衡是翻車重災(zāi)區(qū)人臉識(shí)別最經(jīng)典的敵人是光照和對(duì)齊。PCA對(duì)像素位置極其敏感眼睛坐標(biāo)偏移幾個(gè)像素拉成向量后就是一次全局抖動(dòng)距離計(jì)算立刻變大。所以數(shù)據(jù)預(yù)處理階段必須做兩件額外工作人臉對(duì)齊和光照歸一化。對(duì)齊可以使用OpenCV的cv2.detectMultiScale檢測(cè)人臉框然后用兩只眼睛的坐標(biāo)做仿射變換把兩眼連線旋轉(zhuǎn)到水平并且統(tǒng)一兩眼間距。如果不想引入額外的人臉檢測(cè)器至少要做到裁剪時(shí)以人臉中心為基準(zhǔn)保證鼻子在中心區(qū)域。實(shí)操里我習(xí)慣先用一個(gè)簡(jiǎn)單的Haar級(jí)聯(lián)定位眼睛# 假設(shè)已檢測(cè)到左眼(left_x, left_y)和右眼(right_x, right_y) dx right_x - left_x dy right_y - left_y angle np.degrees(np.arctan2(dy, dx)) center ((left_x right_x) / 2.0, (left_y right_y) / 2.0) M cv2.getRotationMatrix2D(center, angle, scale1.0) aligned cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))邏輯說(shuō)明先算出兩眼連線與水平線的夾角再以兩眼中點(diǎn)為旋轉(zhuǎn)中心將整張圖旋轉(zhuǎn)該角度。這樣人臉的傾斜姿態(tài)被粗略糾正。旋轉(zhuǎn)后還要再裁剪固定區(qū)域取包含額頭到下巴的矩形。對(duì)齊是所有傳統(tǒng)人臉識(shí)別方案的命脈不做對(duì)齊再好的降維算法也白搭。光照歸一化除了上面提到的線性拉伸還可以使用直方圖均衡化。cv2.equalizeHist可以把人臉的亮度分布拉平減少側(cè)光造成的半邊臉陰影。img cv2.equalizeHist(img)參數(shù)說(shuō)明equalizeHist只接受8位單通道圖如果前面已經(jīng)轉(zhuǎn)成float歸一化需要先轉(zhuǎn)回uint8。我在實(shí)驗(yàn)中直方圖均衡通常能把ORL上的識(shí)別率提升1~3個(gè)百分點(diǎn)尤其當(dāng)訓(xùn)練集和測(cè)試集拍攝環(huán)境不一致時(shí)效果明顯。4.3 主成分?jǐn)?shù)K的選取策略不是越多越好很多初學(xué)者以為保留的主成分越多信息越全準(zhǔn)確率越高。實(shí)際在PCA人臉識(shí)別里K太小會(huì)丟掉身份特征K太大會(huì)引入噪聲。比如某人的照片可能有眼鏡反光、表情擠壓這些細(xì)節(jié)被編碼進(jìn)高編號(hào)的主成分后同一個(gè)人不同照片之間的距離反而被拉大識(shí)別率下降。我的經(jīng)驗(yàn)做法是畫一條K-準(zhǔn)確率曲線從5開(kāi)始每隔5取一個(gè)K訓(xùn)練并測(cè)試找到曲線平臺(tái)期然后在平臺(tái)區(qū)間的下限取K。這樣選出的K既穩(wěn)定又帶泛化能力。對(duì)ORL數(shù)據(jù)集K在20~40之間基本就是平臺(tái)如果你只拿10個(gè)人做演示K10左右就夠了。識(shí)別正確率的驗(yàn)證方式很關(guān)鍵必須保證測(cè)試集的人沒(méi)有出現(xiàn)在訓(xùn)練集里。具體做法是按人分組比如每人10張圖中取8張訓(xùn)練、2張測(cè)試。如果隨機(jī)把圖片打進(jìn)訓(xùn)練集同一人的照片可能同時(shí)出現(xiàn)在兩側(cè)等于開(kāi)卷考試準(zhǔn)確率虛高。4.4 距離度量歐氏距離、曼哈頓距離與余弦相似度投影到低維空間后如何定義兩張臉長(zhǎng)得像直接決定識(shí)別結(jié)果。三種常見(jiàn)度量各有脾氣歐氏距離對(duì)每個(gè)維度的權(quán)重平等看待是最常用的默認(rèn)選擇。但它受主成分尺度影響大如果前幾個(gè)主成分方差很大歐氏距離會(huì)被它們主導(dǎo)。曼哈頓距離L1對(duì)離群點(diǎn)更不敏感在光照變化明顯的場(chǎng)景下有時(shí)比歐氏穩(wěn)。余弦相似度只關(guān)心方向、不關(guān)心長(zhǎng)度適合特征向量整體縮放不改變身份的場(chǎng)景。我之前在自建數(shù)據(jù)集上對(duì)比過(guò)歐氏和余弦差異不大但曼哈頓略好一點(diǎn)??赡芤?yàn)镻CA子空間里不同主成分的方差量級(jí)不同L1距離在投影后更接近模板匹配的直覺(jué)。建議你把三種距離都實(shí)現(xiàn)交叉驗(yàn)證時(shí)選小的。代碼如下def compute_distances(q, X_projected, metriceuclidean): if metric euclidean: return np.linalg.norm(X_projected - q, axis1) elif metric manhattan: return np.sum(np.abs(X_projected - q), axis1) elif metric cosine: q_norm np.linalg.norm(q) proj_norm np.linalg.norm(X_projected, axis1) return 1.0 - (X_projected q.ravel()) / (proj_norm * q_norm 1e-6)邏輯說(shuō)明余弦相似度轉(zhuǎn)成距離時(shí)用1 - 相似度數(shù)值越小代表越相似。注意X_projected q.ravel()計(jì)算的是每個(gè)訓(xùn)練樣本與查詢向量的點(diǎn)積結(jié)果是一維數(shù)組。1e-6是為了防止某個(gè)向量模長(zhǎng)為0。實(shí)際使用時(shí)所有模板向量和查詢向量都要先做同樣的投影然后compute_distances返回一維距離數(shù)組。5. PCA人臉識(shí)別避坑指南5個(gè)容易翻車的細(xì)節(jié)5.1 讀圖時(shí)通道混亂導(dǎo)致特征臉花掉現(xiàn)象訓(xùn)練集加載后顯示特征臉變成紅一塊藍(lán)一塊或者明明都是灰度圖flatten()出來(lái)卻有三倍長(zhǎng)度。原因cv2.imread(path)默認(rèn)按BGR三通道讀取即使原圖是灰度圖也會(huì)被擴(kuò)成(M, N, 3)。有人圖省事直接用img.flatten()把三個(gè)通道串在一起每個(gè)像素被當(dāng)成三個(gè)獨(dú)立維度。解決讀圖時(shí)顯式指定cv2.IMREAD_GRAYSCALE如果圖像已經(jīng)讀成了彩色先用cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)轉(zhuǎn)灰度。另外訓(xùn)練前打印X.shape確認(rèn)列數(shù)是寬×高而不是寬×高×3這是最便宜的檢查手段。5.2 中心化用錯(cuò)方差方向按行還是按列現(xiàn)象計(jì)算協(xié)方差時(shí)有人寫成np.cov(X, rowvarTrue)默認(rèn)把每一行當(dāng)成一個(gè)變量、每一列當(dāng)成一個(gè)樣本結(jié)果特征臉完全混亂投影距離毫無(wú)規(guī)律。原因數(shù)據(jù)矩陣X是一行一張人臉行是樣本、列是特征。PCA需要按列中心化即每個(gè)像素位置減去該像素在所有圖片上的平均值。協(xié)方差應(yīng)該描述像素與像素之間的相關(guān)性而不是圖片與圖片之間的相關(guān)性。解決直接用X - X.mean(axis0)不要用np.cov或者使用np.cov(X, rowvarFalse)。我一般用SVD替代協(xié)方差因?yàn)閚p.linalg.svd(X_centered, full_matricesFalse)自動(dòng)把行當(dāng)樣本、列當(dāng)特征避免混淆。代碼里已經(jīng)這么寫但你要明白為什么。5.3 特征向量符號(hào)翻轉(zhuǎn)和排序問(wèn)題現(xiàn)象同樣的數(shù)據(jù)兩次訓(xùn)練得到的特征臉視覺(jué)方向相反比如一個(gè)向右的黑白漸變另一次向左。特征值排序不穩(wěn)定導(dǎo)致主成分順序改變。原因特征向量和奇異向量的符號(hào)是任意的np.linalg.svd和np.linalg.eigh都可能輸出符號(hào)相反的向量。這本身不影響投影后的距離因?yàn)橥队跋禂?shù)也會(huì)變號(hào)但如果你手動(dòng)排序時(shí)把特征值排序和特征向量排序解耦了就會(huì)張冠李戴。解決不要手動(dòng)按特征值排序直接用Vt的順序SVD返回的奇異值本來(lái)就是降序。如果你用np.linalg.eigh注意它返回的特征值升序要反過(guò)來(lái)用eigenvalues, eigenvectors np.linalg.eigh(cov) idx np.argsort(eigenvalues)[::-1] eigenvalues eigenvalues[idx] eigenvectors eigenvectors[:, idx]邏輯說(shuō)明eigh返回的是升序特征值argsort()[::-1]得到降序索引。這里eigenvectors是列向量索引時(shí)對(duì)第二維操作。如果忘記這一步取前K列相當(dāng)于取了方差最小的K個(gè)方向識(shí)別率會(huì)差一截。5.4 數(shù)據(jù)量小于維度時(shí)協(xié)方差矩陣爆內(nèi)存現(xiàn)象圖像尺寸128×128特征維度16384訓(xùn)練集只有500張圖直接算協(xié)方差矩陣需要16384×16384×8字節(jié)約2GB內(nèi)存機(jī)器直接卡死。原因協(xié)方差矩陣大小是特征維度×特征維度與樣本數(shù)無(wú)關(guān)。人臉圖像維度遠(yuǎn)超樣本數(shù)直接分解協(xié)方差矩陣是典型的內(nèi)存陷阱。解決使用SVD它只需要存儲(chǔ)(N×M)的數(shù)據(jù)矩陣(500×16384約65MB)和參與分解的矩陣。full_matricesFalse限制輸出尺寸避免生成M×M的完整U矩陣。如果樣本數(shù)N比特征維度M小很多還記得PCA的本質(zhì)是N-1個(gè)子空間所以n_components最大只能取到N-1不需要去惦記那個(gè)巨大的協(xié)方差空間。5.5 先中心化再標(biāo)準(zhǔn)化的順序陷阱現(xiàn)象數(shù)據(jù)預(yù)處理時(shí)如果先對(duì)每個(gè)特征做標(biāo)準(zhǔn)化減去均值除以標(biāo)準(zhǔn)差再做PCA發(fā)現(xiàn)特征臉外觀完全不同識(shí)別率也曾好曾壞。原因PCA本身依賴方差先標(biāo)準(zhǔn)化會(huì)把所有像素方差強(qiáng)行拉到同一尺度。有人認(rèn)為這樣可以避免大亮度區(qū)域主導(dǎo)但如果某個(gè)像素位置方差很小標(biāo)準(zhǔn)化后會(huì)把它放大成重要特征實(shí)際上是放大了噪聲。解決常規(guī)PCA人臉識(shí)別只做中心化不做按列標(biāo)準(zhǔn)化。灰度歸一化是對(duì)每張圖做全局拉伸不是對(duì)每個(gè)像素做標(biāo)準(zhǔn)化。如果你想消除像素量綱影響可以測(cè)試標(biāo)準(zhǔn)化版本但要意識(shí)到它改變了主成分的意義——用標(biāo)準(zhǔn)化后的PCA和原始PCA識(shí)別結(jié)果需要重新調(diào)K和閾值。我一般保持中心化只在光照差異大的數(shù)據(jù)集上試驗(yàn)標(biāo)準(zhǔn)化。6. 讓PCA人臉識(shí)別更實(shí)用自建門禁數(shù)據(jù)集的進(jìn)階玩法6.1 訓(xùn)練集擴(kuò)充鏡像、平移與亮度擾動(dòng)原始數(shù)據(jù)每人只有幾張照片識(shí)別率很容易波動(dòng)。簡(jiǎn)單有效的擴(kuò)充方式是把每張訓(xùn)練圖做左右翻轉(zhuǎn)、平移一到兩個(gè)像素、加一點(diǎn)高斯噪聲。這樣能把人臉姿態(tài)和微小對(duì)齊誤差的魯棒性喂給模型。但要注意測(cè)試集不要用同一張圖擴(kuò)充后的樣本否則就是變相泄漏。6.2 識(shí)別閾值如何校準(zhǔn)門禁類應(yīng)用里陌生人拒識(shí)比熟人誤識(shí)更重要。我會(huì)先用訓(xùn)練集自身的投影距離分布定一個(gè)初值統(tǒng)計(jì)每個(gè)訓(xùn)練樣本與它同身份最近鄰的距離取95%分位數(shù)作為閾值下限。然后采集一批不在庫(kù)中的人臉圖片計(jì)算它們到最近鄰居的距離觀察兩者分布的重疊區(qū)域閾值取在重疊區(qū)靠陌生人一側(cè)壓低誤識(shí)率。6.3 和深度學(xué)習(xí)方案對(duì)比什么時(shí)候PCA夠用純PCA在光照劇烈變化、姿態(tài)大角度偏轉(zhuǎn)、遮擋面前準(zhǔn)確率會(huì)被CNN方案甩開(kāi)。但如果你的場(chǎng)景滿足三個(gè)條件——人臉正對(duì)、光線可控、庫(kù)內(nèi)人員數(shù)量不大PCA依然是性價(jià)比最高的選擇訓(xùn)練秒級(jí)完成模型只有幾個(gè)矩陣和均值臉內(nèi)存占用不到1MB非常適合嵌入式門禁機(jī)、樹(shù)莓派這類資源緊張的環(huán)境。我自己的習(xí)慣是先拿PCA跑通全流程再根據(jù)瓶頸決定是否上深度學(xué)習(xí)。這條路徑能幫你快速理解特征提取、降維、分類和閾值怎么配合而這些經(jīng)驗(yàn)在后來(lái)調(diào)任何識(shí)別模型都通用。實(shí)際部署時(shí)把均值臉、特征向量、模板投影存成.npy文件識(shí)別端只加載這些參數(shù)做矩陣乘法和距離計(jì)算。這樣訓(xùn)練和識(shí)別分離代碼結(jié)構(gòu)更清晰也避免每次啟動(dòng)都要重新算PCA。希望這套實(shí)現(xiàn)可以成為你手里第一個(gè)能跑通的人臉識(shí)別系統(tǒng)也讓你在后續(xù)換用深度學(xué)習(xí)方案時(shí)知道自己在跟什么做對(duì)比。本文還有配套的精品資源點(diǎn)擊獲取