Een gezichtsherkenningssysteem bouwen met behulp van Scikit Learn in Python

Bronknooppunt: 1866698

gezichtsherkenning met Python

Wat is gezichtsherkenning?

Gezichtsherkenning is de taak van het vergelijken van een onbekend gezicht van een individu naar afbeeldingen in een database met opgeslagen records. De mapping kan één-op-één of één-op-veel zijn, afhankelijk van of we gezichtsverificatie of gezichtsidentificatie uitvoeren.

In deze zelfstudie zijn we geïnteresseerd in het bouwen van een gezichtsidentificatiesysteem dat zal verifiëren of een afbeelding, algemeen bekend als sondebeeld, bestaat binnen een reeds bestaande database met gezichten, algemeen bekend als de evaluatieset.

Intuïtie

Er zijn vier hoofdstappen betrokken bij het bouwen van een dergelijk systeem:

1. Detecteer gezichten in een afbeelding

Beschikbare modellen voor gezichtsdetectie zijn MTCNN, FaceNet, Dlib, enz.

2. Gezichten bijsnijden en uitlijnen voor uniformiteit

OpenCV-bibliotheek biedt alle tools die we nodig hebben voor deze stap.

3. Zoek vectorweergave voor elk gezicht

Omdat programma's niet rechtstreeks met jpg- of png-bestanden kunnen werken, hebben we een manier nodig om afbeeldingen naar getallen te vertalen. In deze zelfstudie gebruiken we de Inzichtgezicht model voor het maken van een multidimensionale (512-d) inbedding voor een gezicht zodat het bruikbare semantisch informatie over het gezicht.

Om alle drie de stappen aan te pakken met behulp van een enkele bibliotheek, zullen we gebruiken insightface. We gaan met name werken met het ArcFace-model van Insightface.

InzichtGezicht is een open-source deep face-analysemodel voor gezichtsherkenning, gezichtsdetectie en gezichtsuitlijningstaken.

4. Vergelijking van de inbeddingen

Zodra we elk uniek gezicht in een vector hebben vertaald, komt het vergelijken van gezichten in wezen neer op het vergelijken van de overeenkomstige inbeddingen. We zullen deze inbeddingen gebruiken om een ​​sci-kit leermodel te trainen.

PS Als je mee wilt doen, de code is beschikbaar op GitHub.

Als deze diepgaande educatieve inhoud nuttig voor u is, abonneer u op onze AI-research mailinglijst om gewaarschuwd te worden wanneer we nieuw materiaal uitbrengen.

Setup

Creëer een virtuele omgeving (optioneel):

python3 -m venv face_search_env

Activeer deze omgeving:

bron face_search_env/bin/activate

Noodzakelijke installaties binnen deze omgeving:

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

Wat nog belangrijker is, als je eenmaal klaar bent met het installeren van pip insightface:

- Download de antilope modelvrijgave van onedrive. (Het bevat twee vooraf getrainde modellen voor detectie en herkenning).
- Leg het eronder ~/.insightface/models/, dus er zijn onnx-modellen bij ~/.insightface/models/antelope/*.onnx.

Dit is hoe het eruit zou moeten zien als de installatie correct is uitgevoerd:

en als je naar binnen kijkt antelope map, vindt u de twee onnx modellen voor gezichtsherkenning en -herkenning:

Opmerking: Sinds de laatste release van insightface 0.4.1 vorige week was de installatie niet zo eenvoudig als ik had gehoopt (althans voor mij). Daarom zal ik 0.2.1 gebruiken voor deze zelfstudie. In de toekomst zal ik de code op Github dienovereenkomstig bijwerken. Zie de instructies hier als je vastzit.

dataset

We zullen werken met de Yale Faces-dataset die beschikbaar is op Kaggle, met ongeveer 165 grijswaardenafbeeldingen van 15 individuen (dwz 11 unieke afbeeldingen per identiteit). De afbeeldingen zijn samengesteld uit een grote verscheidenheid aan uitdrukkingen, poses en verlichtingsconfiguraties.

Zodra u de dataset heeft, kunt u deze uitpakken in een nieuw gemaaktedata directory binnen uw project (zie de projectdirectorystructuur op GitHub).

Laten we beginnen…

Als je mee wilt volgen, de Jupyter Notebook is te vinden op Github.

Invoer

import os
import pickle
import numpy as np
from PIL import Image
from typing import List
from tqdm import tqdm from insightface.app import FaceAnalysis
from sklearn.neighbors import NearestNeighbors

Insightface-model laden

Eens insightface is geïnstalleerd, moeten we bellen app=FaceAnalysis(name="model_name")om de modellen te laden.

Sinds we onze hebben opgeslagen onnx modellen binnen de antilope directory:

app = FaceAnalysis(name="antelope")
app.prepare(ctx_id=0, det_size=(640, 640))

Genereer Insightface-insluitingen

Het genereren van een inbedding voor een afbeelding is vrij eenvoudig met de insightface model. Bijvoorbeeld:

# Generating embeddings for an image img_emb_results = app.get(np.asarray(img))
img_emb = img_emb_results[0].embedding
img_emb.shape
------------OUTPUT---------------
(512,)

dataset

Voordat we deze dataset gaan gebruiken, moeten we de extensies voor de bestanden in de map zodanig corrigeren dat de bestandsnamen eindigen op .gif. (of .jpg , .pngEnz.).

Het volgende codefragment verandert bijvoorbeeld de bestandsnaam subject01.glasses naar subject01_glasses.gif.

# Fixing the file extensions
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))

Vervolgens splitsen we de gegevens op in de evaluatie- en sondesets: 90% of 10 afbeeldingen per proefpersoon worden onderdeel van de evaluatieset en de resterende 10% of 1 afbeelding per proefpersoon wordt gebruikt in de sondeset.

Om bemonsteringsbias te voorkomen, wordt het sondebeeld voor elk onderwerp willekeurig gekozen met behulp van een hulpfunctie genaamd create_probe_eval_set() . Het neemt als invoer een lijst met de (bestandsnamen voor de) 11 afbeeldingen die bij een bepaald onderwerp horen en retourneert twee lijsten met lengtes 1 en 10. De eerste bevat de bestandsnaam die moet worden gebruikt voor de sondeset, terwijl de laatste bestandsnamen bevat voor de evaluatieset.

def create_probe_eval_set(files: List): # pick random index between 0 and 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

Genereer inbeddingen

Beide lijsten geretourneerd door de create_probe_eval_set() worden achtereenvolgens ingevoerd in een helperfunctie genaamd generate_embs(). Voor elke bestandsnaam in de lijst leest het de afbeelding in grijstinten, converteert het naar RGB, berekent de overeenkomstige inbeddingen en retourneert ten slotte de inbeddingen samen met de afbeeldingslabels (geschrapt uit de bestandsnaam).

def generate_embs(img_fpaths: List[str]): embs_set = list() embs_label = list() for img_fpath in img_fpaths: # read grayscale img img = Image.open(os.path.join(YALE_DIR, img_fpath)) img_arr = np.asarray(img) # convert grayscale to rgb im = Image.fromarray((img_arr * 255).astype(np.uint8)) rgb_arr = np.asarray(im.convert('RGB')) # generate Insightface embedding res = app.get(rgb_arr) # append emb to the eval set embs_set.append(res) # append label to eval_label set embs_label.append(img_fpath.split("_")[0]) return embs_set, embs_label

Nu we een raamwerk hebben voor het genereren van inbeddingen, gaan we verder met het maken van inbeddingen voor zowel de sonde als de evaluatieset met behulp van generate_embs().

# sorting files
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): # ignore the README.txt file at files[0] # print(i) probe, eval = create_probe_eval_set(files[i:i+IMAGES_PER_IDENTITY]) # store eval embs and labels eval_set_t, eval_labels_t = generate_embs(eval) eval_set.extend(eval_set_t) eval_labels.extend(eval_labels_t) # store probe embs and labels probe_set_t, probe_labels_t = generate_embs(probe) probe_set.extend(probe_set_t) probe_labels.extend(probe_labels_t)

Enkele dingen om te overwegen:

  • De bestanden geretourneerd door os.listdir()staan ​​in volledig willekeurige volgorde, daarom is sorteren op regel 3 belangrijk. Waarom hebben we gesorteerde bestandsnamen nodig? Onthouden create_probe_eval_set() op regel 11 vereist alle bestanden die bij een bepaald onderwerp horen in een enkele iteratie.

Uitvoer van os.listdir() zonder sorteren (links) en met sorteren (rechts)

  • [optioneel] We hadden de kunnen vervangen create_probe_eval_set() functie, ontdoen van de forloop, en vereenvoudigde een paar regels in het bovenstaande codefragment als we de gestratificeerd train_test_splitfunctionaliteit geleverd door sklearn. Voor deze zelfstudie heb ik echter prioriteit gegeven aan duidelijkheid boven de eenvoud van de code.

Vaak, insightface kan een gezicht niet detecteren en genereert er vervolgens een lege inbedding voor. Dat verklaart waarom sommige vermeldingen in probe_setor eval_set lijst is mogelijk leeg. Het is belangrijk dat we ze eruit filteren en alleen niet-lege waarden behouden.

Om dit te doen, creëren we een andere helperfunctie genaamd filter_empty_embs():

def filter_empty_embs(img_set: List, img_labels: List[str]): # 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: # filtering eval set and labels based on good idx clean_labels = np.array(img_labels)[good_idx] clean_set = np.array(img_set, dtype=object)[good_idx] # generating embs for good idx clean_embs = [e[0].embedding for e in clean_set] return clean_embs, clean_labels

Het neemt als invoer de afbeeldingsset (ofwel probe_set or eval_set ) en verwijdert die elementen waarvoor insightface kon geen inbedding genereren (zie regel 6). Hierna werkt het ook de labels bij (ofwel probe_labelsor eval_labels) (zie Regel 7) zodat beide sets en labels dezelfde lengte hebben.

Ten slotte kunnen we de 512-d inbedding verkrijgen voor alleen de goede indices in beide evaluatieset en sondeset:

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)

Met beide sets tot onze beschikking, zijn we nu klaar om ons gezichtsidentificatiesysteem te bouwen met behulp van een populaire leermethode zonder toezicht die is geïmplementeerd in de Sklearn-bibliotheek.

Een gezichtsherkenningssysteem maken

Wij trainen de Naaste buur model met behulp van .fit() met evaluatie-inbeddingen als X. Dit is een handige techniek om zonder toezicht naaste buren te leren.

Met de dichtstbijzijnde buurmethode kunnen we een vooraf bepaald aantal trainingsvoorbeelden vinden die qua afstand het dichtst bij een nieuw punt liggen.

Opmerking: de afstand kan in het algemeen elke metrische maat zijn, zoals Euclidisch, Manhattan, Cosinus, Minktowski, enz.

# Nearest neighbour learning method
nn = NearestNeighbors(n_neighbors=3, metric="cosine")
nn.fit(X=evaluation_embs) # save the model to disk
filename = 'faceID_model.pkl'
with open(filename, 'wb') as file: pickle.dump(nn, file) # some time later...
# load the model from disk
# with open(filename, 'rb') as file:
# pickle_model = pickle.load(file)

Omdat we een ongecontroleerd leermethode, merk op dat we geen labels doorgeven, dwz evaluation_label aan de fit methode. Het enige wat we hier doen is het in kaart brengen van de face-inbeddingen in de evaluatieset in een latente ruimte.

Waarom??, je vraagt.
Eenvoudig antwoord: door de trainingsset van tevoren in het geheugen op te slaan, kunnen we het zoeken naar de naaste buren tijdens de inferentietijd versnellen.

Hoe werkt dit?
Eenvoudig antwoord: het is best handig om de boom op een geoptimaliseerde manier in het geheugen op te slaan, vooral wanneer de trainingsset groot is en het zoeken naar de buren van een nieuw punt rekenkundig duur wordt.

Op buren gebaseerde methoden staan ​​bekend als niet-generaliserende methoden voor machinaal leren, omdat ze eenvoudigweg alle trainingsgegevens "onthouden" (mogelijk getransformeerd in een snelle indexeringsstructuur zoals een Ball Tree of KD Tree). [bron]

Opmerking: Zie dit StackOverflow-discussie als je nog steeds niet overtuigd bent!

Gevolgtrekking

Voor elk nieuw sondebeeld kunnen we vinden of het aanwezig is in de evaluatieset door naar de top ervan te zoeken k burengebruik nn.neighbours()methode. Bijvoorbeeld,

# Example inference on test image dists, inds = nn.kneighbors(X = probe_img_emb.reshape(1,-1), n_neighbors = 3, return_distances = True )

Als de labels bij de geretourneerde indexen (inds) in de evaluatieset perfect overeenkomen met het originele/echte label van de sondeafbeelding, dan weten we dat we ons gezicht in het verificatiesysteem hebben gevonden.

We hebben de bovengenoemde logica verpakt in de print_ID_results() methode. Het neemt als invoer het sondebeeldpad, de evaluatiesetlabels en de verbose vlag om aan te geven of gedetailleerde resultaten moeten worden weergegeven.

def print_ID_results(img_fpath: str, evaluation_labels: np.ndarray, verbose: bool = False): img = Image.open(img_fpath) img_emb = app.get(np.asarray(img))[0].embedding # get pred from KNN dists, inds = nn.kneighbors(X=img_emb.reshape(1,-1), n_neighbors=3, return_distance=True) # get labels of the neighbours pred_labels = [evaluation_labels[i] for i in inds[0]] # check if any dist is greater than 0.5, and if so, print the results 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!") # print labels and corresponding distances 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}")

Enkele belangrijke dingen om hier op te merken:

  • inds bevatten de indices van de naaste buren in de evaluation_labels instellen (lijn 6). Bijvoorbeeld, inds = [[2,0,11]]betekent label op index = 2 inch evaluation_labels blijkt het dichtst bij het sondebeeld te liggen, gevolgd door het label bij index = 0.
  • sinds voor elke beeld, nn.neighborseen niet-leeg antwoord zal retourneren, moeten we die resultaten alleen beschouwen als een gezichts-ID-overeenkomst if de geretourneerde afstand is kleiner dan of gelijk aan 0.6 (regel 12). (PS De keuze van 0.6 is volledig willekeurig).
    Ga bijvoorbeeld verder met het bovenstaande voorbeeld waar inds = [[2,0, 11]]en laten we zeggendists = [[0.4, 0.6, 0.9]], beschouwen we alleen de labels bij index=2 en index = 0 (in evaluation_labels) als een echte gezichtsovereenkomst omdat de dist want de laatste buurman is te groot om een ​​echte match te zijn.

Laten we, als een snelle controle van de gezondheid, de reactie van het systeem bekijken wanneer we het gezicht van een baby invoeren als een sondebeeld. Zoals verwacht, onthult het dat er geen overeenkomende gezichten zijn gevonden! We stellen echter vast verbose as True, waardoor we de labels en afstanden ervoor te zien krijgen nep naaste buren in de database, die allemaal behoorlijk groot lijken te zijn (>0.8).

Evalueren van het gezichtsherkenningssysteem

Een van de manieren om te testen of dit systeem goed is, is door te kijken hoeveel relevante  resultaten zijn aanwezig in de top k buren. Een relevant resultaat is een resultaat waarbij het ware label overeenkomt met het voorspelde label. Deze statistiek wordt over het algemeen aangeduid als precisie bij k, waarbij k vooraf bepaald is.

Kies bijvoorbeeld een afbeelding (of liever een inbedding ) uit de sondeset met een echt label als 'subject01'. Als de bovenste twee pred_labels geretourneerd door nn.neighborsvoor deze afbeelding zijn ['subject01', 'subject01'], dit betekent de precisie bij k (p@k) met k=2 bedraagt ​​100%. Evenzo, als slechts één van de waarden in pred_labels was gelijk aan 'subject05', p@k zou 50% zijn, enzovoort...

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']

Laten we doorgaan en het gemiddelde berekenen p@k waarde over de gehele sondeset:

# inference on probe set
dists, inds = nn.kneighbors(X=probe_embs, n_neighbors=2, return_distance=True) # calculate avg 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()

------ UITGANG --------0.9

Geweldig! 90% Niet al te armoedig maar zeker voor verbetering vatbaar (maar dat is voor een andere keer)…

Een pluim voor je dat je dit doorzet! Hopelijk was deze warme kennismaking met gezichtsherkenning, een actief onderzoeksgebied op het gebied van computervisie, voldoende om u op weg te helpen. Zoals altijd, laat het me weten als er een gemakkelijkere manier is om sommige dingen te doen die ik in dit artikel heb genoemd.

Tot de volgende keer 🙂

Podurama is de beste podcastspeler om meer dan een miljoen shows en 30 miljoen afleveringen te streamen. Het biedt de beste aanbevelingen op basis van uw interesses en luistergeschiedenis. Beschikbaar voor iOS  Android  MacOS  Windows 10 en internet. Vroege gebruikers krijgen gratis levenslange synchronisatie tussen onbeperkte apparaten.

Dit artikel is oorspronkelijk gepubliceerd op Op weg naar data science en opnieuw gepubliceerd naar TOPBOTS met toestemming van de auteur.

Geniet van dit artikel? Meld u aan voor meer AI-updates.

We laten het je weten wanneer we meer technisch onderwijs vrijgeven.

Bron: https://www.topbots.com/building-face-recognition-system-in-python/

Tijdstempel:

Meer van TOPBOTS