人臉識(shí)別:使用Scikit-Learn構(gòu)建人臉識(shí)別系統(tǒng)
什么是人臉識(shí)別
人臉識(shí)別是將未知個(gè)體的人臉與存儲(chǔ)記錄數(shù)據(jù)庫(kù)中的圖像進(jìn)行比較的任務(wù)。映射可以是一對(duì)一或一對(duì)多,這取決于我們是在運(yùn)行人臉驗(yàn)證還是人臉識(shí)別。
在本教程中,我們感興趣的是構(gòu)建一個(gè)面部識(shí)別系統(tǒng),該系統(tǒng)將驗(yàn)證圖像(通常稱為探測(cè)圖像)是否存在于預(yù)先存在的面部數(shù)據(jù)庫(kù)(通常稱為評(píng)估集)中。
直覺(jué)
建立這樣一個(gè)系統(tǒng)涉及四個(gè)主要步驟:
1.檢測(cè)圖像中的人臉可用的人臉檢測(cè)模型包括MTCNN、FaceNet、Dlib等。
2.裁剪和對(duì)齊人面OpenCV庫(kù)提供了此步驟所需的所有工具。
3.查找每個(gè)面的向量表示由于程序不能直接處理jpg或png文件,我們需要某種方法將圖像轉(zhuǎn)換為數(shù)字。在本教程中,我們將使用Insightface模型為人臉創(chuàng)建多維(512-d)嵌入,從而封裝與人臉相關(guān)的有用語(yǔ)義信息。要使用單個(gè)庫(kù)處理所有三個(gè)步驟,我們將使用insightface。特別是,我們將使用Insightface的ArcFace模型。InsightFace是一個(gè)開(kāi)源的深度人臉?lè)治瞿P,用于人臉識(shí)別、人臉檢測(cè)和人臉對(duì)齊任務(wù)。
4.比較嵌入一旦我們將每個(gè)唯一的人臉轉(zhuǎn)換成一個(gè)向量,比較特征就歸結(jié)為比較相應(yīng)的嵌入。我們將利用這些嵌入來(lái)訓(xùn)練scikit-learn模型。
另外,如果你想繼續(xù),代碼可以在Github上找到:https://github.com/V-Sher/Face-Search。
安裝程序
創(chuàng)建虛擬環(huán)境(可選):python3 -m venv face_search_env
激活此環(huán)境:source face_search_env/bin/activate
此環(huán)境中的必要安裝:pip install mxnet==1.8.0.post0
pip install -U insightface==0.2.1
pip install onnx==1.10.1
pip install onnxruntime==1.8.1
更重要的是,完成pip安裝insightface后:從onedrive下載antelope模型版本。(它包含兩個(gè)預(yù)訓(xùn)練的檢測(cè)和識(shí)別模型)。把它放在*~/.insightface/models/下,所以在~/.insightface/models/antelope.onnx*上有onnx模型。這是正確完成設(shè)置后的外觀:
如果你查看antelope目錄,你會(huì)發(fā)現(xiàn)用于人臉檢測(cè)和識(shí)別的兩個(gè)onnx模型:
注意:自從上周insightface 0.4.1的最新版本發(fā)布以來(lái),安裝并不像我希望的那樣簡(jiǎn)單(至少對(duì)我來(lái)說(shuō))。因此,我將在本教程中使用0.2.1。
將來(lái),我將相應(yīng)地更新Github上的代碼。
如果你被卡住了,請(qǐng)看這里的說(shuō)明。數(shù)據(jù)集我們將使用Kaggle上提供的Yale人臉數(shù)據(jù)集,該數(shù)據(jù)集包含15個(gè)人的大約165張灰度圖像(即每個(gè)人大概11張唯一圖像)。
這些圖像由各種表情、姿勢(shì)和照明組成。獲得數(shù)據(jù)集后,繼續(xù)將其解壓縮到項(xiàng)目中新創(chuàng)建的數(shù)據(jù)目錄中(請(qǐng)參閱Github上的項(xiàng)目目錄結(jié)構(gòu))
開(kāi)始如果你想繼續(xù),可以在Github上找到Jupyter筆記本:https://github.com/V-Sher/Face-Search/blob/main/notebooks/face-search-yale.ipynb。導(dǎo)入import os
import pickle
import numpy as np
from PIL import Image
from typing import List
from tqdm import tqdm
from insightface.a(chǎn)pp import FaceAnalysis
from sklearn.neighbors import NearestNeighbors
加載Insightface模型安裝insightface后,我們必須調(diào)用app=FaceAnalysis(name="model_name")來(lái)加載模型。由于我們將onnx模型存儲(chǔ)在antelope目錄中:app = FaceAnalysis(name="antelope")
app.prepare(ctx_id=0, det_size=(640, 640))
生成Insightface嵌入使用insightface模型為圖像生成嵌入非常簡(jiǎn)單。例如:# 為圖像生成嵌入
img_emb_results = app.get(np.a(chǎn)sarray(img))
img_emb = img_emb_results[0].embedding
img_emb.shape
------------OUTPUT---------------
(512,)
數(shù)據(jù)集在使用此數(shù)據(jù)集之前,我們必須修復(fù)目錄中文件的擴(kuò)展名,使文件名以.gif結(jié)尾。(或.jpg、.png等)。例如,以下代碼段將文件名subject01.glasses更改為subject01_glasses.gif。# 修復(fù)擴(kuò)展名
YALE_DIR = "../data/yalefaces"
files = os.listdir(YALE_DIR)[1:]
for i, img in enumerate(files):
# print("original name: ", img)
new_ext_name = "_".join(img.split(".")) + ".gif"
# print("new name: ", new_ext_name)
os.rename(os.path.join(YALE_DIR, img), os.path.join(YALE_DIR, new_ext_name))
接下來(lái),我們將數(shù)據(jù)分為評(píng)估集和探測(cè)集:每個(gè)受試者90%或10張圖像將成為評(píng)估集的一部分,每個(gè)受試者剩余的10%或1張圖像將用于探測(cè)集中。為了避免采樣偏差,將使用名為create_probe_eval_set的輔助函數(shù)隨機(jī)選擇每個(gè)對(duì)象的探測(cè)圖像。它將包含屬于特定主題的11個(gè)圖像(文件名)的列表作為輸入,并返回長(zhǎng)度為1和10的兩個(gè)列表。前者包含用于探測(cè)集的文件名,而后者包含用于評(píng)估集的文件名。def create_probe_eval_set(files: List):
# 選擇0和len(files)-1之間的隨機(jī)索引
random_idx = np.random.randint(0,len(files))
probe_img_fpaths = [files[random_idx]]
eval_img_fpaths = [files[idx] for idx in range(len(files)) if idx 。 random_idx]
return probe_img_fpaths, eval_img_fpaths
生成嵌入create_probe_eval_set返回的兩個(gè)列表都按順序送到名為generate_embs的助手函數(shù)。對(duì)于列表中的每個(gè)文件名,它讀取灰度圖像,將其轉(zhuǎn)換為RGB,計(jì)算相應(yīng)的嵌入,最后返回嵌入以及圖像標(biāo)簽。def generate_embs(img_fpaths: List[str])
embs_set = list()
embs_label = list()
for img_fpath in img_fpaths:
# 讀取灰度圖
img = Image.open(os.path.join(YALE_DIR, img_fpath))
img_arr = np.a(chǎn)sarray(img)
# 將灰度轉(zhuǎn)換為RGB
im = Image.fromarray((img_arr * 255).a(chǎn)stype(np.uint8))
rgb_arr = np.a(chǎn)sarray(im.convert('RGB'))
# 生成Insightface嵌入
res = app.get(rgb_arr)
# 將emb添加到eval set
embs_set.a(chǎn)ppend(res)
# 添加標(biāo)簽到eval_label set
embs_label.a(chǎn)ppend(img_fpath.split("_")[0])
return embs_set, embs_label
現(xiàn)在我們有了一個(gè)生成嵌入的框架,讓我們繼續(xù)使用generate_embs()為探測(cè)和評(píng)估集創(chuàng)建嵌入。# 排序文件
files = os.listdir(YALE_DIR)
files.sort()
eval_set = list()
eval_labels = list()
probe_set = list()
probe_labels = list()
IMAGES_PER_IDENTITY = 11
for i in tqdm(range(1, len(files), IMAGES_PER_IDENTITY), unit_divisor=True): # 忽略在files[0]的README.txt文件
# print(i)
probe, eval = create_probe_eval_set(files[i:i+I(xiàn)MAGES_PER_IDENTITY])
# 存儲(chǔ)eval embs和標(biāo)簽
eval_set_t, eval_labels_t = generate_embs(eval)
eval_set.extend(eval_set_t)
eval_labels.extend(eval_labels_t)
# 存儲(chǔ)探測(cè)embs和標(biāo)簽
probe_set_t, probe_labels_t = generate_embs(probe)
probe_set.extend(probe_set_t)
probe_labels.extend(probe_labels_t)
需要考慮的幾件事:os.listdir返回的文件是完全隨機(jī)的,因此第3行的排序很重要。不帶排序和帶排序的os.listdir輸出:
[可選]如果我們使用sklearn提供的分層訓(xùn)練測(cè)試功能,我們本可以替換create_probe_eval_set函數(shù),去掉forloop,并簡(jiǎn)化上述代碼段中的幾行。然而,在本教程中,我將清晰性置于代碼簡(jiǎn)單性之上。通常情況下,insightface無(wú)法檢測(cè)到人臉,并隨后為其生成空嵌入。這解釋了為什么probe_setor eval_set列表中的某些條目可能為空。重要的是我們要過(guò)濾掉它們,只保留非空值。為此,我們創(chuàng)建了另一個(gè)名為filter_empty_embs的助手函數(shù):def filter_empty_embs(img_set: List, img_labels: List[str]):
# 在insightface無(wú)法生成嵌入的地方過(guò)濾filtering where insightface could not generate an embedding
good_idx = [i for i,x in enumerate(img_set) if x]
if len(good_idx) == len(img_set):
clean_embs = [e[0].embedding for e in img_set]
clean_labels = img_labels
else:
# 保留good_idx
clean_labels = np.a(chǎn)rray(img_labels)[good_idx]
clean_set = np.a(chǎn)rray(img_set, dtype=object)[good_idx]
# 生成embs
clean_embs = [e[0].embedding for e in clean_set]
return clean_embs, clean_labels
它將圖像集(probe_set或eval_set)作為輸入,并刪除insightface無(wú)法生成嵌入的元素(參見(jiàn)第6行)。隨后,它還會(huì)更新標(biāo)簽(probe_labels或eval_labels)(請(qǐng)參見(jiàn)第7行),以使集合和標(biāo)簽具有相同的長(zhǎng)度。最后,對(duì)于評(píng)估集和探測(cè)集中,我們可以獲得512維嵌入:evaluation_embs, evaluation_labels = filter_empty_embs(eval_set, eval_labels)
probe_embs, probe_labels = filter_empty_embs(probe_set, probe_labels)
assert len(evaluation_embs) == len(evaluation_labels)
assert len(probe_embs) == len(probe_labels)
有了這兩套設(shè)備,我們現(xiàn)在可以使用Sklearn庫(kù)中實(shí)現(xiàn)的一種流行的無(wú)監(jiān)督學(xué)習(xí)方法來(lái)構(gòu)建人臉識(shí)別系統(tǒng)。創(chuàng)建人臉識(shí)別系統(tǒng)我們使用.fit訓(xùn)練最近鄰模型,評(píng)估嵌入為X。這是一種用于無(wú)監(jiān)督最近鄰學(xué)習(xí)的簡(jiǎn)潔技術(shù)。注:一般來(lái)說(shuō),距離可以是任何度量單位,如歐幾里德、曼哈頓、余弦、閔可夫斯基等。# 最近鄰學(xué)習(xí)方法
nn = NearestNeighbors(n_neighbors=3, metric="cosine")
nn.fit(X=evaluation_embs)
# 保存模型到磁盤
filename = 'faceID_model.pkl'
with open(filename, 'wb') as file:
pickle.dump(nn, file)
# 過(guò)了一段時(shí)間…
# 從磁盤加載模型
# with open(filename, 'rb') as file:
# pickle_model = pickle.load(file)
因?yàn)槲覀冋趯?shí)施一種無(wú)監(jiān)督的學(xué)習(xí)方法,請(qǐng)注意,我們沒(méi)有將任何標(biāo)簽傳遞給fit方法,即評(píng)估標(biāo)簽。我們?cè)谶@里所做的就是將評(píng)估集中的人臉嵌入映射到一個(gè)潛在空間中。為什么??簡(jiǎn)單回答:通過(guò)提前將訓(xùn)練集存儲(chǔ)在內(nèi)存中,我們可以在推理過(guò)程中加快搜索最近鄰的速度。它是如何做到這一點(diǎn)的?簡(jiǎn)單回答:在內(nèi)存中以優(yōu)化的方式存儲(chǔ)樹(shù)是非常有用的,尤其是當(dāng)訓(xùn)練集很大并且搜索新點(diǎn)的鄰居時(shí),計(jì)算成本會(huì)很高;卩徲虻姆椒ū环Q為非泛化機(jī)器學(xué)習(xí)方法,因?yàn)樗鼈冎皇恰坝涀 逼渌杏?xùn)練數(shù)據(jù)推理對(duì)于每個(gè)新的探測(cè)圖像,我們可以通過(guò)使用nn.neights方法搜索其前k個(gè)鄰域來(lái)確定它是否存在于評(píng)估集中。例如,# 測(cè)試圖像的實(shí)例推理
dists, inds = nn.kneighbors(X = probe_img_emb.reshape(1,-1),
n_neighbors = 3,
return_distances = True
)
如果評(píng)估集中返回索引(IND)處的標(biāo)簽與圖像的原始/真實(shí)標(biāo)簽完全匹配,則我們知道我們?cè)隍?yàn)證系統(tǒng)中找到了自己的臉。我們已經(jīng)將上述邏輯包裝到print_ID_results方法中。它將探測(cè)圖像路徑、評(píng)估集標(biāo)簽和詳細(xì)標(biāo)志作為輸入,以指定是否應(yīng)顯示詳細(xì)結(jié)果。def print_ID_results(img_fpath: str, evaluation_labels: np.ndarray, verbose: bool = False):
img = Image.open(img_fpath)
img_emb = app.get(np.a(chǎn)sarray(img))[0].embedding
# 從KNN獲取預(yù)測(cè)
dists, inds = nn.kneighbors(X=img_emb.reshape(1,-1), n_neighbors=3, return_distance=True)
# 獲取鄰居的標(biāo)簽
pred_labels = [evaluation_labels[i] for i in inds[0]]
# 檢查dist是否大于0.5,如果是,打印結(jié)果
no_of_matching_faces = np.sum([1 if d <=0.6 else 0 for d in dists[0]])
if no_of_matching_faces > 0:
print("Matching face(s) found in database! ")
verbose = True
else:
print("No matching face(s) not found in database!")
# 打印標(biāo)簽和相應(yīng)的距離
if verbose:
for label, dist in zip(pred_labels, dists[0]):
print(f"Nearest neighbours found in the database have labels {label} and is at a distance of {dist}")
這里需要注意的幾個(gè)重要事項(xiàng):IND包含評(píng)估標(biāo)簽集中最近鄰的索引(第6行)。例如,inds=[[2,0,11]]意味著評(píng)估中索引=2處的標(biāo)簽被發(fā)現(xiàn)最靠近探測(cè)圖像,然后是索引=0處的標(biāo)簽。因?yàn)閷?duì)于任何圖像,nn.neighbors都會(huì)返回非空響應(yīng)。我們要過(guò)濾一些,如果返回的距離小于或等于0.6(行12),我們只考慮這些結(jié)果。(請(qǐng)注意,0.6的選擇完全是任意的)。例如,繼續(xù)上面的例子,其中Inds= [[2,0,11 ] ]和例子= [[ 0.4,0.6,0.9 ] ],我們將只考慮在索引=2和索引=0,因?yàn)樽詈笠粋(gè)鄰居的距離太大。作為一個(gè)快速的健康檢查,讓我們看看當(dāng)我們輸入嬰兒的臉作為探測(cè)圖像時(shí)系統(tǒng)的響應(yīng)。正如所料,它顯示沒(méi)有找到匹配的臉!但是,我們將verbose設(shè)置為True,因此我們可以在數(shù)據(jù)庫(kù)中看到其偽近鄰的標(biāo)簽和距離,所有這些都非常大(>0.8)。
人臉識(shí)別系統(tǒng)的評(píng)價(jià)測(cè)試此系統(tǒng)是否良好的方法之一是查看前k個(gè)鄰居中存在多少相關(guān)結(jié)果。相關(guān)結(jié)果是真實(shí)標(biāo)簽與預(yù)測(cè)標(biāo)簽匹配的結(jié)果。該度量通常稱為k處的精確度,其中k是預(yù)先確定的。例如,從探測(cè)集中選擇一個(gè)圖像(或者更確切地說(shuō)是一個(gè)嵌入),其真實(shí)標(biāo)簽為“subject01”。如果nn.Neighers為該圖像返回的前兩個(gè)pred_labels為['subject01','subject01'],則表示k處的精度(p@k)k=2時(shí)為100%。類似地,如果pred_labels中只有一個(gè)值等于“subject05”,p@k將是50%,依此類推…dists, inds = nn.kneighbors(X=probe_embs_example.reshape(1, -1),
n_neighbors=2,
return_distance=True)
pred_labels = [evaluation_labels[i] for i in inds[0] ]
pred_labels
----- OUTPUT ------
['002', '002']
讓我們繼續(xù)計(jì)算整個(gè)探測(cè)集上p@k的平均值:# 探測(cè)集上的推理
dists, inds = nn.kneighbors(X=probe_embs, n_neighbors=2, return_distance=True)
# 計(jì)算平均p@k
p_at_k = np.zeros(len(probe_embs))
for i in range(len(probe_embs)):
true_label = probe_labels[i]
pred_neighbr_idx = inds[i]
pred_labels = [evaluation_labels[id] for id in pred_neighbr_idx]
pred_is_labels = [1 if label == true_label else 0 for label in pred_labels]
p_at_k[i] = np.mean(pred_is_labels)
p_at_k.mean()
------ OUTPUT --------
0.9
90%!還可以,但肯定可以繼續(xù)改進(jìn)。

發(fā)表評(píng)論
請(qǐng)輸入評(píng)論內(nèi)容...
請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字
最新活動(dòng)更多
-
3月27日立即報(bào)名>> 【工程師系列】汽車電子技術(shù)在線大會(huì)
-
4月30日立即下載>> 【村田汽車】汽車E/E架構(gòu)革新中,新智能座艙挑戰(zhàn)的解決方案
-
5月15-17日立即預(yù)約>> 【線下巡回】2025年STM32峰會(huì)
-
即日-5.15立即報(bào)名>>> 【在線會(huì)議】安森美Hyperlux™ ID系列引領(lǐng)iToF技術(shù)革新
-
5月15日立即下載>> 【白皮書(shū)】精確和高效地表征3000V/20A功率器件應(yīng)用指南
-
5月16日立即參評(píng) >> 【評(píng)選啟動(dòng)】維科杯·OFweek 2025(第十屆)人工智能行業(yè)年度評(píng)選
推薦專題
- 1 UALink規(guī)范發(fā)布:挑戰(zhàn)英偉達(dá)AI統(tǒng)治的開(kāi)始
- 2 北電數(shù)智主辦酒仙橋論壇,探索AI產(chǎn)業(yè)發(fā)展新路徑
- 3 降薪、加班、裁員三重暴擊,“AI四小龍”已折戟兩家
- 4 “AI寒武紀(jì)”爆發(fā)至今,五類新物種登上歷史舞臺(tái)
- 5 國(guó)產(chǎn)智駕迎戰(zhàn)特斯拉FSD,AI含量差幾何?
- 6 光計(jì)算迎來(lái)商業(yè)化突破,但落地仍需時(shí)間
- 7 東陽(yáng)光:2024年扭虧、一季度凈利大增,液冷疊加具身智能打開(kāi)成長(zhǎng)空間
- 8 地平線自動(dòng)駕駛方案解讀
- 9 封殺AI“照騙”,“淘寶們”終于不忍了?
- 10 優(yōu)必選:營(yíng)收大增主靠小件,虧損繼續(xù)又逢關(guān)稅,能否乘機(jī)器人東風(fēng)翻身?