人臉識別:使用Scikit-Learn構建人臉識別系統(tǒng)
什么是人臉識別
人臉識別是將未知個體的人臉與存儲記錄數(shù)據(jù)庫中的圖像進行比較的任務。映射可以是一對一或一對多,這取決于我們是在運行人臉驗證還是人臉識別。
在本教程中,我們感興趣的是構建一個面部識別系統(tǒng),該系統(tǒng)將驗證圖像(通常稱為探測圖像)是否存在于預先存在的面部數(shù)據(jù)庫(通常稱為評估集)中。
直覺
建立這樣一個系統(tǒng)涉及四個主要步驟:
1.檢測圖像中的人臉可用的人臉檢測模型包括MTCNN、FaceNet、Dlib等。
2.裁剪和對齊人面OpenCV庫提供了此步驟所需的所有工具。
3.查找每個面的向量表示由于程序不能直接處理jpg或png文件,我們需要某種方法將圖像轉換為數(shù)字。在本教程中,我們將使用Insightface模型為人臉創(chuàng)建多維(512-d)嵌入,從而封裝與人臉相關的有用語義信息。要使用單個庫處理所有三個步驟,我們將使用insightface。特別是,我們將使用Insightface的ArcFace模型。InsightFace是一個開源的深度人臉分析模型,用于人臉識別、人臉檢測和人臉對齊任務。
4.比較嵌入一旦我們將每個唯一的人臉轉換成一個向量,比較特征就歸結為比較相應的嵌入。我們將利用這些嵌入來訓練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模型版本。(它包含兩個預訓練的檢測和識別模型)。把它放在*~/.insightface/models/下,所以在~/.insightface/models/antelope.onnx*上有onnx模型。這是正確完成設置后的外觀:
如果你查看antelope目錄,你會發(fā)現(xiàn)用于人臉檢測和識別的兩個onnx模型:
注意:自從上周insightface 0.4.1的最新版本發(fā)布以來,安裝并不像我希望的那樣簡單(至少對我來說)。因此,我將在本教程中使用0.2.1。
將來,我將相應地更新Github上的代碼。
如果你被卡住了,請看這里的說明。數(shù)據(jù)集我們將使用Kaggle上提供的Yale人臉數(shù)據(jù)集,該數(shù)據(jù)集包含15個人的大約165張灰度圖像(即每個人大概11張唯一圖像)。
這些圖像由各種表情、姿勢和照明組成。獲得數(shù)據(jù)集后,繼續(xù)將其解壓縮到項目中新創(chuàng)建的數(shù)據(jù)目錄中(請參閱Github上的項目目錄結構)
開始如果你想繼續(xù),可以在Github上找到Jupyter筆記本:https://github.com/V-Sher/Face-Search/blob/main/notebooks/face-search-yale.ipynb。導入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后,我們必須調用app=FaceAnalysis(name="model_name")來加載模型。由于我們將onnx模型存儲在antelope目錄中:app = FaceAnalysis(name="antelope")
app.prepare(ctx_id=0, det_size=(640, 640))
生成Insightface嵌入使用insightface模型為圖像生成嵌入非常簡單。例如:# 為圖像生成嵌入
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ù)集之前,我們必須修復目錄中文件的擴展名,使文件名以.gif結尾。(或.jpg、.png等)。例如,以下代碼段將文件名subject01.glasses更改為subject01_glasses.gif。# 修復擴展名
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))
接下來,我們將數(shù)據(jù)分為評估集和探測集:每個受試者90%或10張圖像將成為評估集的一部分,每個受試者剩余的10%或1張圖像將用于探測集中。為了避免采樣偏差,將使用名為create_probe_eval_set的輔助函數(shù)隨機選擇每個對象的探測圖像。它將包含屬于特定主題的11個圖像(文件名)的列表作為輸入,并返回長度為1和10的兩個列表。前者包含用于探測集的文件名,而后者包含用于評估集的文件名。def create_probe_eval_set(files: List):
# 選擇0和len(files)-1之間的隨機索引
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返回的兩個列表都按順序送到名為generate_embs的助手函數(shù)。對于列表中的每個文件名,它讀取灰度圖像,將其轉換為RGB,計算相應的嵌入,最后返回嵌入以及圖像標簽。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)
# 將灰度轉換為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)
# 添加標簽到eval_label set
embs_label.a(chǎn)ppend(img_fpath.split("_")[0])
return embs_set, embs_label
現(xiàn)在我們有了一個生成嵌入的框架,讓我們繼續(xù)使用generate_embs()為探測和評估集創(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+IMAGES_PER_IDENTITY])
# 存儲eval embs和標簽
eval_set_t, eval_labels_t = generate_embs(eval)
eval_set.extend(eval_set_t)
eval_labels.extend(eval_labels_t)
# 存儲探測embs和標簽
probe_set_t, probe_labels_t = generate_embs(probe)
probe_set.extend(probe_set_t)
probe_labels.extend(probe_labels_t)
需要考慮的幾件事:os.listdir返回的文件是完全隨機的,因此第3行的排序很重要。不帶排序和帶排序的os.listdir輸出:
[可選]如果我們使用sklearn提供的分層訓練測試功能,我們本可以替換create_probe_eval_set函數(shù),去掉forloop,并簡化上述代碼段中的幾行。然而,在本教程中,我將清晰性置于代碼簡單性之上。通常情況下,insightface無法檢測到人臉,并隨后為其生成空嵌入。這解釋了為什么probe_setor eval_set列表中的某些條目可能為空。重要的是我們要過濾掉它們,只保留非空值。為此,我們創(chuàng)建了另一個名為filter_empty_embs的助手函數(shù):def filter_empty_embs(img_set: List, img_labels: List[str]):
# 在insightface無法生成嵌入的地方過濾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無法生成嵌入的元素(參見第6行)。隨后,它還會更新標簽(probe_labels或eval_labels)(請參見第7行),以使集合和標簽具有相同的長度。最后,對于評估集和探測集中,我們可以獲得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)
有了這兩套設備,我們現(xiàn)在可以使用Sklearn庫中實現(xiàn)的一種流行的無監(jiān)督學習方法來構建人臉識別系統(tǒng)。創(chuàng)建人臉識別系統(tǒng)我們使用.fit訓練最近鄰模型,評估嵌入為X。這是一種用于無監(jiān)督最近鄰學習的簡潔技術。注:一般來說,距離可以是任何度量單位,如歐幾里德、曼哈頓、余弦、閔可夫斯基等。# 最近鄰學習方法
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)
# 過了一段時間…
# 從磁盤加載模型
# with open(filename, 'rb') as file:
# pickle_model = pickle.load(file)
因為我們正在實施一種無監(jiān)督的學習方法,請注意,我們沒有將任何標簽傳遞給fit方法,即評估標簽。我們在這里所做的就是將評估集中的人臉嵌入映射到一個潛在空間中。為什么??簡單回答:通過提前將訓練集存儲在內(nèi)存中,我們可以在推理過程中加快搜索最近鄰的速度。它是如何做到這一點的?簡單回答:在內(nèi)存中以優(yōu)化的方式存儲樹是非常有用的,尤其是當訓練集很大并且搜索新點的鄰居時,計算成本會很高。基于鄰域的方法被稱為非泛化機器學習方法,因為它們只是“記住”其所有訓練數(shù)據(jù)推理對于每個新的探測圖像,我們可以通過使用nn.neights方法搜索其前k個鄰域來確定它是否存在于評估集中。例如,# 測試圖像的實例推理
dists, inds = nn.kneighbors(X = probe_img_emb.reshape(1,-1),
n_neighbors = 3,
return_distances = True
)
如果評估集中返回索引(IND)處的標簽與圖像的原始/真實標簽完全匹配,則我們知道我們在驗證系統(tǒng)中找到了自己的臉。我們已經(jīng)將上述邏輯包裝到print_ID_results方法中。它將探測圖像路徑、評估集標簽和詳細標志作為輸入,以指定是否應顯示詳細結果。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獲取預測
dists, inds = nn.kneighbors(X=img_emb.reshape(1,-1), n_neighbors=3, return_distance=True)
# 獲取鄰居的標簽
pred_labels = [evaluation_labels[i] for i in inds[0]]
# 檢查dist是否大于0.5,如果是,打印結果
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。ⅲ
# 打印標簽和相應的距離
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}")
這里需要注意的幾個重要事項:IND包含評估標簽集中最近鄰的索引(第6行)。例如,inds=[[2,0,11]]意味著評估中索引=2處的標簽被發(fā)現(xiàn)最靠近探測圖像,然后是索引=0處的標簽。因為對于任何圖像,nn.neighbors都會返回非空響應。我們要過濾一些,如果返回的距離小于或等于0.6(行12),我們只考慮這些結果。(請注意,0.6的選擇完全是任意的)。例如,繼續(xù)上面的例子,其中Inds= [[2,0,11 ] ]和例子= [[ 0.4,0.6,0.9 ] ],我們將只考慮在索引=2和索引=0,因為最后一個鄰居的距離太大。作為一個快速的健康檢查,讓我們看看當我們輸入嬰兒的臉作為探測圖像時系統(tǒng)的響應。正如所料,它顯示沒有找到匹配的臉!但是,我們將verbose設置為True,因此我們可以在數(shù)據(jù)庫中看到其偽近鄰的標簽和距離,所有這些都非常大(>0.8)。
人臉識別系統(tǒng)的評價測試此系統(tǒng)是否良好的方法之一是查看前k個鄰居中存在多少相關結果。相關結果是真實標簽與預測標簽匹配的結果。該度量通常稱為k處的精確度,其中k是預先確定的。例如,從探測集中選擇一個圖像(或者更確切地說是一個嵌入),其真實標簽為“subject01”。如果nn.Neighers為該圖像返回的前兩個pred_labels為['subject01','subject01'],則表示k處的精度(p@k)k=2時為100%。類似地,如果pred_labels中只有一個值等于“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ù)計算整個探測集上p@k的平均值:# 探測集上的推理
dists, inds = nn.kneighbors(X=probe_embs, n_neighbors=2, return_distance=True)
# 計算平均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ù)改進。
請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個字
最新活動更多
-
即日-11.13立即報名>>> 【在線會議】多物理場仿真助跑新能源汽車
-
11月28日立即報名>>> 2024工程師系列—工業(yè)電子技術在線會議
-
12月19日立即報名>> 【線下會議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會
-
即日-12.26火熱報名中>> OFweek2024中國智造CIO在線峰會
-
即日-2025.8.1立即下載>> 《2024智能制造產(chǎn)業(yè)高端化、智能化、綠色化發(fā)展藍皮書》
-
精彩回顧立即查看>> 【限時免費下載】TE暖通空調系統(tǒng)高效可靠的組件解決方案
推薦專題
-
5 夾縫中的文遠知行
- 高級軟件工程師 廣東省/深圳市
- 自動化高級工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結構工程師 廣東省/深圳市