POZOR: Ak používate počítač, ktorého GPU nepodporuje prostredie CUDA, môžete na akceleráciu použiť Google Colab notebook s GPU podporou.
# Doinštalujeme balíčky, ktoré nie sú defaultne k dispozícii
import sys
!{sys.executable} -m pip install face_recognition google-images-download lapjv
# Import potrebných balíčkov
import os
import shutil
from sklearn.cluster import DBSCAN
from umap import UMAP
import matplotlib.pyplot as plt
import cv2
from lapjv import lapjv
from scipy.spatial.distance import cdist
#@title >> SKRYTÁ IMPLEMENTÁCIA RUTÍN << { display-mode: "form" }
import face_recognition
import cv2
import os
import glob
from imutils import build_montages
import matplotlib.pyplot as plt
import numpy as np
def get_image_filenames(
directory,
image_exts = ['.jpg', '.jpeg', '.png', '.gif'],
recursive = True
):
images = []
for fname in glob.glob(os.path.join(directory, "**/*"), recursive=recursive):
if os.path.isfile(fname) and os.path.splitext(fname)[-1] in image_exts:
images.append(fname)
return images
def enforce_maxres(image, max_resolution):
height, width = image.shape[:2]
if width > height:
if width > max_resolution:
width, height = max_resolution, max_resolution / width * height
image = cv2.resize(image, (int(width), int(height)))
else:
if height > max_resolution:
width, height = max_resolution / height * width, max_resolution
image = cv2.resize(image, (int(width), int(height)))
return image
def encode_image(image, imagePath,
detection_method="cnn",
max_resolution=1800):
"""
detection_method: "cnn" or "hog"
"""
image = enforce_maxres(image, max_resolution)
rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# detect the (x, y)-coordinates of the bounding boxes
# corresponding to each face in the input image
boxes = face_recognition.face_locations(rgb,
model=detection_method)
# compute the facial embedding for the face
encodings = face_recognition.face_encodings(rgb, boxes)
# build a dictionary of the image path, bounding box location,
# and facial encodings for the current image
d = [{"imagePath": imagePath, "loc": box,
"encoding": enc, 'shape': image.shape}
for (box, enc) in zip(boxes, encodings)]
return d
def encode_dataset(dataset_path, detection_method,
verbose=1, max_resolution=1800):
imagePaths = get_image_filenames(dataset_path)
data = []
for (i, imagePath) in enumerate(imagePaths):
# load the input image and convert it from RGB (OpenCV ordering)
# to dlib ordering (RGB)
if verbose:
print("Processing image {}/{}".format(i + 1,
len(imagePaths)))
print(imagePath)
image = cv2.imread(imagePath)
if image is None:
if verbose:
print("Skipping '{}'; the file cannot be opened.".format(
imagePath))
continue
d = encode_image(image, imagePath, detection_method=detection_method,
max_resolution=max_resolution)
data.extend(d)
if verbose:
print("Found the total of {} face boxes.".format(len(data)))
return data
def plot_clusters(data, clusts, labelIDs, verbose=1, figsize=(10, 8)):
# loop over the unique face integers
for labelID in labelIDs:
# find all indexes into the `data` array that belong to the
# current label ID, then randomly sample a maximum of 25 indexes
# from the set
if verbose:
print("Faces for face ID: {}".format(labelID))
idxs = np.where(clusts == labelID)[0]
idxs = np.random.choice(idxs, size=min(25, len(idxs)),
replace=False)
# initialize the list of faces to include in the montage
faces = []
# loop over the sampled indexes
for i in idxs:
# load the input image and extract the face ROI
image = cv2.imread(data[i]["imagePath"])
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
shape = data[i]["shape"]
image = cv2.resize(image, (shape[1], shape[0]))
(top, right, bottom, left) = data[i]["loc"]
face = image[top:bottom, left:right]
# force resize the face ROI to 96x96 and then add it to the
# faces montage list
face = cv2.resize(face, (96, 96))
faces.append(face)
# create a montage using 96x96 "tiles" with 5 rows and 5 columns
montage = build_montages(faces, (96, 96), (5, 5))[0]
# show the output montage
title = "Face ID #{}".format(labelID)
title = "Unknown Faces" if labelID == -1 else title
plt.figure(figsize=figsize)
plt.imshow(montage)
plt.title(title)
Definujeme zopár základných parametrov, ako sú cesta ku dátovej množine a pod.
# Cesta ku priečinku, v ktorom budú obrázky.
dataset_path = "dataset"
# Metóda na detekciu tvárí: "cnn" alebo "hog".
# CNN je presnejšia, ale HoG je trochu rýchlejšia, najmä
# ak sa nepoužíva GPU na akceleráciu.
detection_method = "cnn"
# Počet paralelných vlákien, ktoré sa použijú na zhlukovanie
# (-1 znamená použiť všetky CPU).
num_jobs = -1
Ďalej budeme potrebovať obrázky ľudských tvárí, na ktoré budeme aplikovať zhlukovanie. Použijeme preto balíček googleimagesdownload
pomocou ktorého si stiahneme niekoľko obrázkov zo služby Google Images – napríklad fotografie známych osobností.
Do zoznamu keywords
v nasledujúcej bunke zapíšte 5 alebo 6 mien osobností, ktoré sa použijú ako kľúčové slová pri vyhľadávaní fotografií.
keywords = [
# sem doplňte zoznam 5 alebo 6 mien
]
Pre každé z kľúčových slov teraz stiahneme niekoľko obrázkov a uložíme ich v priečinku downloads
.
for k in keywords:
!googleimagesdownload -k "$k" --limit=25
Ďalej prejdeme všetky novostiahnuté podpriečinky priečinka downloads
a súbory kopírujeme do priečinka dataset
, odkiaľ ich budeme následne čítať.
dataset_path = "dataset"
shutil.rmtree(dataset_path, ignore_errors=True)
os.makedirs(dataset_path)
for root, dirs, files in os.walk('downloads'):
for file in files:
src_file = os.path.join(root, file)
dst_file = os.path.join(dataset_path, file)
shutil.copy2(src_file, dst_file)
Keďže obrázky, ktoré v príklade používame, sú stiahnuté priamo z Google Images, budú obsahovať nielen ľudské tváre, ale aj celé osoby a ďalšie objekty. Musíme z nich preto nejakou metódou tváre extrahovať. Ak sme vyššie zvolili možnosť detection_method = "cnn", použije sa na to metóda založená na konvolučných neurónových sieťach.
V ďalšom kroku použijeme inú hlbokú sieť na transformáciu obrázkov tvárí do novej reprezentácie, ktorá lepšie vyjadruje podobnosti a rozdiely medzi ľudskými tvárami, než by to dokázala surová pixelová reprezentácia. Sieť je predtrénovaná na dátovej množine v s veľkým množstvom tvárí, a to podľa schémy na obrázku.
Každá tvár sa transformuje na 128-rozmerný vektor. Počas učenia sa sieti prezentujú trojice obrázkov. Dva z nich predstavujú dve odlišné fotografie tej istej osoby, zatiaľ čo tretí obrázok inej osoby. Úlohou siete je minimalizovať rozdiel medzi reprezentáciou fotografií toho isté človeka a maximalizovať rozdiel oproti fotografii inej osoby.
data = encode_dataset(dataset_path, detection_method)
Platí, že na extrahovaných obrázkoch sa môžu vyskytovať aj tváre iných osôb, než sme predpokladali (na pôvodných fotografiách mohli byť aj ďalší ľudia). Niektoré tváre môžu byť extrahované chybne, alebo sieť môže omylom namiesto tváre extrahovať inú časť fotografie. Uvidíme, ako sa s tým sieť extrahujúca 128-rozmerné reprezentácie vysporiada.
Následne z predspracovaných dát extrahujeme len vypočítané reprezentácie tvárí.
data = np.array(data)
encodings = [d["encoding"] for d in data]
Vykonajte zhlukovanie na poli encodings
, napr. pomocou metódy DBSCAN. Výsledné čísla zhlukov priraďte do premennej clusts.
# vykonajte zhlukovanie
clusts = # sem priraďte čísla zhlukov
Nakoniec vizualizujeme tváre patriace do jednotlivých zhlukov. Prvý obrázok predstavuje tváre, ktoré nepatria do žiadneho zhluku.
labelIDs = np.unique(clusts)
numUniqueFaces = len(np.where(labelIDs > -1)[0])
print("Počet rozličných tvárí: {}".format(numUniqueFaces))
print("Fotografie sme hľadali podľa {} rozličných kľúčových slov.".format(len(keywords)))
plot_clusters(data, clusts, labelIDs, verbose=0)
Použite metódu UMAP, aby ste znížili rozmer dát v poli encodings
zo 128 na 2 – aby sa dáta dali vizualizovať. Vhodným nastavením argumentov min_dist
a spread
zabezpečte, aby mal obrázok dobrú výpovednú hodnotu (t.j. všetky tváre sa nebudú navzájom úplne prekrývať). Výsledné dáta uložte do poľa s názvom embeds
.
# vykonajte znižovanie rozmeru dát
embeds = # sem priraďte dáta so zníženým rozmerom
Dáta zníženého rozmeru normalizujeme do rozsahu [0, 1].
embeds -= embeds.min(axis=0)
embeds /= embeds.max(axis=0)
Definujeme metódu plot_faces
, ktorá vykreslí príslušné tváre na zadaných pozíciách.
def plot_faces(data, poses, w=0.08, h=0.08, ax=None):
ax = plt.gca()
for i in range(len(data)):
try:
image = cv2.imread(data[i]['imagePath'])
shape = data[i]["shape"]
image = cv2.resize(image, (shape[1], shape[0]))
(top, right, bottom, left) = data[i]["loc"]
face = image[top:bottom, left:right]
face = cv2.resize(face, (96, 96))
face = cv2.cvtColor(face, cv2.COLOR_BGR2RGB)
pos = poses[i]
ax.imshow(face, extent=[pos[0] - w/2, pos[0] + w/2,
pos[1] - h/2, pos[1] + h/2])
except:
pass
plt.xlim([np.min(poses[:, 0]) - w, np.max(poses[:, 0]) + w])
plt.ylim([np.min(poses[:, 1]) - h, np.max(poses[:, 1]) + h])
Pomocou metódy plot_faces
vykreslíme tváre.
plt.figure(figsize=(10, 8))
plot_faces(data, embeds)
plt.xlabel('$d_1$')
plt.ylabel('$d_2$')
V rámci vizualizácie vytvorenej pomocou t-SNE vidno vzdialenosti medzi zhlukmi tvárí a podobne. Obrázky sa však navzájom prekrývajú, čo robí obrázok ťažko čitateľným. Preto pozície skúsime premietnuť do pravidelnej mriežky pomocou algoritmu Jonker-Volgenant.
sqrt_size = int(np.ceil(np.sqrt(len(embeds))))
size = sqrt_size * sqrt_size
grid = np.dstack(np.meshgrid(np.linspace(0, 1, sqrt_size), np.linspace(0, 1, sqrt_size))).reshape(-1, 2)
padded_embeds = np.zeros((size, embeds.shape[1]))
padded_embeds[:embeds.shape[0], :] = embeds
cost_matrix = cdist(grid, padded_embeds, "sqeuclidean").astype(np.float32)
cost_matrix = cost_matrix * (100000 / cost_matrix.max())
row_as, col_as, _ = lapjv(cost_matrix)
grid_jv = grid[col_as]
Nové pozície fotografií uložené v poli grid_jv
použijeme na vykreslenie.
plt.figure(figsize=(12, 12))
plot_faces(data, grid_jv)
plt.axis('off')