訂閱
糾錯(cuò)
加入自媒體

人臉識(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)。

聲明: 本文由入駐維科號(hào)的作者撰寫,觀點(diǎn)僅代表作者本人,不代表OFweek立場(chǎng)。如有侵權(quán)或其他問(wèn)題,請(qǐng)聯(lián)系舉報(bào)。

發(fā)表評(píng)論

0條評(píng)論,0人參與

請(qǐng)輸入評(píng)論內(nèi)容...

請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字

您提交的評(píng)論過(guò)于頻繁,請(qǐng)輸入驗(yàn)證碼繼續(xù)

  • 看不清,點(diǎn)擊換一張  刷新

暫無(wú)評(píng)論

暫無(wú)評(píng)論

    掃碼關(guān)注公眾號(hào)
    OFweek人工智能網(wǎng)
    獲取更多精彩內(nèi)容
    文章糾錯(cuò)
    x
    *文字標(biāo)題:
    *糾錯(cuò)內(nèi)容:
    聯(lián)系郵箱:
    *驗(yàn) 證 碼:

    粵公網(wǎng)安備 44030502002758號(hào)