Skip to content

Commit

Permalink
Merge pull request #33 from Face-Tagger/release
Browse files Browse the repository at this point in the history
Release Face Tagger v1.0.0
  • Loading branch information
ByteAurora authored Sep 7, 2023
2 parents 28d0974 + b377268 commit 2ba4cc8
Show file tree
Hide file tree
Showing 26 changed files with 584 additions and 17 deletions.
1 change: 1 addition & 0 deletions .github/workflows/lib-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:

- name: Install dependencies
run:
pip install --upgrade pip
pip install build --user

- name: Build a binary wheel and a source tarball
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/lib-e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2

- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.11'

- name: Install dependencies
run: |
pip install --upgrade pip
pip install pytest
pip install -r requirements.txt
- name: Run tests
- name: Run e2e tests
run: |
pytest
pytest tests/face_tagger_e2e_test.py
11 changes: 9 additions & 2 deletions .github/workflows/lib-unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2

- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.11'

- name: Install dependencies
run: |
pip install --upgrade pip
pip install pytest
pip install -r requirements.txt
- name: Run tests
- name: Run unit tests
run: |
pytest
pytest tests/face_tagger_unit_test.py
97 changes: 93 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Face Tagger is a Python library designed to classify photos containing specific

- **Face Recognition**: Identify and differentiate faces in multiple images.
- **Photo Classification**: Group photos based on the identified individual.
- **GPU Acceleration**: Optimized performance using GPU, if available.
- **Easy Integration**: Simple APIs for seamless integration into various applications.

<br>

Expand All @@ -21,21 +23,108 @@ pip install face-tagger

<br>

## Dependencies

Face Tagger relies on several key libraries to function effectively. Understanding these dependencies can provide insights into the library's internal workings and can be beneficial for troubleshooting. Here are the primary dependencies:

- **OpenCV (cv2)**: Used for image processing tasks like reading and preprocessing images.
- **Torch (torch)**: The primary deep learning framework employed for face embedding.
- **MTCNN**: Essential for the detection of faces within images.

Make sure to have these dependencies properly installed or ensure they are present in your environment when working with Face Tagger. For a comprehensive list and exact versions, please refer to the `requirements.txt` file.

<br>

## How it Works

Face Tagger follows a systematic process to classify images:

1. **Image Loading**
- Uses standard Python libraries, typically OpenCV (`cv2`), to read and preprocess images.

2. **Face Detection**
- Utilizes the MTCNN (Multi-task Cascaded Convolutional Networks) library for detecting faces in images.

3. **Embedding Computation**
- Employs the FaceNet model, often via the `torch` library, to compute a vector (embedding) for each
detected face.

4. **Face Classification**
- Uses a HDBSCAN clustering algorithm to group similar face embeddings together.

5. **Result Compilation**
- Once the faces are classified, the result is compiled and presented in a structured format that informs which
images contain which individual(s).

<br>

## Usage

To utilize the Face Tagger library, follow these steps:

1. **Define an image generator**:
The generator function allows you to loop through all images in a specified directory and create `ImageObject`
instances for each one.

```python
# Sample Code
def image_generator(image_dir):
for filename in os.listdir(image_dir):
img = cv2.imread(os.path.join(image_dir, filename))
if img is not None:
yield ImageObject(filename, img)
```

(Note: The above code is just a fictional example and may not represent the actual usage of the Face Tagger library.)
(Note: You don't necessarily need to use a generator for image_objects. You can also create ImageObject list from loaded
images in an array and pass them.)

Here is the representation of the `ImageObject` class:

> #### Attributes:
> - **image_id**: A unique identifier for the image.
> - **image_data**: Actual image data, loaded using OpenCV.
>
> #### Example:
> ```python
> img_obj = ImageObject(image_id="unique_id_123", image_data=loaded_image_data)
> ```
2. **Initialize the Face Tagger**:
Here, you can set various parameters like `use_gpu`, `image_resize_factor`, `min_faces_to_be_group`,
and `min_similarity_face_count`.
```python
face_tagger = FaceTagger(
use_gpu=False,
image_resize_factor=1.0,
min_faces_to_be_group=2,
min_similarity_face_count=1
)
```
3. **Classify images by person**:
Now, use the `classify_images_by_person` method of the `face_tagger` instance and pass the `image_objects` from the
generator function to classify the images.

```python
result = face_tagger.classify_images_by_person(image_objects=image_generator("path_to_images_directory"))
print(result)
```

Replace `path_to_images_directory` with the path to your image directory.

---

With these steps, you can effectively utilize the Face Tagger library to classify images based on the individuals they
contain.

<br>

## Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Pull requests are welcome. For major changes, please open an [issue]('https://github.com/Face-Tagger/facetagger-lib/issues/new') first to discuss what you would like to change.

<br>

## License

[MIT](https://choosealicense.com/licenses/mit/)
[MIT](https://github.com/Face-Tagger/facetagger-lib/blob/main/LICENSE)
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "face_tagger"
version = "0.0.6"
version = "1.0.0"
authors = [
{ name="Bae SungJoon", email="[email protected]" },
{ name="Yoon JongBeom", email="[email protected]" },
Expand All @@ -13,7 +13,7 @@ authors = [
]
description = "A library for face recognition in multiple images and classification of photos containing each individual."
readme = "README.md"
requires-python = ">=3.7"
requires-python = ">=3.11"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
Expand Down
Binary file modified requirements.txt
Binary file not shown.
20 changes: 20 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from setuptools import setup, find_packages

with open('requirements.txt') as f:
required = f.read().splitlines()

setup(
name="face_tagger",
version="1.0.0",
description="Python library designed to classify photos containing specific individuals from a collection of "
"images.",
author="Sohn YoungJin",
author_email="[email protected]",
packages=find_packages(),
install_requires=required,
classifiers=[
"Programming Language :: Python :: 3.11",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
)
5 changes: 5 additions & 0 deletions src/face_tagger/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .face_tagger import FaceTagger
from .classifier import Classifier
from .detector import Detector
from .embedder import Embedder
from .utils import resize_image, convert_bgr_to_rgb, bytes_to_cvimage
50 changes: 50 additions & 0 deletions src/face_tagger/classifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import hdbscan
import torch


class Classifier:
"""
Class for face classification.
"""

def __init__(self, min_faces_to_be_group=3, min_similarity_face_count=2):
"""
Constructor of Classifier class.
:param min_faces_to_be_group: Minimum number of faces required to be classified as a group.
:param min_similarity_Face_count: Minimum number of similar faces required to be included in a group.
"""

self.min_faces_to_be_group = min_faces_to_be_group
self.min_similarity_face_count = min_similarity_face_count

def convert_to_numpy(self, face_embeddings):
"""
Convert embeddings to numpy format.
:param face_embeddings: Embeddings to convert.
:return: Numpy format embeddings.
"""

return torch.stack(face_embeddings).view(len(face_embeddings), -1).detach().numpy()

def cluster_embeddings(self, embeddings_np):
"""
Cluster embeddings using HDBSCAN.
:param embeddings_np: Numpy format embeddings to cluster.
:return: Cluster labels.
"""

hdbscan_cluster = hdbscan.HDBSCAN(min_samples=self.min_similarity_face_count,
min_cluster_size=self.min_faces_to_be_group).fit(embeddings_np)

return hdbscan_cluster.labels_

def classify_faces(self, face_embeddings):
"""
Classify faces using clustering.
:param face_embeddings: Embeddings to classify.
:return: Cluster labels.
"""

embeddings_np = self.convert_to_numpy(face_embeddings)

return self.cluster_embeddings(embeddings_np)
47 changes: 47 additions & 0 deletions src/face_tagger/detector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from mtcnn import MTCNN


class Detector:
"""
Class for face detection.
"""

def __init__(self):
"""
Constructor of Detector class.
"""

self.face_detector = MTCNN()

def detect_face(self, image):
"""
Detects faces in an image.
:param image: Image to detect faces.
:return: Detected faces with their bounding boxes.
"""

# Face detection using MTCNN.
return self.face_detector.detect_faces(image)

def crop(self, image, face):
"""
Crops the face area from the image.
:param image: Image from which to crop the face.
:param face: Detected face with bounding box.
:return: Cropped face image.
"""

x, y, width, height = face['box']

return image[y:y + height, x:x + width]

def detect_face_and_crop(self, image):
"""
Detects faces in an image and returns cropped face images.
:param image: Image to detect faces.
:return: List of cropped face images.
"""

faces = self.detect_face(image)

return [self.crop(image, face) for face in faces]
45 changes: 45 additions & 0 deletions src/face_tagger/embedder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import torch
import torchvision.transforms as transforms
from facenet_pytorch import InceptionResnetV1
from .utils import resize_image, convert_bgr_to_rgb


class Embedder:
"""
Class for face embedding.
"""

def __init__(self, use_gpu=False):
"""
Constructor of Embedder class.
:param use_gpu: Use GPU or not.
"""

self.device = torch.device('cuda' if use_gpu and torch.cuda.is_available() else 'cpu')
self.resnet = InceptionResnetV1(pretrained='vggface2').eval().to(self.device)
self.transform = transforms.ToTensor()

def prepare_image_tensor(self, face_image):
"""
Convert the image to tensor format and add batch dimension.
:param face_image: An image to process.
:return: Processed image tensor.
"""

return self.transform(face_image).unsqueeze(0).to(self.device)

def compute_embeddings(self, face_image):
"""
Compute embeddings of an image.
:param face_image: An image to compute embeddings.
:return: Embeddings of an image.
"""

face_image = resize_image(face_image, 160, 160)
face_image = convert_bgr_to_rgb(face_image)
img_tensor = self.prepare_image_tensor(face_image)

# Compute embeddings.
img_embedding = self.resnet(img_tensor)

return img_embedding.cpu()
Loading

0 comments on commit 2ba4cc8

Please sign in to comment.