
簡介基于 PCA主成分分析的人臉識別 MATLAB 實踐資源適合正在學習機器學習與圖像處理的高校學生和初學者用來解決如何通過降維提取人臉關(guān)鍵特征并完成身份識別的問題也適合用于實驗教學和課程設(shè)計。壓縮包共 44 個文件大小僅 332KB其中 40 個 BMP 圖像按 train 與 test 目錄組織分別提供訓練集和測試集樣本4 個 .m 文件包含主程序、PCA 計算函數(shù)以及 Min 等輔助函數(shù)可直接在 MATLAB 中運行復現(xiàn)。目前已有 181 人瀏覽學習。通過這套資源讀者能完整走通從圖像讀取、灰度歸一化、構(gòu)建數(shù)據(jù)矩陣、計算協(xié)方差矩陣與特征向量、投影降維到分類匹配的流程并借助自帶圖像檢驗識別準確率同時還能通過調(diào)整主成分個數(shù)觀察降維對結(jié)果的影響將代碼遷移到其他圖像分類任務(wù)為后續(xù)學習深度學習人臉識別打下基礎(chǔ)。1. PCA 人臉識別為什么 20 年前的算法還在門禁機里跑早上在寫字樓刷臉進閘機屏幕閃一下“識別成功”很少有人會想背后跑的是什么。拆過幾款中低端人臉識別門禁機就會發(fā)現(xiàn)主控板上不少根本沒有 NPU跑的還是 PCA 特征臉。PCA主成分分析人臉識別是二十多年前的算法但它至今還活在大量小樣本、低功耗、離線可更新的設(shè)備里。標題里的 pca_usable.rar 這類資料包代表的正是這套“能用就行”的 PCA 人臉識別流程。下面不打算考古而是講清三個問題PCA 人臉識別到底在做什么、怎么在本地跑出一套可用的識別、以及光照、對齊、閾值這些會讓演示翻車的坑在哪。適合想做快速原型、理解門禁機識別邏輯的工程師也適合準備算法面試的新手。2. 從特征臉到特征向量PCA 人臉識別的原理與選型2.1 把人臉圖片當成向量PCA 降維到底在做什么一張 64×64 的灰度圖按行展開就是一個 4096 維的向量。如果直接拿兩個這樣的大向量算歐氏距離效果很反直覺同一個人戴個眼鏡像素差全加在一起距離可能比不同人之間的距離還大。這說明原始像素空間里的距離不能代表人臉是否相似。PCA 的出發(fā)點就是找一個新的坐標系在這個坐標系里相似人臉的坐標點更貼近不同人臉的坐標點拉得更開。具體到數(shù)學實現(xiàn)假設(shè)訓練集有 N 張灰度人臉圖每張圖是一維向量 x_i長度 D。先計算均值臉 m (1/N)∑x_i這是“所有臉的公共部分”。把每張臉減去均值臉得到差值向量 d_i x_i - m。把這些差值向量按列排成矩陣 X然后計算協(xié)方差矩陣 C X X^T。理論上C 的特征向量就是“主方向”特征值越大說明這個方向上的樣本差異越大。特征值最大的前 K 個特征向量就是 PCA 要保留的投影軸。把這 K 個投影軸重構(gòu)成圖像會看到一團模糊的人臉輪廓這就是特征臉Eigenface名字的由來。實際工程里很少有人真的去算 C X X^T因為 D 動輒幾千X X^T 是 D × D 的矩陣內(nèi)存和時間都壓不住。更穩(wěn)的做法是對差值矩陣 X 直接做奇異值分解SVDX 分解成 U S V^T。V 的行向量就是主成分S 的對角線是奇異值奇異值的平方和特征值成正比。用 SVD 的好處是數(shù)值穩(wěn)定性更好而且能直接拿到前 K 個方向不用把整個 D × D 矩陣構(gòu)造出來。scikit-learn 的 PCA 在 n_components 小于樣本數(shù)時就是這么干的。PCA 完成之后每張訓練臉都可以投影到 K 維空間變成一個極短的向量。如果只保留 30 個主成分一張 4096 維的圖被壓縮成 30 維信息保留率通常還在 90% 以上。這個低維小區(qū)間已經(jīng)足夠表達人臉之間的差異。最后接一個最近鄰分類器把待識別臉投影到同一組主成分上計算它和所有訓練投影點的歐氏距離或余弦距離距離最小且低于閾值的人就是識別結(jié)果。這里有一個初學者容易混淆的地方PCA 本身不是分類器它不關(guān)心“哪張臉屬于誰”它只負責把原始像素壓縮到低維。誰來做“是誰”的判斷是 PCA 之后接的分類器的事情。最常見的是最近鄰也有人用 SVM 或貝葉斯分類器。資料包 pca_usable.rar 里如果既有降維又有分類比如用 LDA 進一步處理那其實已經(jīng)是 Fisherfaces 范疇了別和純 PCA 混為一談。2.2 為什么 PCA 在人臉識別門禁機上還不過時與 ArcFace、EasyAI 的取舍先說結(jié)論PCA 不是最準的但它在“約束環(huán)境 小樣本 低算力”這個組合下依然是最實用的入門方案之一。室內(nèi)門禁機、考勤機、柜門鎖這些設(shè)備的位置固定人臉基本正面朝向攝像頭光照環(huán)境可控注冊時每個人能留下多張照片。這種環(huán)境正好避開了 PCA 最怕的姿態(tài)和光照變化卻又對成本和功耗非常敏感。對比成熟的人臉識別方案ArcFace 這類基于深度學習的模型在公開測試集上準確率能到 99% 以上但需要較大的模型推理開銷。ArcFace 本身是一個損失函數(shù)通常配著 ResNet 之類的骨干網(wǎng)絡(luò)一張 112×112 的輸入要經(jīng)過上千萬次浮點運算。商用 SDK 如 EasyAI 人臉識別把活體檢測、抓拍、比對打包成黑匣子指標漂亮但授權(quán)費、密鑰管理和云端依賴讓很多小項目望而卻步。用 PCA 特征臉就簡單得多訓練階段做一次 SVD識別階段只有一次矩陣乘法和一次距離排序任何單片機都能扛得住。我把選型對比整理成一張表方便套到自己的項目里維度PCA 特征臉深度學習ArcFace 等商用 SDKEasyAI 等每類最少樣本35 張幾十張起步按 SDK 要求一般也需要多張硬件要求任意 CPUGPU/NPU 或高主頻 CPU視 SDK 而定可解釋性特征臉可可視化黑匣子完全黑匣子光照/姿態(tài)魯棒差好好活體檢測無需要額外方案無需要額外方案通常內(nèi)置離線部署容易中等部分依賴聯(lián)網(wǎng)成本零模型訓練算力成本授權(quán)費適合場景門禁、考勤、寢室閘機高并發(fā)安防快速商用對門禁機這種“注冊一個人之后只驗證這個人”的場景PCA 的閾值控制得當誤識率可以壓得很低。如果現(xiàn)場光照實在不穩(wěn)定還可以在人臉框內(nèi)區(qū)域做曝光補償或者在注冊階段用不同光位多拍幾張讓 PCA 的子空間更魯棒。反過來如果場景是火車站抓拍、運動目標識別、或者需要判斷是不是真人而不是照片那 PCA 不是不能用而是坑太密我一般直接建議用 ArcFace 加活體模塊。易用性和準確率之間的取舍比算法本身更能決定項目成敗。2.3 常用工具鏈OpenCVNumPy、Weka、ENVI 各自適合什么場景傳統(tǒng) PCA 人臉識別的工程鏈路分成五段人臉檢測、人臉對齊、灰度化歸一化、PCA 降維、分類比對。前兩段基本繞不開 OpenCV后兩段可以用 NumPy 或 scikit-learn。如果只是想快速驗證“這個數(shù)據(jù)集能不能用 PCA 分開”Weka 是很好用的圖形化工具。把每張人臉展開成一行數(shù)值加上 person_id 屬性存成 CSV導入 Weka 后選 Filter-Unsupervised-PrincipalComponents設(shè)置 varianceCovered0.95降維后再接一個 IBkKNN分類器跑一遍 10 折交叉驗證十幾秒就能看到大致準確率。Weka 的 PCA 參數(shù)里值得改的還有 centerData默認不開人臉特征沒有明顯尺度問題開不開影響不大但如果數(shù)據(jù)里混了無關(guān)特征建議打開。ENVI 里的主成分分析 PCA 是遙感工具鏈的一部分功能本身沒問題但 ENVI 的定位是柵格影像處理不是人臉識別。用 ENVI 對人臉圖片做 PCA雖然也能輸出主成分分量但它缺少人臉檢測、關(guān)鍵點對齊這些前置處理而且沒法直接接最近鄰分類器來做身份判斷。我見過有人把 ENVI 的 PCA 輸出拿去做 Weka 分類實驗效果自然不差但那只是“用數(shù)據(jù)壓縮工具處理人臉圖”和端到端識別設(shè)備是兩回事。如果你搜索“envi主成分分析pca”后看到一堆波段圖像別指望它幫你解決門禁識別問題。真正上手做我的套路是人臉檢測用 OpenCV 的 DNN 檢測器對齊用雙眼坐標的仿射變換PCA 訓練用 scikit-learn 的 PCA 或手寫 SVD分類用帶閾值的最近鄰。每完成一步把中間結(jié)果存成圖或 CSV 看一眼避免黑匣子式地一路跑到底。拿到 pca_usable.rar 這類資料包時我也習慣先看它的目錄和說明文件找到訓練腳本里有沒有圖像預處理這一段。沒有預處理后面的準確率數(shù)字再高也不可全信有預處理但沒寫閾值標定方法那也得自己補上。這些習慣能讓你少走至少一周彎路。3. 把 PCA 人臉識別跑起來最小實現(xiàn)與參數(shù)調(diào)整3.1 準備數(shù)據(jù)對齊、灰度化、裁剪的約定PCA 對輸入格式非常敏感。我在項目里的固定做法是先做一次人臉檢測拿到人臉框再用兩只眼睛的坐標做仿射變換把雙眼旋轉(zhuǎn)到水平然后統(tǒng)一縮放到 64×64最后轉(zhuǎn)灰度圖并做直方圖均衡化。為什么是 64×64因為 PCA 的維度等于像素數(shù)分辨率翻倍向量長度變四倍訓練和識別時間都明顯增加而小樣本情況下高分辨率并不能帶來等量的精度提升。我用 ORL 數(shù)據(jù)集做過對比48×48 和 64×64 準確率幾乎一樣再往上就有點浪費。數(shù)據(jù)集的目錄結(jié)構(gòu)建議按人分目錄每類目錄里放這個人不同時刻的照片。如果下載的資料包里自帶樣本圖先統(tǒng)一改造成這種結(jié)構(gòu)后續(xù)代碼不用動。加載時按目錄名把人臉和標簽對應起來import os import cv2 import numpy as np def load_faces(data_dir, target_size(64, 64)): X, y [], [] for label_dir in sorted(os.listdir(data_dir)): # 假設(shè)目錄名形如 0_zhangsan取前綴數(shù)字作為標簽 label int(label_dir.split(_)[0]) person_path os.path.join(data_dir, label_dir) for fname in os.listdir(person_path): path os.path.join(person_path, fname) if not fname.lower().endswith((.jpg, .jpeg, .png)): continue img cv2.imread(path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray cv2.resize(gray, target_size) # CLAHE 放在 resize 之后、展開之前 gray cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)).apply(gray) X.append(gray.flatten() / 255.0) y.append(label) return np.array(X), np.array(y)這里把灰度值歸一化到 01讓 SVD 的數(shù)值范圍穩(wěn)定。有人會忘記這一步直接把 0255 的整數(shù)矩陣丟進 PCA導致奇異值偏大閾值標定時的距離單位也失去參考。加載函數(shù)里先 resize 再 CLAHE是因為 CLAHE 基于局部直方圖不同尺寸下統(tǒng)計區(qū)域不一樣統(tǒng)一后再做才有可比性。接下來是對齊函數(shù)。前面加載函數(shù)里省略了人臉檢測實際工程中不能直接 resize 原始抓拍照因為攝像頭角度難免有輕微上下偏。下面這段代碼輸入是已經(jīng)檢測到的左右眼坐標輸出是一張對齊且裁剪好的灰度圖def align_face(img, left_eye, right_eye, out_size(64, 64)): # 計算雙眼連線與水平線的夾角 dx right_eye[0] - left_eye[0] dy right_eye[1] - left_eye[1] angle np.degrees(np.arctan2(dy, dx)) # 繞圖像中心旋轉(zhuǎn)使雙眼連線水平 h, w img.shape[:2] center (w // 2, h // 2) M cv2.getRotationMatrix2D(center, angle, 1.0) rotated cv2.warpAffine(img, M, (w, h)) # 以雙眼中點為中心按眼距的固定比例裁剪 ex abs(dx) scale 1.8 # 裁得不要太貼近五官留出額頭和下巴 crop_size int(ex * scale) eye_mid ((left_eye[0] right_eye[0]) // 2, (left_eye[1] right_eye[1]) // 2) x0 max(eye_mid[0] - crop_size // 2, 0) y0 max(eye_mid[1] - int(crop_size * 0.4), 0) x1 min(x0 crop_size, w) y1 min(y0 crop_size, h) cropped rotated[y0:y1, x0:x1] # 灰度化并縮放到統(tǒng)一尺寸 gray cv2.cvtColor(cropped, cv2.COLOR_BGR2GRAY) gray cv2.resize(gray, out_size) # 直方圖均衡化減輕光照影響 gray cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)).apply(gray) return gray / 255.0代碼里的幾個參數(shù)值得說清楚。angle 的正負號由 dy 決定如果右眼比左眼高dy 為正angle 為正圖像逆時針旋轉(zhuǎn)。scale 取 1.8 而不是 2.0是為了避免把背景裁進人臉區(qū)域因為 PCA 會把背景像素也當作特征學習。y0 的偏移用 crop_size * 0.4把裁剪框向上挪一點讓人臉在全圖里居中偏上減少頭發(fā)和脖子的影響。最后一步 CLAHE 非常關(guān)鍵它能在不放大噪聲的前提下抑制局部光照不均。你如果把這一步去掉同一個人的不同亮度照片距離會大幅波動閾值也會隨之失效。3.2 訓練均值臉、PCA 投影與最近鄰分類接下來是 PCA 訓練的核心函數(shù)。這里為了演示直接用 NumPy 手寫 SVD 版這樣你能看清每一步在做什么也方便移植到不支持 scikit-learn 的環(huán)境。import numpy as np def train_pca(face_vectors, labels, n_components30): # face_vectors: (N, D) 每一行是一張展開后的灰度圖值域 [0,1] # labels: (N,) 每張圖對應的用戶ID mean_face np.mean(face_vectors, axis0) centered face_vectors - mean_face # 對差值矩陣做瘦 SVD直接得到前 min(N,D) 個主方向 U, S, Vt np.linalg.svd(centered, full_matricesFalse) # 前 n_components 個右奇異向量 Vt 的每一行就是一個特征臉基向量 components Vt[:n_components] # shape (n_components, D) # 訓練集投影到低維子空間 projected_train np.dot(centered, components.T) # (N, n_components) return { mean_face: mean_face, components: components, projected_train: projected_train, labels: labels }這里要注意 SVD 返回的 Vt 每一行是一個主方向不是每一列。所以我用 Vt[:n_components] 取前 K 個再用 centered 乘以 Vt 的轉(zhuǎn)置做投影。scikit-learn 的 PCA 對象里有 n_components_就是這家伙。另一個容易踩的地方是SVD 前不需要再做標準化因為所有像素都在同一個量綱亮度下如果先做了 per-pixel 歸一化反而會破壞灰度圖的相對明暗關(guān)系。識別階段的函數(shù)同樣短def recognize(face_vector, model, threshold0.50): mean_face model[mean_face] components model[components] proj_train model[projected_train] labels model[labels] centered face_vector - mean_face proj np.dot(centered, components.T) # 歐氏距離L2 范數(shù)。也可以用余弦距離但這里用歐氏更直觀 dists np.linalg.norm(proj_train - proj, axis1) best_idx int(np.argmin(dists)) if dists[best_idx] threshold: return labels[best_idx], dists[best_idx] else: return -1, dists[best_idx]threshold 的單位不是維度數(shù)而是投影空間里的距離。因為投影向量每個分量的尺度會隨 SVD 奇異值變化不同訓練集算出來的距離絕對值差異很大。所以絕對閾值沒法遷移必須在自己的數(shù)據(jù)集上標定。我一般先打印所有同類距離和異類距離的最小/平均/最大值再看兩個分布是否有交疊。理論上同類最近鄰距離和異類最近鄰距離是兩條分布曲線閾值取交點效果最好實際項目中我會把閾值設(shè)置在交點偏小一側(cè)寧可多拒絕也要少誤識。后面避坑章節(jié)會展開。順便說一個容易翻車的細節(jié)這一步用的是歐氏距離。把投影后的坐標稍微想一下如果某個特征值特別大那么它對應的軸向距離就主導了歐氏距離。這時候可以改用馬氏距離或者先對投影向量做單位化但單位化會丟掉尺度信息。對于門禁驗證我習慣保留歐氏距離因為閾值標定更直觀。3.3 評估留一法與累計方差貢獻率模型寫好了不能直接看一個總準確率就收工。小樣本的人臉數(shù)據(jù)集必須用留一法評估因為隨機劃分訓練測試集會把同一個人的不同照片分到兩邊造成數(shù)據(jù)泄漏準確率虛高。我常用這段代碼跑一個最誠實的評估def evaluate_loo(face_vectors, labels, n_components30, threshold10**9): from sklearn.model_selection import LeaveOneOut loo LeaveOneOut() correct 0 total 0 for train_idx, test_idx in loo.split(face_vectors): train_vecs face_vectors[train_idx] train_labels labels[train_idx] test_vec face_vectors[test_idx][0] true_label labels[test_idx[0]] model train_pca(train_vecs, train_labels, n_components) pred, _ recognize(test_vec, model, threshold) if pred true_label: correct 1 total 1 return correct / total這段代碼每次留一都重新訓練一次 PCA數(shù)據(jù)量大的時候會很慢但它的結(jié)果才代表真實泛化能力。threshold 先設(shè)成一個很大的數(shù)讓最近鄰永遠“認得出”這樣 eval 出來的是純最近鄰的準確率。等閾值標定結(jié)束再拿真正要用的 threshold 重跑一次記錄此時被拒識的比例兩個數(shù)字放一起看你就能知道自己的模型能容忍多少誤識。還有一個更常用的調(diào)參手段是畫累計方差貢獻率曲線。scikit-learn 直接支持from sklearn.decomposition import PCA pca PCA(n_components50) pca.fit(face_vectors) cumsum np.cumsum(pca.explained_variance_ratio_) # 找到達到 95% 方差的最少維度 k np.searchsorted(cumsum, 0.95) 1 print(f保留95%方差需要 {k} 個主成分)這個 k 就是你在 train_pca 里應該用的 n_components。不是越大越好主成分越靠后越接近光照和噪聲把這些維度也放進比對會把不同人的特征臉“拉近”反而降低準確率。我用 ORL 數(shù)據(jù)集的經(jīng)驗是 k 在 20~40 之間累計方差貢獻率達到 90%~95%準確率基本穩(wěn)定。如果你換到 64×64 以上的分辨率k 可以稍微往上加但不要超過樣本數(shù)的一半否則 SVD 后段全是噪點。訓練集只有 5 類、每類 5 張的時候總樣本 25 張k 設(shè) 30 也拿不到 30 個非零主成分。所以 k 的上限是樣本數(shù)減一不是隨便填。實際項目里數(shù)據(jù)準備和訓練常常要反復調(diào)幾輪。第一次跑通后我會把距離矩陣用 seaborn 畫成熱力圖檢查有沒有明顯的交叉點。如果兩個不同人的距離比同一個人的還小那基本可以斷定預處理環(huán)節(jié)還有問題先去查對齊再查光照最后才懷疑 PCA 參數(shù)。這個排查順序在下面的避坑章節(jié)里會再展開。4. PCA 人臉識別的 5 個避坑點光照、對齊、維度與閾值4.1 光照不均讓模型“睜眼瞎”先做 CLAHE 再進 PCA現(xiàn)象訓練時在室內(nèi)燈下拍的人臉識別時換到窗邊陽光下系統(tǒng)把同一個人誤判成陌生人甚至另一個員工。原因PCA 的主成分是按像素方差找出來的全局亮度變化產(chǎn)生的方差比人臉五官差異還大所以主成分里很大一部分編碼的是光照方向而不是身份信息。解決在灰度化之后立即做 CLAHE限制對比度讓局部細節(jié)更平均如果光照情況跨度更大注冊時多采集幾個時段的人臉讓均值和主成分把不同照度吸收掉。這一步比換分類器更能提升準確率。我看到不少 pca_usable.rar 里的訓練腳本省略了 CLAHE實際識別時就開始翻車。判斷方法很簡單打印前 5 個特征臉的可視化如果第一張看起來像一張單純的亮度漸變圖而不是人臉輪廓說明光照方差占了主導趕緊把預處理補上。補了以后特征臉會出現(xiàn)更多五官輪廓識別率一般會立刻往上走。4.2 人臉沒對齊PCA 學到的是姿態(tài)而不是臉現(xiàn)象一個同事抬頭、低頭、左右轉(zhuǎn)頭各拍了幾張訓練完以后識別率不到 70%查投影空間發(fā)現(xiàn)同一個人聚成好幾坨。原因PCA 不區(qū)分姿態(tài)變化和身份變化它只知道哪些方向變化大。人臉沒有對齊時旋轉(zhuǎn)、平移造成的像素移動會占據(jù)最大的方差方向。解決入模之前必須做仿射變換把眼睛位置固定到模板坐標。沒有關(guān)鍵點時可以只用 OpenCV DNN 人臉檢測器返回的人臉框和五個關(guān)鍵點取兩只眼睛做旋轉(zhuǎn)對齊也能救回大部分精度。還有一個檢查方法把學習到的特征臉可視化如果特征臉上有明顯的邊緣輪廓像某種姿勢的剪影說明數(shù)據(jù)里姿態(tài)偏差太大需要回去重新對齊。實踐里最省事的做法是直接采用 3.1 的 align_face 函數(shù)只要拍到左右眼就能把角度誤差控制在一個像素以內(nèi)。如果你連關(guān)鍵點都拿不到至少要保證人臉框的高度和寬度比例一致再用框中心做縮放裁剪效果會差一些但不至于完全不能用。4.3 維度 k 不是越大越好用累計方差貢獻率來確定現(xiàn)象有人把 n_components 設(shè)成 200認為保留信息越多越好結(jié)果交叉驗證準確率反而比 40 維時低 10 個百分點。原因主成分是按方差排序的后面幾十個主成分主要由噪聲、光照殘差組成它們把不同類別的人臉距離拉小了干擾最近鄰判斷。解決畫累計方差貢獻率曲線取 90% 到 95% 交叉點對應的 k。另一種穩(wěn)健做法是畫 k-準確率曲線選準確率平臺期的起點。平臺期的起點通常是準確率曲線從快速上升到基本持平的轉(zhuǎn)折點那個 k 一般落在 20~50 之間。注意如果每類樣本只有 3 張k 再大也不會超過樣本數(shù)超過樣本數(shù)以后 SVD 會得到零奇異值純屬白算。試參數(shù)的時候還要同時看驗證集和訓練集的準確率。訓練集 100%、驗證集掉到 80%大概率是 k 太大導致過擬合反過來兩個都很低說明預處理和特征選取的方向不對。先用 30 維跑通流程再花時間精調(diào)比一上來就網(wǎng)格搜索靠譜。4.4 閾值不是玄學要用距離分布來標定現(xiàn)象演示時用同一個人測試閾值設(shè)得很松結(jié)果陌生人也能通過閾值調(diào)緊一點員工又頻繁要刷好幾次才能開門。原因閾值本質(zhì)是投影空間中的一個半徑它決定了“最近鄰居要近到什么程度才算認識”。只靠試幾個數(shù)字來調(diào)很難兼顧誤識率和拒識率。解決在驗證集上統(tǒng)計兩類距離——同類最近鄰距離真正的用戶到庫里的最近點和異類最近鄰距離陌生人到庫里的最近點。畫出直方圖后理想閾值取兩條曲線交點如果業(yè)務(wù)上更怕陌生人闖入把閾值往左偏如果更怕員工被拒就往右偏。這個過程的代碼并不復雜把 recognize 函數(shù)里的 dists 拉出來分別收集同一個人的最小距離和不同人的最小距離最后用 matplotlib 畫直方圖。上線后還要定時收集運行日志更新閾值因為隨著注冊人數(shù)增加異類距離整體會變近固定閾值會慢慢失效。我見過最典型的翻車是門禁系統(tǒng)運行半年后誤開率上升一查發(fā)現(xiàn)閾值還是初始調(diào)試時定的數(shù)據(jù)庫里的人比那時多了兩倍。4.5 樣本不均衡導致“老用戶好刷新用戶刷不進”現(xiàn)象老員工在庫里存了 30 張照片新員工只有 3 張識別結(jié)果總是偏向老員工新員工經(jīng)常被拒。原因PCA 是無監(jiān)督的樣本多的類別對均值和主成分的影響更大同時最近鄰比較環(huán)境里老員工周圍有更多點更容易被選中。解決每類樣本量做到一致最少 5 張。數(shù)據(jù)不夠就做離線增強水平翻轉(zhuǎn)、小角度旋轉(zhuǎn) ±5 度、亮度擾動、小幅平移。注意增強只能在訓練側(cè)做如果測試也用了同一張原圖的增強版本會造成數(shù)據(jù)泄漏準確率虛高。還有識別的時候建議把“最小距離”和“次小距離的比值”也作為判斷依據(jù)如果最小距離和次小距離非常接近說明這個人處在兩個類別的邊界上應該觸發(fā)重新采集而不是直接下結(jié)論。人臉識別門禁機產(chǎn)品如果要提高新用戶的體驗這一步比調(diào)算法參數(shù)更有效。5. 從 PCA 模型到人臉識別門禁機驗證流程與進階技巧一段真正的門禁識別不是跑通腳本就算完它需要三個驗證動作按人劃分數(shù)據(jù)集、用留一法或 K 折得到真實準確率、用閾值標定得到誤識/拒識率。這三個動作在前面章節(jié)里已經(jīng)寫了實現(xiàn)真正上線前還要把它們固化成回歸測試——每次改預處理或加樣本都要重跑一遍防止舊功能回歸。還要注意驗證時一定要按人劃分不能把同一個人的多張照片同時放進訓練集和測試集否則算出來的準確率參考價值很低。誤識率和拒識率要一起看。只報準確率 98% 沒什么意義因為門禁場景里準確率不等于安全性。我會在驗證集上跑一遍畫出誤識率隨閾值變化的曲線再結(jié)合門禁點每天通過的人數(shù)估算出閾值調(diào)緊后每天預計有幾次誤拒。這個數(shù)字要和業(yè)務(wù)方確認而不是自己拍腦袋。比如 300 人的辦公室每天 600 次刷臉即使拒識率只有 0.5%一天也有 3 次被擋在門外員工體驗會很差。進階的第一招是 PCA LDA也就是 Fisherfaces。PCA 先去掉冗余再做線性判別讓不同人的投影點更分離。代碼層面很簡單先對數(shù)據(jù)做 PCA 降到 50 維再對降維結(jié)果做 LDA 降到 C-1 維C 是人數(shù)。Fisherfaces 在 ORL 數(shù)據(jù)集上的準確率通常比純 PCA 高 2~5 個百分點而且它更抗光照變化。第二招是給每個用戶單獨維護一個 PCA 子空間。當用戶要重新錄入照片時不用全量重訓只把這個人的照片集更新后重新算一次均值臉和投影。這樣做雖然犧牲一點全局一致性但在門禁機這種內(nèi)存有限的設(shè)備上可以顯著降低更新耗時。第三招是圖像預處理參數(shù)要跟著識別性能一起鎖版本。你優(yōu)化了 CLAHE 的 clipLimit 或者裁剪框的 scale準確率可能從 95% 升到 98%但這個提升只有在你整套鏈路里才有意義。如果資料包或隊友直接用另一套預處理推到設(shè)備上效果會立刻退化。我自己早期在一個門禁項目里吃過虧跑演示時準確率 98%上線后每天都有人被拒后來發(fā)現(xiàn)是設(shè)備攝像頭角度偏低人臉對齊后眼睛位置和訓練集差十來個像素PCA 投影距離整體變大了。解決方式是補拍一批低角度照片進訓練集并把閾值按新分布重新標定。那次之后我養(yǎng)成了“先標定距離分布、再談準確率”的習慣。希望這些步驟和踩坑記錄能幫到你少走幾步彎路。本文還有配套的精品資源點擊獲取