原理與CNN實現(xiàn):從特征提取到工程落地)
開始之前人臉識別到底在識別什么人臉識別這幾年被炒得很熱從手機解鎖到安防閘機再到刷臉支付好像什么場景都能插一腳。但你真讓我用一句話說清楚它做了什么我會這么講它做的事情本質(zhì)上就是“看圖說話”——把一張人臉照片變成一個數(shù)學(xué)向量然后拿這個向量和數(shù)據(jù)庫里的向量做比對距離夠近就算同一個人。這里面的核心技術(shù)就是卷積神經(jīng)網(wǎng)絡(luò)圈內(nèi)習(xí)慣叫CNN。CNN不是什么新鮮詞2012年之后它幾乎統(tǒng)治了所有圖像類任務(wù)人臉識別只是它的一個應(yīng)用場景而已。這篇我就把人臉識別的完整鏈路講清楚從傳統(tǒng)方法到CNN的核心原理再落到怎么用CNN把一個人臉識別系統(tǒng)搭起來。內(nèi)容盡量做到既要原理可追溯也要代碼能落地適合剛接觸圖像識別的學(xué)生、轉(zhuǎn)行做算法工程師的朋友以及工作中需要快速上手人臉識別方案的開發(fā)者。說句實在話很多人最開始接觸人臉識別腦子里全部是“深度學(xué)習(xí)”“神經(jīng)網(wǎng)絡(luò)”這些詞但其實人臉識別并不是深度學(xué)習(xí)火了之后才有的。早些年基于幾何特征和局部二值模式的方法也能做只是精度和魯棒性差不少。要真想搞明白現(xiàn)在的人臉識別為什么做得這么好得先把CNN內(nèi)部那幾層結(jié)構(gòu)的來龍去脈搞清楚。1. 人臉識別的整體技術(shù)鏈路與方案選擇1.1 人臉識別的四個核心環(huán)節(jié)以我做過的一個模擬項目X為例當時要在某園區(qū)出入口做一套刷臉通行系統(tǒng)。整體流程跑下來我把它歸納為四個環(huán)節(jié)人臉檢測、人臉對齊、特征提取、特征比對。人臉檢測負責從畫面里找到人臉在哪輸出一個矩形框。這一步看似基礎(chǔ)但實際影響非常大因為后面所有步驟都依賴這個框是否準確。如果框偏了或者把背景框進來了對齊和特征提取都會受影響。主流方案還是以MTCNN和RetinaFace為主它們在小目標、遮擋、夸張姿態(tài)上各有取舍。人臉對齊是把檢測到的人臉做幾何校正讓眼睛、鼻子、嘴巴的位置盡量標準化。因為同一個人的臉在不同照片里角度和尺度都不一樣如果不先把臉“擺正”提取特征的時候就會混淆“臉長什么樣”和“臉朝哪個方向”。這一步通常通過檢測關(guān)鍵點然后做相似變換來搞定。特征提取是整個流程的核心。這一步把一張人臉圖像壓縮成一個固定長度的向量比如128維或512維。這個向量在理想情況下同一個人的不同照片應(yīng)該距離很近不同人的照片應(yīng)該距離很遠。人臉識別算法比拼的大部分能力都集中在這一步。特征比對就是拿提取出來的向量去和庫里存的特征去算距離常用的是余弦相似度和歐氏距離。設(shè)定一個閾值超過就判定為同一個人低于就拒絕。這個閾值的選擇很有講究定得太嚴容易誤拒定得太松容易誤識。1.2 傳統(tǒng)方法為什么被人臉識別淘汰很多人以為人臉識別天生就是深度學(xué)習(xí)的樣子其實不是。早年的傳統(tǒng)做法比如基于幾何特征的方法是標定眼睛、鼻子、嘴巴等關(guān)鍵位置然后測量它們之間的距離和角度關(guān)系構(gòu)造一個特征向量。這個思路聽著很直觀但實操時極其脆弱——光照一變、表情一變那些距離值就跟著亂跳。后來流行過一段時間局部二值模式直方圖的方法用局部紋理描述人臉在當年算是挺優(yōu)雅的方案。但它的天花板很低因為底層用的還是人工設(shè)計的特征。人工設(shè)計特征的問題在于做特征的人要事先猜測“什么信息對區(qū)分人臉最重要”這個猜測很難做到全面。CNN的出現(xiàn)改變的是這個底層邏輯不再人為指定特征而是讓網(wǎng)絡(luò)在訓(xùn)練過程中自己學(xué)習(xí)該關(guān)注什么。我舉個生活化的類比傳統(tǒng)方法就像你要給朋友描述一個人的長相只能按你想到的維度去描述比如“眼睛大不大、鼻梁高不高”而CNN是直接把一大堆照片丟給網(wǎng)絡(luò)讓它自己歸納出哪些維度最能區(qū)分人臉可能是膚色可能是輪廓也可能是某種人眼說不清道不明的組合反正它自己說了算。1.3 從圖像分類到人臉識別的本質(zhì)區(qū)別CNN最開始大規(guī)模應(yīng)用是在ImageNet圖像分類上比如區(qū)分一張圖是貓還是狗。但人臉識別不是一個“分類”問題它是一個“度量學(xué)習(xí)”問題。分類問題的輸出是類別標簽比如1000類物體網(wǎng)絡(luò)最后接一個1000維的Softmax輸出。人臉識別不一樣它要應(yīng)對的是“訓(xùn)練時沒見過的人”。你不能把每個人當成一個類別去訓(xùn)練因為世界上有幾十億人而且識別階段出現(xiàn)的人臉可能完全沒參與過訓(xùn)練。所以人臉識別的主流做法是把網(wǎng)絡(luò)當做一個特征提取器訓(xùn)練時用分類的損失函數(shù)去約束特征分布但真正做識別時丟掉最后的分類層只取前面部分的特征向量來做比對。舉個具體例子我用某個開源人臉模型時輸入是112x112的RGB圖像經(jīng)過一系列卷積和池化后得到一個512維的特征向量。訓(xùn)練的時候這個512維向量后面還會接一個全連接層去做分類比如訓(xùn)練集有1萬個人那就接一個1萬維的分類層。但真正部署識別的時候這一層會被去掉只用前面的512維向量。2. CNN核心概念拆解卷積、池化、全連接2.1 卷積層到底在算什么CNN里最核心的構(gòu)件是卷積層。很多人第一次接觸卷積覺得頭大其實可以換個角度看卷積層做的事情就是用一個小的“掃描窗口”在圖像上滑動每滑到一個位置就把窗口里的像素值和窗口本身的權(quán)重做加權(quán)求和得到一個輸出值。這個“掃描窗口”叫卷積核也叫濾波器。它的大小通常是3x3或5x5深度和輸入圖像的通道數(shù)一致。舉個例子輸入是一張112x112x3的圖卷積核是3x3x3它就在圖上一格一格地滑動每算一次得到一個數(shù)值。把所有滑過的位置算一遍得到一張新的特征圖。為什么要這么做因為卷積核本質(zhì)上是一個局部模式探測器。比如某個卷積核的權(quán)重恰好能檢測水平邊緣那它在圖像中所有存在水平邊緣的位置都會產(chǎn)生較大的響應(yīng)值。網(wǎng)絡(luò)的淺層卷積核會學(xué)習(xí)到邊緣、顏色塊這類基礎(chǔ)特征深層卷積核則會組合出眼睛、鼻子這類復(fù)雜結(jié)構(gòu)。有一個關(guān)鍵參數(shù)叫“感受野”指的是輸出特征圖上的一個像素對應(yīng)回輸入圖像上多大的一塊區(qū)域。這是CNN能夠理解全局結(jié)構(gòu)的基礎(chǔ)。以人臉識別為例淺層卷積核看到的是局部紋理而深層卷積核通過層層疊加能感知到兩只眼睛之間的距離、臉型輪廓這種全局關(guān)系。2.2 池化層的作用與為什么不能省池化層通常跟在卷積層后面最常見的做法是最大池化。它做的事情很簡單在2x2的窗口里取最大值然后把圖像尺寸減半。很多人覺得池化不就是降采樣嗎為什么非要取最大值平均值不行嗎我的理解是這樣的最大池化保留了窗口內(nèi)最強烈的響應(yīng)相當于把“這個位置有沒有出現(xiàn)某個特征”這個信息保留下來而平均池化會把強響應(yīng)和弱響應(yīng)互相抵消反而模糊了特征。所以在圖像識別里最大池化更好用。池化的意義不只是減少計算量更關(guān)鍵的是它帶來了某種程度的平移不變性。什么意思呢如果人臉在圖像里偏移了幾個像素經(jīng)過池化后特征圖上的響應(yīng)位置變化會變小。這正好符合人臉識別的需求——臉并不總是在畫面正中央。當然現(xiàn)在很多新網(wǎng)絡(luò)架構(gòu)開始用帶步長的卷積替代池化功效類似但池化層的概念還是要理解很多經(jīng)典網(wǎng)絡(luò)的代碼里都在用。2.3 全連接層和特征向量之間的關(guān)系卷積層和池化層反復(fù)堆疊之后得到的是三維特征圖高度和寬度越來越小通道數(shù)越來越多。最后要把這些特征變成一個向量就需要把三維特征圖“拉平”接上全連接層。全連接層就是傳統(tǒng)的神經(jīng)網(wǎng)絡(luò)層每個輸入節(jié)點和每個輸出節(jié)點之間都有權(quán)重連接。它的參數(shù)量極其龐大所以現(xiàn)代網(wǎng)絡(luò)設(shè)計傾向于用全局平均池化替代部分全連接層直接把每個通道的所有值取平均得到一個長度等于通道數(shù)的向量。在人臉識別網(wǎng)絡(luò)里最后那個特征向量通常來自最后一個池化層或一個維度較小的全連接層。這個向量就是“人臉特征”的數(shù)字表達。我在實際項目里經(jīng)常把這個向量直接存數(shù)據(jù)庫比對的時候就做向量運算。這里有一個很重要的點特征向量的維度選擇要平衡精度和速度。128維的向量在嵌入式設(shè)備上跑得非??斓壬舷薏蝗?12維。我測試過一個場景在RK3588這類邊緣設(shè)備上512維向量的余弦相似度計算一萬次大約要十幾毫秒實際影響不是特別大但如果是超大規(guī)模人臉庫尤其是千萬級以上的維度的影響就會被放大。3. 從零實現(xiàn)一個CNN人臉識別模型3.1 選型與準備為什么用PyTorch現(xiàn)在做人臉識別框架基本就兩個選項PyTorch或TensorFlow。個人偏向PyTorch原因有三一是動態(tài)圖機制調(diào)試方便可以隨時打印中間變量二是學(xué)術(shù)界開源的人臉識別模型像ArcFace、CosFace這些基本都是PyTorch版優(yōu)先三是部署時轉(zhuǎn)ONNX再轉(zhuǎn)各種推理引擎的鏈路已經(jīng)非常成熟。硬件方面訓(xùn)練一個像樣的人臉識別模型一張像樣的GPU是少不了的。我沒法給你一個絕對最低配置但以我的經(jīng)驗顯存低于8G的話很多開源的訓(xùn)練代碼跑起來會非常痛苦光是調(diào)batch size就夠折騰的。這幾年很多云服務(wù)商都提供按小時的GPU實例短期實驗完全不用自己買卡。數(shù)據(jù)集方面學(xué)術(shù)上常用的是CASIA-WebFace和MS-Celeb-1M。不過MS-Celeb-1M因為授權(quán)問題已經(jīng)停止公開下載了現(xiàn)在用得更多的是Glint360K這類清洗過的數(shù)據(jù)集。自己從頭訓(xùn)練一個大規(guī)模模型成本不低我建議初學(xué)階段先下載一個預(yù)訓(xùn)練模型做細粒度調(diào)優(yōu)或者干脆在小型數(shù)據(jù)集上把整個訓(xùn)練流程跑通。3.2 數(shù)據(jù)預(yù)處理人臉對齊的代碼級解剖不管是訓(xùn)練還是推理數(shù)據(jù)預(yù)處理流程是一樣的。以我常用的方式為例完整鏈路是讀圖 - 人臉檢測 - 關(guān)鍵點定位 - 相似變換對齊 - 歸一化 - 送入模型。對齊這一步是用相似變換把五個關(guān)鍵點左眼、右眼、鼻尖、左嘴角、右嘴角映射到標準位置。標準位置是固定的比如在112x112的圖像上左眼通常在(38.2946, 51.6963)右眼在(73.5318, 51.5014)。這些數(shù)值不是拍腦袋定的而是從數(shù)據(jù)集統(tǒng)計出來的目的是讓人臉在圖像中占的比例一致。import cv2 import numpy as np def align_face(image, keypoints, size(112, 112)): # keypoints 是 5x2 的數(shù)組順序為左眼、右眼、鼻尖、左嘴角、右嘴角 dst np.array([ [38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366], [41.5493, 92.3655], [70.7299, 92.2041] ], dtypenp.float32) src keypoints.astype(np.float32) # 計算相似變換矩陣 transform cv2.estimateAffinePartial2D(src, dst)[0] aligned cv2.warpAffine(image, transform, size) return aligned這個代碼看起來簡單但有個細節(jié)容易被忽略estimateAffinePartial2D算出來的是部分仿射變換只包含旋轉(zhuǎn)、平移和縮放不包含切變。人臉對齊不應(yīng)該有切變因為臉的角度變化是剛體變換強行加入切變會把臉拉變形。對齊做完之后還要歸一化常見做法是每個像素減127.5再除以128這樣像素值范圍大概落在[-1, 1]區(qū)間。這一步能幫助網(wǎng)絡(luò)訓(xùn)練時梯度更穩(wěn)定。如果你用的是ONNX導(dǎo)出的模型預(yù)處理方式必須和訓(xùn)練時完全一致否則精度掉得很厲害。3.3 模型訓(xùn)練ArcFace損失函數(shù)與度量學(xué)習(xí)模型結(jié)構(gòu)上可以用ResNet50或MobileFaceNet做骨干網(wǎng)絡(luò)。MobileFaceNet是為移動端設(shè)計的輕量模型參數(shù)量小且精度不錯我在多個邊緣設(shè)備項目里都用的它。訓(xùn)練的時候光用Softmax損失是不夠的。Softmax優(yōu)化的是“分類正確”但人臉識別要求“類間距離大類內(nèi)距離小”。ArcFace就是針對這個目標設(shè)計的損失函數(shù)它在Softmax基礎(chǔ)上給角度加上一個懲罰項讓網(wǎng)絡(luò)學(xué)到的特征在超球面上分布得更開。我用一段簡化代碼來展示ArcFace的思路。import torch import torch.nn as nn import torch.nn.functional as F class ArcFace(nn.Module): def __init__(self, in_features, out_features, s64.0, m0.5): super().__init__() self.weight nn.Parameter(torch.FloatTensor(out_features, in_features)) nn.init.xavier_normal_(self.weight) self.s s self.m m def forward(self, inputs, labels): # 歸一化輸入和權(quán)重 inputs F.normalize(inputs, p2, dim1) weight F.normalize(self.weight, p2, dim1) # 計算余弦相似度 cos_theta F.linear(inputs, weight) # 計算角度 theta torch.acos(cos_theta.clamp(-1.0 1e-7, 1.0 - 1e-7)) # 對目標類別加上角度懲罰 target_mask torch.zeros_like(cos_theta) target_mask.scatter_(1, labels.view(-1, 1), 1.0) target_logits torch.cos(theta self.m) output cos_theta * (1 - target_mask) target_logits * target_mask output * self.s return output這段代碼里有兩個超參數(shù)值得關(guān)注s是縮放系數(shù)設(shè)置為64m是角度懲罰設(shè)置為0.5。s越大特征的分布越集中但太大容易導(dǎo)致訓(xùn)練不穩(wěn)定m越大類間推開的力度越大但太大也難收斂。這組參數(shù)是論文作者試出來的經(jīng)驗值新手最好不要亂改。訓(xùn)練時batch size盡量調(diào)大一點。人臉識別訓(xùn)練特別吃batch size因為損失函數(shù)需要在一個batch內(nèi)部構(gòu)造正負樣本對比batch太小的話模型很難學(xué)到有區(qū)分度的特征。我個人的經(jīng)驗是至少64起步能到128或256更好。如果你的顯存不夠可以嘗試用梯度累積來模擬大batch的效果。3.4 特征提取與相似度計算訓(xùn)練完成后真正部署時只需要用網(wǎng)絡(luò)的前向部分做特征提取。以我常用的方式為例加載訓(xùn)練好的模型去掉最后一層分類層輸入一張對齊后的112x112人臉圖輸出512維向量。import torch import torchvision.transforms as transforms def extract_feature(model, face_image): model.eval() transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ]) img_tensor transform(face_image).unsqueeze(0) with torch.no_grad(): feature model(img_tensor) # L2歸一化 feature F.normalize(feature, p2, dim1) return feature.cpu().numpy().flatten()提取出的特征建議做L2歸一化好處是計算余弦相似度時可以直接用點積替代余弦公式省一次除法運算。歸一化之后兩個特征向量之間的余弦相似度就等于它們的點積。相似度計算也很直接import numpy as np def cosine_similarity(feat1, feat2): return np.dot(feat1, feat2) def euclidean_distance(feat1, feat2): return np.linalg.norm(feat1 - feat2)經(jīng)驗上余弦相似度比歐氏距離更適合人臉識別。原因是L2歸一化后余弦相似度等價于關(guān)注兩個向量的方向一致性對向量模長不敏感。而歐氏距離會把特征向量的“長度”信息也納入考量這個信息通常沒什么用反而會引入噪聲。3.5 閾值怎么定從相似度分數(shù)到識別決策特征比對只是得到了一個分數(shù)真正要做出“是不是同一個人”的判斷還得定一個閾值。閾值的選擇必須基于實際的驗證集測試不能拍腦袋。我的做法是準備一批正樣本對同一個人的兩張照片和負樣本對不同人的照片全部算出相似度分數(shù)然后畫出分布曲線。正樣本對的分數(shù)通常集中在0.5到0.9之間負樣本對的分數(shù)通常在-0.1到0.3之間。閾值就選在兩類分布重疊最少的地方。這個閾值在不同數(shù)據(jù)集上差異很大。在某個人臉數(shù)據(jù)集上0.4的閾值可能已經(jīng)很合適但換個數(shù)據(jù)集可能得調(diào)到0.55。所以如果換了一批訓(xùn)練數(shù)據(jù)閾值一定要重新標定這是新手最容易踩的坑。4. 實操中遇到的典型問題與排查思路4.1 預(yù)處理不一致導(dǎo)致的精度暴跌我在做項目時遇到過一個很典型的問題訓(xùn)練時用得是112x112的RGB輸入但到部署階段代碼里讀圖用的是灰度模式通道數(shù)對不上模型直接報警告精度也掉了不少。這類問題最隱蔽因為代碼能跑只是結(jié)果不對。排查手段其實很基礎(chǔ)就是打印輸入模型的張量形狀確認通道數(shù)、尺寸、數(shù)值范圍。我建議在模型推理入口加一個斷言比如assert input_tensor.shape (1, 3, 112, 112), funexpected shape: {input_tensor.shape} assert input_tensor.min() -1.0 and input_tensor.max() 1.0, input not normalized這種斷言能幫你在開發(fā)早期就暴露問題而不是等到上線了才發(fā)現(xiàn)精度不對。4.2 光照和角度變化導(dǎo)致誤判人臉識別在實際環(huán)境中最大的敵人不是算法本身而是光照、角度、遮擋這些“環(huán)境噪聲”。同一個角度下不同角度的同一張臉相似度可能差別很大。針對光照問題一個實用做法是數(shù)據(jù)增強。訓(xùn)練時隨機調(diào)整亮度、對比度、飽和度甚至加一些隨機遮擋塊模擬真實場景中的干擾。我在一個項目中把訓(xùn)練數(shù)據(jù)增加了隨機光照擾動之后模型在暗光環(huán)境下的通過率提升了接近十個百分點。針對角度問題目前比較有效的方法是訓(xùn)練時做隨機水平翻轉(zhuǎn)。還有一個思路是多角度注冊也就是錄入人臉時不只拍一張正面照而是拍幾張不同角度的照片都存入特征庫比對時取最高的相似度作為最終結(jié)果。4.3 模型在嵌入式設(shè)備上的推理速度優(yōu)化如果你要把模型部署到嵌入式設(shè)備上比如門禁機、考勤機就需要關(guān)注推理速度。MobileFaceNet在CPU上單張112x112人臉推理大約能跑到20到50毫秒具體取決于芯片。如果速度不達標有幾步優(yōu)化可以做先把模型轉(zhuǎn)成ONNX再用ONNX Runtime或TensorRT推理通常比PyTorch原版快不少。其次可以做INT8量化以少量精度損失換取接近兩倍的加速。我一般建議先用ONNX Runtime的CPU模式跑一遍如果速度還不行再考慮量化。不要一上來就直接量化工序因為量化調(diào)試還挺麻煩要對齊校準數(shù)據(jù)集搞不好精度掉得厲害。4.4 小樣本注冊問題與增量更新實際項目中還有一個常見場景注冊庫里的照片只有一張但用戶每天上班的角度、表情、光線都在變。這種情況下單張注冊的特征往往不夠魯棒。一個有效的方案是“動態(tài)特征更新”。每次識別通過且相似度超過一個較高閾值比如0.8時就把當前幀提取的特征和庫里的舊特征做加權(quán)平均更新庫里的特征向量。這樣特征庫會隨著使用越來越貼合這個人的實際外觀變化。不過這種做法要加一個保護機制只有在相似度非常高的前提下才更新否則會把誤識別的特征混進庫里造成污染。我在一個實際項目里設(shè)定的規(guī)則是相似度大于0.85才允許更新小于這個值只能讀不能寫。4.5 常見問題速查表現(xiàn)象可能原因排查思路模型訓(xùn)練loss不降學(xué)習(xí)率過大或過小調(diào)低學(xué)習(xí)率到1e-3附近確認歸一化正常訓(xùn)練精度高但測試精度低過擬合增加數(shù)據(jù)增強或加大正則化權(quán)重部署后精度明顯下降預(yù)處理不一致檢查輸入尺寸、均值方差、通道順序推理速度慢模型過大或框架不合適轉(zhuǎn)ONNX做量化或換輕量網(wǎng)絡(luò)同一個人相似度波動大光照角度變化數(shù)據(jù)增強多角度注冊動態(tài)特征更新誤識率高閾值過低重新標定閾值檢查特征歸一化5. 模型部署與服務(wù)化從離線到實時5.1 模型導(dǎo)出與推理引擎選擇訓(xùn)練好模型只是第一步真正讓人臉識別跑起來還得把它部署成服務(wù)或者集成到應(yīng)用里。模型導(dǎo)出的標準格式是ONNX它相當于各種深度學(xué)習(xí)框架之間的“翻譯官”。PyTorch導(dǎo)出ONNX的代碼很簡單import torch model load_model(mobilenet_face.pth) model.eval() dummy_input torch.randn(1, 3, 112, 112) torch.onnx.export( model, dummy_input, face_net.onnx, input_names[input], output_names[feature], dynamic_axes{input: {0: batch}, feature: {0: batch}}, opset_version11 )導(dǎo)出之后可以用ONNX Runtime來推理。它的優(yōu)勢是輕量、跨平臺CPU推理速度比PyTorch快不少。如果是NVIDIA的GPU環(huán)境可以考慮TensorRT速度能進一步翻倍但配置稍微繁瑣一些。5.2 本地推理服務(wù)示例寫一個簡單的Flask服務(wù)接收圖片返回特征向量這樣方便前后端分離對接。實際生產(chǎn)環(huán)境通常用gRPC性能和吞吐更好但邏輯是一樣的。from flask import Flask, request, jsonify import cv2 import numpy as np import onnxruntime as ort app Flask(__name__) session ort.InferenceSession(face_net.onnx) def preprocess(image_bytes): image cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR) aligned align_face(image, detect_keypoints(image)) aligned (aligned - 127.5) / 128.0 aligned aligned.transpose(2, 0, 1).astype(np.float32) return np.expand_dims(aligned, axis0) app.route(/embedding, methods[POST]) def get_embedding(): image_bytes request.files[image].read() img preprocess(image_bytes) feature session.run(None, {input: img})[0][0] return jsonify({feature: feature.tolist()}) if __name__ __main__: app.run(host0.0.0.0, port8080)這只是一個最小可用示例生產(chǎn)環(huán)境還需要加鑒權(quán)、限流、批量處理等機制但流程是通的。特提取出來后比對還是在服務(wù)端做或者把特征下發(fā)到設(shè)備端做取決于架構(gòu)設(shè)計。5.3 單機比對效率與大規(guī)模方案取舍人臉特征比對本身計算量不算大。一萬個人的人臉庫每人512維浮點特征總大小約20MB加載到內(nèi)存里做線性掃描一次完整比對大概也就幾十毫秒。這個量級完全不用上專門的向量數(shù)據(jù)庫。但當你的人臉庫到十萬、百萬量級時線性掃描就吃力了這時候得考慮近似最近鄰索引。常用的方案有FAISS和Milvus。以FAISS為例用IVF索引可以把百萬級庫的檢索時間從幾百毫秒壓縮到幾十毫秒以內(nèi)。我的建議是項目初期不要上重組件一個小型庫用內(nèi)存比對完全夠用。等技術(shù)方案驗證成功、業(yè)務(wù)量確實起來了再引入FAISS或者Milvus系統(tǒng)性平滑遷移并不難。6. 人臉識別項目的工程落地經(jīng)驗6.1 從Demo到產(chǎn)品的關(guān)鍵差異一個在實驗室跑通的模型Demo和一套實際可用的產(chǎn)品之間有挺長的路要走。實驗室環(huán)境里所有人臉都是正臉、光照均勻、分辨率足夠現(xiàn)實環(huán)境是反著來的。我自己做過一個校園門禁項目最初在測試集上準確率99%看著很漂亮一上真實場景立刻暴露問題逆光環(huán)境下人臉過曝、戴帽子遮擋額頭、手機屏幕反光等各種情況都冒出來了。解決方案不是死磕算法而是從硬件和工程角度做優(yōu)化調(diào)整攝像頭安裝位置避免逆光增加補光燈設(shè)計合理的閘機等待區(qū)讓用戶能正面面對鏡頭。做項目的同學(xué)和工程師一定要記住一句話人臉識別是“算法加工程”的綜合問題光有模型準確率解決不了所有事。6.2 隱私安全與合規(guī)注意事項人臉數(shù)據(jù)屬于個人敏感信息采集、存儲、使用都要特別注意。做項目的時候有幾條底線不要碰不收集與識別無關(guān)的人臉信息、不明文存儲原始人臉圖像、不將人臉數(shù)據(jù)用于授權(quán)范圍之外的用途。技術(shù)上可以做幾件事來降低風險原始圖片只在內(nèi)存中做檢測和特征提取不落盤數(shù)據(jù)庫里只存特征向量不存原始照片特征向量本身建議加密存儲。這樣即使數(shù)據(jù)庫泄露攻擊者拿到的也只是一堆無法還原成圖像的數(shù)值。6.3 后續(xù)還能怎么擴展人臉識別做到能跑后續(xù)擴展方向其實挺多。比如加上活體檢測防止照片或視頻冒充?;铙w檢測分兩類配合式讓你眨眨眼、搖搖頭和非配合式靜默判斷后者在安防場景更友好但技術(shù)難度高一些。還可以做人臉屬性分析性別、年齡、表情這些都能從特征里再派生出來。又或者做跨攝像頭的人臉軌跡追蹤在多個攝像頭畫面中確認同一個人出現(xiàn)的時間和位置這些在商業(yè)場景中都很常見。我個人最推薦初學(xué)的人把精力放在“特征質(zhì)量”這件事上因為不管上層應(yīng)用怎么變特征總是最核心的東西。特征分得開、穩(wěn)得住上面做表情分析也好、做軌跡也好效果都不會差。最后再分享一個做項目時特別實用的習(xí)慣每次跑實驗都要記錄輸入圖像的來源、預(yù)處理參數(shù)、模型版本、閾值設(shè)置最好連同最終效果一起存下來。人臉識別實驗的變量非常多不記錄的話你可能今天跑出一個好結(jié)果下周想復(fù)現(xiàn)卻怎么也想不起來當時是怎么配的了。做好記錄比多調(diào)幾次參有用得多。