779 KiB
W poniższych materiałach zaprezentujemy klasyczne metody rozpoznawania twarzy. Opisywane zagadnienia można odnaleźć w _5.2.3 Principal component analysis R. Szeliski (2022) Computer Vision: Algorithms and Applications oraz dokumentacji.
Na początku załadujmy niezbędne biblioteki.
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import sklearn.metrics
import ipywidgets
import os
import random
Rozpakujmy zbiór danych, na którym będziemy pracować:
!cd datasets && unzip -qo yaleextb.zip
'unzip' is not recognized as an internal or external command, operable program or batch file.
Nasz zbiór zawiera po kilkadziesiąt zdjęć kilkudziesięciu osób, które zostały sfotografowane w różnych warunkach oświetlenia. Wczytane zdjęcia podzielimy na zbiór treningowy i testowy w stosunku 3/1 oraz wyświetlimy kilka przykładowych zdjęć:
dataset_dir = "datasets/att_faces"
img_data = []
img_labels = []
images = os.listdir(dataset_dir)
n_examples = 15
for i in range(1, 41):
i_str = str(i).zfill(2)
images_p = [img for img in images if img.startswith(f"s{i_str}")]
for img in images_p[:n_examples]:
img_data.append(cv.imread(f"{dataset_dir}/{img}", cv.IMREAD_GRAYSCALE))
img_labels.append(i)
random.seed(1337)
selector = random.choices([False, True], k=len(images), weights=[3, 1])
train_data = [x for x, y in zip(img_data, selector) if not y]
train_labels = [x for x, y in zip(img_labels, selector) if not y]
test_data = [x for x, y in zip(img_data, selector) if y]
test_labels = [x for x, y in zip(img_labels, selector) if y]
plt.figure(figsize=(12,5))
for i in range(4):
plt.subplot(251 + i)
plt.imshow(train_data[i], cmap='gray');
for i in range(4):
plt.subplot(256 + i)
plt.imshow(train_data[-i-20], cmap='gray');
Pierwszym modelem jest _Eigenfaces zaimplementowany w EigenFaceRecognizer
. Główny pomysł polega na użyciu PCA do redukcji wymiarów. W naszym przykładzie zachowamy 60 wektorów własnych.
model = cv.face.EigenFaceRecognizer_create(60)
model.train(np.array(train_data), np.array(train_labels))
Zachowane wektory własne możemy zwizualizować:
img_shape = train_data[0].shape
plt.figure(figsize=(12,5))
for i in range(5):
e_v = model.getEigenVectors()[:,i]
e_v = np.reshape(e_v, img_shape)
plt.subplot(151+i)
plt.imshow(e_v, cmap='gray');
Możemy zobaczyć jakie potencjalne twarze znajdują się w naszej przestrzeni. Do _uśrednionej twarzy dodajemy kolejne wektory własne z odpowiednimi wagami. Poniżej mamy przykład wykorzystujący 6 wektorów:
mean = model.getMean()
W = model.getEigenVectors()
def generate_face(**args):
img = mean.copy()
for i, k in enumerate(args.keys()):
img = np.add(img, W[:,i]*(10*args[k]))
img = np.reshape(img, img_shape)
plt.figure(figsize=(5,5))
plt.imshow(img, cmap='gray')
plt.show()
ipywidgets.interactive(generate_face,
w_0=ipywidgets.IntSlider(min=-128, max=128),
w_1=ipywidgets.IntSlider(min=-128, max=128),
w_2=ipywidgets.IntSlider(min=-128, max=128),
w_3=ipywidgets.IntSlider(min=-128, max=128),
w_4=ipywidgets.IntSlider(min=-128, max=128),
w_5=ipywidgets.IntSlider(min=-128, max=128))
interactive(children=(IntSlider(value=0, description='w_0', max=128, min=-128), IntSlider(value=0, description…
Możemy teraz spróbować zrobić rekonstrukcję np. pierwszej twarzy ze zbioru treningowego. Pobieramy dla niej projekcje (wagi) z naszego modelu i podobnie jak wyżej wykorzystujemy uśrednioną twarz i wektory własne. Możemy zobaczyć, że użycie większej liczby wektorów powoduje zwiększenie precyzji rekonstrukcji:
pro = model.getProjections()[0]
def reconstruct_face(k):
img = mean.copy()
for i in range(k):
img = np.add(img, W[:,i]*pro[0,i])
return img
plt.figure(figsize=(12,6))
for i in range(6):
k = (i+1)*10
r_face = np.reshape(reconstruct_face(k), img_shape)
j = 0 if i <= 4 else 10
plt.subplot(151+i+100)
plt.imshow(r_face, cmap='gray')
plt.title(f"k = {k}")
plt.subplot(257)
plt.imshow(train_data[0], cmap='gray');
plt.title("original");
Spróbujmy teraz odnaleźć osobny znajdujące się na dwóch przykładowych obrazach ze zbioru testowego. Dla nieznanej twarzy obliczamy projekcje i szukamy metodą najbliższego sąsiada projekcji ze zbioru treningowego. Poniżej mamy przykład z poprawnym rozpoznaniem osoby oraz z niepoprawnym rozpoznaniem:
def find_face(query_id):
query_face = test_data[query_id]
query_label = test_labels[query_id]
x = np.reshape(query_face, mean.shape)
x_coeff = np.dot(x - mean, W)
best_face = None
best_label = None
best_dist = float('inf')
for i, p in enumerate(model.getProjections()):
dist = np.linalg.norm(np.reshape(p, 60) - np.reshape(x_coeff, 60))
if dist < best_dist:
best_face = train_data[i]
best_label = train_labels[i]
best_dist = dist
return query_face, query_label, best_face, best_label
qf_1, ql_1, bf_1, bl_1 = find_face(45)
qf_2, ql_2, bf_2, bl_2 = find_face(10)
plt.figure(figsize=(8,11))
plt.subplot(221)
plt.imshow(qf_1, cmap='gray')
plt.title(f"Face 1: query label = {ql_1}")
plt.subplot(222)
plt.imshow(bf_1, cmap='gray');
plt.title(f"Face 1: best label = {bl_1}")
plt.subplot(223)
plt.imshow(qf_2, cmap='gray')
plt.title(f"Face 2: query label = {ql_2}")
plt.subplot(224)
plt.imshow(bf_2, cmap='gray');
plt.title(f"Face 2: best label = {bl_2}");
Bardziej kompaktowe wykonanie predykcji możemy uzyskać poprzez metodę predict()
:
print(test_labels[45], model.predict(test_data[45])[0])
print(test_labels[10], model.predict(test_data[10])[0])
18 18 3 3
Jak widać poniżej, metoda ta nie uzyskuje szczególnie zadowalających wyników (generalnie słabo sobie radzi w sytuacji zmian oświetlenia):
predictions = []
for test_img in test_data:
p_label, p_conf = model.predict(test_img)
predictions.append(p_label)
print(f"Accuracy: {sklearn.metrics.accuracy_score(test_labels, predictions) * 100:.2f} %")
Accuracy: 98.96 %
Poniżej krótko zaprezentujemy jeszcze dwa rozwinięcia tego algorytmu. Pierwszym z nich jest _Fisherfaces zaimplementowany w FisherFaceRecognizer
. Tym razem przy pomocy LDA chcemy dodatkowo uwzględnić rozrzut pomiędzy klasami (por. przykład). Poniżej tworzymy model z 40 komponentami:
model = cv.face.FisherFaceRecognizer_create(40)
model.train(np.array(train_data), np.array(train_labels))
Zauważmy, że uzyskujemy tutaj ponad dwukrotnie lepszy wynik:
predictions = []
for test_img in test_data:
p_label, p_conf = model.predict(test_img)
predictions.append(p_label)
print(f"Accuracy: {sklearn.metrics.accuracy_score(test_labels, predictions) * 100:.2f} %")
Accuracy: 97.92 %
Dalszym rozwinięciem jest model _Local Binary Patterns Histograms (LBPH) zaimplementowany w LBPHFaceRecognizer
. W tym wypadku chcemy np. uwzględnić możliwość innego oświetlenia osób niż taki, który występuje w naszym zbiorze treningowym. Podobnie jak wcześniej zależy nam na redukcji wymiarów, ale tym razem uzyskamy to poprzez wyliczanie cech (progowanie) dla poszczególnych pikseli w zadanych regionach.
model = cv.face.LBPHFaceRecognizer_create(radius=10, neighbors=10, grid_x=32, grid_y=32)
model.train(np.array(train_data), np.array(train_labels))
Uzyskany wynik jest o kilka punktów procentowy lepszy od poprzedniego modelu, jednak możemy zauważyć, że zmiana domyślnych parametrów na takie, które zwiększają precyzję, powoduje również zwiększenie czasu potrzebnego na wykonanie predykcji:
predictions = []
for test_img in test_data:
p_label, p_conf = model.predict(test_img)
predictions.append(p_label)
print(f"Accuracy: {sklearn.metrics.accuracy_score(test_labels, predictions) * 100:.2f} %")
Accuracy: 86.46 %
Zadanie 1
W katalogu datasets
znajduje się zbiór zdjęć att_faces
. Sprawdź jakiego typu są to zdjęcia oraz jak powyższe algorytmy działają na tym zbiorze.