Skip to content

Face Similarity Search

See the source code Open In Colab

This example shows how to use mozuma to extract face embeddings from a collection of images. Using these face embeddings, this example shows how to compute face similarity.

Let's get started by importing mozuma modules.

from mozuma.torch.options import TorchRunnerOptions
from mozuma.torch.runners import TorchInferenceRunner
from mozuma.callbacks.memory import (
    CollectBoundingBoxesInMemory,
    CollectFeaturesInMemory,
)
from mozuma.torch.datasets import (
    LocalBinaryFilesDataset,
    ImageDataset,
    ImageBoundingBoxDataset,
)
from mozuma.helpers.files import list_files_in_dir

from mozuma.models.mtcnn.pretrained import torch_mtcnn
from mozuma.models.arcface.pretrained import torch_arcface_insightface

import torch
import matplotlib.pyplot as plt
import numpy as np
from facenet_pytorch.models.utils.detect_face import crop_resize
from PIL import Image
import seaborn as sns

sns.set(style="white")
%matplotlib inline

Load images

base_path = "../../tests/fixtures/berset"
file_names = list_files_in_dir(base_path, allowed_extensions=("jpg",))
# Load image dataset
berset_dataset = ImageDataset(LocalBinaryFilesDataset(file_names))

Run face detection with torch_mtcnn

torch_device = torch.device("cpu")
face_detector = torch_mtcnn(device=torch_device)

# Callbacks
bb = CollectBoundingBoxesInMemory()

# Runner
runner = TorchInferenceRunner(
    model=face_detector,
    dataset=berset_dataset,
    callbacks=[bb],
    options=TorchRunnerOptions(
        data_loader_options={"batch_size": 1}, device=torch_device, tqdm_enabled=True
    ),
)
runner.run()

Extract face features with TorchArcFaceModule

arcface = torch_arcface_insightface(device=torch_device)

# Dataset
dataset = ImageBoundingBoxDataset(
    image_dataset=ImageDataset(LocalBinaryFilesDataset(bb.indices)),
    bounding_boxes=bb.bounding_boxes,
)

# Callbacks
ff = CollectFeaturesInMemory()

# Runner
runner = TorchInferenceRunner(
    model=arcface,
    dataset=dataset,
    callbacks=[ff],
    options=TorchRunnerOptions(
        data_loader_options={"batch_size": 3}, device=torch_device, tqdm_enabled=True
    ),
)
runner.run()

Display the cropped faces

def image_grid(array, ncols=10):
    index, height, width, channels = array.shape
    nrows = index // ncols
    img_grid = (
        array.reshape(nrows, ncols, height, width, channels)
        .swapaxes(1, 2)
        .reshape(height * nrows, width * ncols, channels)
    )

    return img_grid


img_arr = []
for k, file_name in enumerate(bb.indices):
    img = Image.open(file_name).convert("RGB")
    bboxes = bb.bounding_boxes[k]
    for box in bboxes.bounding_boxes:
        cropped_face = np.asarray(crop_resize(img, box, image_size=112))
        img_arr.append(cropped_face)

result = image_grid(np.array(img_arr), ncols=len(img_arr))
fig = plt.figure(figsize=(20.0, 20.0))
plt.imshow(result)

Compute face similarity

sim_mat = ff.features @ ff.features.T
fig, ax = plt.subplots(figsize=(8, 6))
ax = sns.heatmap(sim_mat, cmap="PuRd", annot=True)

From this heatmap, we can see that the cosine similarities between the three faces of Alain Berset is quite high (from 0.7 to 0.74) while they are very low between all other faces.