Created
April 14, 2023 19:02
-
-
Save yosifonoren/b2dc4c31aec6c94a778ad20e85e0ea71 to your computer and use it in GitHub Desktop.
face-gallery-maker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"id": "28978799", | |
"metadata": {}, | |
"source": [ | |
"# Face Gallery Creator\n", | |
"\n", | |
"Face Gallery Creator is a Python program designed to generate a gallery of images from a folder containing multiple images of different people. The program uses face recognition techniques to group images of the same person together, displaying one thumbnail per person along with a list of image filenames corresponding to that person.\n", | |
"\n", | |
"The code is provided as a Jupyter Notebook, making it easy to run and visualize the results.\n", | |
"\n", | |
"## Features\n", | |
"\n", | |
"- Extract face vectors from images using the face_recognition library\n", | |
"- Cluster face vectors using the DBSCAN clustering algorithm from scikit-learn\n", | |
"- Display a gallery in table format with one thumbnail per person and a list of image filenames\n", | |
"- Cache face vectors to disk using Pickle for faster subsequent runs\n", | |
"\n", | |
"## Installation\n", | |
"\n", | |
"1. You should have `git` installed on your computer as well as `python` 3.x .\n", | |
"\n", | |
"2. Clone this repository to your local machine.\n", | |
"```bash\n", | |
"git clone https://github.com/yourusername/face-gallery-creator.git\n", | |
"```\n", | |
"\n", | |
"3. create a virtual environemnt and activate it\n", | |
"```bash\n", | |
"python3 -m venv .venv\n", | |
"source .venv/bin/activate\n", | |
"```\n", | |
"\n", | |
"4. install dependencies\n", | |
"```bash\n", | |
"pip install -r requirements.txt\n", | |
"```\n", | |
"\n", | |
"## Running the notebok\n", | |
"1. create a subfolder under the current folder call images, and copy your images there, make sure they're all in .jpg format\n", | |
"\n", | |
"2. if your files are really large, this may slow down the script considerably, you can resize the files in-place using imagemagick like so:\n", | |
"\n", | |
"```bash\n", | |
"magick mogrify -resize '800' *.jpg\n", | |
"```\n", | |
"\n", | |
"if imagemagick is not installed you can install it via brew (mac), apt/yum linux, or choco (windows)\n", | |
"\n", | |
"3. run jupyter notebook server\n", | |
"```bash\n", | |
"jupyter notebook\n", | |
"```\n", | |
"\n", | |
"4. run all the cells and have fun.\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"id": "64f138fa", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import os\n", | |
"import cv2\n", | |
"import dlib\n", | |
"import pickle\n", | |
"import face_recognition\n", | |
"from PIL import Image, ImageDraw\n", | |
"from IPython.display import display\n", | |
"from sklearn.cluster import DBSCAN\n", | |
"import numpy as np\n", | |
"from tqdm import tqdm" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"id": "c50acc4c", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"\n", | |
"\n", | |
"# Function to extract face vector and save it in the local vector store\n", | |
"def extract_face_vectors(image_path):\n", | |
" image = face_recognition.load_image_file(image_path)\n", | |
" face_encodings = face_recognition.face_encodings(image)\n", | |
" \n", | |
" if len(face_encodings) > 0:\n", | |
" return face_encodings[0]\n", | |
" else:\n", | |
" return None\n", | |
"\n", | |
"# Function to create face vectors\n", | |
"def create_face_vectors(folder_path):\n", | |
" vectors = {}\n", | |
" for filename in tqdm(os.listdir(folder_path), desc=\"Extracting face vectors\"):\n", | |
" if filename.endswith(\".jpg\"):\n", | |
" filepath = os.path.join(folder_path, filename)\n", | |
" face_vector = extract_face_vectors(filepath)\n", | |
" if face_vector is not None:\n", | |
" vectors[filename] = face_vector\n", | |
" return vectors\n", | |
"\n", | |
"# Function to save face vectors\n", | |
"def save_face_vectors(vectors, output_file):\n", | |
" with open(output_file, 'wb') as f:\n", | |
" pickle.dump(vectors, f)\n", | |
"\n", | |
"# Function to load face vectors\n", | |
"def load_face_vectors(input_file):\n", | |
" with open(input_file, 'rb') as f:\n", | |
" vectors = pickle.load(f)\n", | |
" return vectors\n", | |
"\n", | |
"# Function to cluster face vectors\n", | |
"def cluster_face_vectors(vectors):\n", | |
" face_vectors = list(vectors.values())\n", | |
" \n", | |
" if not face_vectors:\n", | |
" return []\n", | |
" \n", | |
" clustering_model = DBSCAN(eps=0.5, min_samples=2, metric='euclidean')\n", | |
" labels = clustering_model.fit_predict(face_vectors)\n", | |
" \n", | |
" return labels\n", | |
"\n", | |
"# Function to create clusters\n", | |
"def create_clusters(vectors):\n", | |
" labels = cluster_face_vectors(vectors)\n", | |
" clusters = {}\n", | |
" \n", | |
" for i, label in enumerate(labels):\n", | |
" if label not in clusters:\n", | |
" clusters[label] = []\n", | |
" clusters[label].append(list(vectors.keys())[i])\n", | |
"\n", | |
" return clusters\n", | |
"\n", | |
"# Function to display the gallery\n", | |
"def display_gallery(folder_path, clusters):\n", | |
" print(\"Gallery:\")\n", | |
" for label, filenames in clusters.items():\n", | |
" thumbnail_path = os.path.join(folder_path, filenames[0])\n", | |
" thumbnail = Image.open(thumbnail_path)\n", | |
" thumbnail.thumbnail((150, 150))\n", | |
" display(thumbnail)\n", | |
" print(\"Image list for person {}:\".format(label))\n", | |
" for filename in filenames:\n", | |
" print(filename)\n", | |
"\n", | |
"# Main function\n", | |
"def make_gallery(folder_path):\n", | |
" vectors_file = \"face_vectors.pkl\"\n", | |
" \n", | |
" if not os.path.isfile(vectors_file):\n", | |
" vectors = create_face_vectors(folder_path)\n", | |
" save_face_vectors(vectors, vectors_file)\n", | |
" else:\n", | |
" vectors = load_face_vectors(vectors_file)\n", | |
" \n", | |
" clusters = create_clusters(vectors)\n", | |
" display_gallery(folder_path, clusters)\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"id": "f9371ae8", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"make_gallery(\"./images\")\n" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3 (ipykernel)", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.9.16" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 5 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
anyio==3.6.2 | |
appnope==0.1.3 | |
argon2-cffi==21.3.0 | |
argon2-cffi-bindings==21.2.0 | |
arrow==1.2.3 | |
asttokens==2.2.1 | |
attrs==22.2.0 | |
backcall==0.2.0 | |
beautifulsoup4==4.12.2 | |
bleach==6.0.0 | |
cffi==1.15.1 | |
click==8.1.3 | |
comm==0.1.3 | |
debugpy==1.6.7 | |
decorator==5.1.1 | |
defusedxml==0.7.1 | |
dlib==19.24.1 | |
executing==1.2.0 | |
face-recognition==1.3.0 | |
face-recognition-models==0.3.0 | |
fastjsonschema==2.16.3 | |
fqdn==1.5.1 | |
idna==3.4 | |
importlib-metadata==6.3.0 | |
ipykernel==6.22.0 | |
ipython==8.12.0 | |
ipython-genutils==0.2.0 | |
ipywidgets==8.0.6 | |
isoduration==20.11.0 | |
jedi==0.18.2 | |
Jinja2==3.1.2 | |
joblib==1.2.0 | |
jsonpointer==2.3 | |
jsonschema==4.17.3 | |
jupyter==1.0.0 | |
jupyter-console==6.6.3 | |
jupyter-events==0.6.3 | |
jupyter_client==8.1.0 | |
jupyter_core==5.3.0 | |
jupyter_server==2.5.0 | |
jupyter_server_terminals==0.4.4 | |
jupyterlab-pygments==0.2.2 | |
jupyterlab-widgets==3.0.7 | |
MarkupSafe==2.1.2 | |
matplotlib-inline==0.1.6 | |
mistune==2.0.5 | |
nbclassic==0.5.5 | |
nbclient==0.7.3 | |
nbconvert==7.3.1 | |
nbformat==5.8.0 | |
nest-asyncio==1.5.6 | |
notebook==6.5.4 | |
notebook_shim==0.2.2 | |
numpy==1.24.2 | |
opencv-python-headless==4.7.0.72 | |
packaging==23.1 | |
pandocfilters==1.5.0 | |
parso==0.8.3 | |
pexpect==4.8.0 | |
pickleshare==0.7.5 | |
Pillow==9.5.0 | |
platformdirs==3.2.0 | |
prometheus-client==0.16.0 | |
prompt-toolkit==3.0.38 | |
psutil==5.9.4 | |
ptyprocess==0.7.0 | |
pure-eval==0.2.2 | |
pycparser==2.21 | |
Pygments==2.15.0 | |
pyrsistent==0.19.3 | |
python-dateutil==2.8.2 | |
python-json-logger==2.0.7 | |
PyYAML==6.0 | |
pyzmq==25.0.2 | |
qtconsole==5.4.2 | |
QtPy==2.3.1 | |
rfc3339-validator==0.1.4 | |
rfc3986-validator==0.1.1 | |
scikit-learn==1.2.2 | |
scipy==1.10.1 | |
Send2Trash==1.8.0 | |
six==1.16.0 | |
sniffio==1.3.0 | |
soupsieve==2.4 | |
stack-data==0.6.2 | |
terminado==0.17.1 | |
threadpoolctl==3.1.0 | |
tinycss2==1.2.1 | |
tornado==6.2 | |
tqdm==4.65.0 | |
traitlets==5.9.0 | |
typing_extensions==4.5.0 | |
uri-template==1.2.0 | |
wcwidth==0.2.6 | |
webcolors==1.13 | |
webencodings==0.5.1 | |
websocket-client==1.5.1 | |
widgetsnbextension==4.0.7 | |
zipp==3.15.0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment