Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify face alignment procedure #863

Merged
merged 1 commit into from
Dec 21, 2023
Merged

Simplify face alignment procedure #863

merged 1 commit into from
Dec 21, 2023

Conversation

bhky
Copy link
Contributor

@bhky bhky commented Oct 18, 2023

Hello!

Currently, the face alignment procedure considers the eye positions and makes use of the Cosine Law to calculate the needed rotation angle. However, the angle can actually be calculated by a simple use of the np.arctan2 function.

In this PR, I proposed to vastly simplify the alignment_procedure function, while keeping the semantics of the function unchanged so that nothing will break, e.g., left-eye is the eye to the left of the viewer, as in the original code.

I have also prepared the following snippet, to illustrate that the angle calculated by the current method, and the newly proposed method, are the same. This snippet calculates the angle found by the two methods at different relative eye positions, and print out the angle values for comparison.

Please feel free to let me know if there are any concerns.
Thanks in advance for your consideration!

import math
import numpy as np


# --- Proposed simpler method


def get_alignment_angle_arctan2(left_eye, right_eye):
    """
    The left_eye is the eye to the left of the viewer,
    i.e., right eye of the person in the image.
    The top-left point of the frame is (0, 0).
    """
    return float(np.degrees(
        np.arctan2(right_eye[1] - left_eye[1], right_eye[0] - left_eye[0])
    ))


# --- Current method


def find_euclidean_distance(source_representation, test_representation):
    """
    https://github.com/serengil/deepface/blob/master/deepface/commons/distance.py#L11
    """
    if isinstance(source_representation, list):
        source_representation = np.array(source_representation)

    if isinstance(test_representation, list):
        test_representation = np.array(test_representation)

    euclidean_distance = source_representation - test_representation
    euclidean_distance = np.sum(np.multiply(euclidean_distance, euclidean_distance))
    euclidean_distance = np.sqrt(euclidean_distance)
    return euclidean_distance


def get_alignment_angle_cosine_law(left_eye, right_eye):
    """
    https://github.com/serengil/deepface/blob/master/deepface/detectors/FaceDetector.py#L85

    left eye is the eye appearing on the left (right eye of the person)
    left top point is (0, 0)
    """

    left_eye_x, left_eye_y = left_eye
    right_eye_x, right_eye_y = right_eye

    # -----------------------
    # find rotation direction

    if left_eye_y > right_eye_y:
        point_3rd = (right_eye_x, left_eye_y)
        direction = -1  # rotate same direction to clock
    else:
        point_3rd = (left_eye_x, right_eye_y)
        direction = 1  # rotate inverse direction of clock

    # -----------------------
    # find length of triangle edges

    a = find_euclidean_distance(np.array(left_eye), np.array(point_3rd))
    b = find_euclidean_distance(np.array(right_eye), np.array(point_3rd))
    c = find_euclidean_distance(np.array(right_eye), np.array(left_eye))

    # -----------------------

    # apply cosine rule

    if b != 0 and c != 0:  # this multiplication causes division by zero in cos_a calculation
        cos_a = (b * b + c * c - a * a) / (2 * b * c)
        angle = np.arccos(cos_a)  # angle in radian
        angle = (angle * 180) / math.pi  # radian to degree

        # -----------------------
        # rotate base image

        if direction == -1:
            angle = 90 - angle

    else:
        angle = 0

    img_rotate_angle = direction * angle

    return img_rotate_angle


def test():
    # Assume frame size is (10, 10).

    # Left-eye is to the left of the viewer,
    # i.e., right eye of the person in the image.

    # For simplicity, here we only change right-eye-y values to produce
    # different angles. While it's not exhaustive, it should be good enough
    # for illustration.
    right_eye_ys = np.arange(0, 10, 0.05)
    right_eye_xs = 7 * np.ones_like(right_eye_ys)
    left_eye_ys = 5 * np.ones_like(right_eye_ys)
    left_eye_xs = 3 * np.ones_like(right_eye_ys)

    left_eyes = zip(left_eye_xs, left_eye_ys)
    right_eyes = zip(right_eye_xs, right_eye_ys)

    for (left_eye, right_eye) in zip(left_eyes, right_eyes):

        angle_cosine_law = get_alignment_angle_cosine_law(left_eye, right_eye)
        angle_tangent = get_alignment_angle_arctan2(left_eye, right_eye)

        print(f"Angle cosine law: {angle_cosine_law}")
        print(f"Angle tangent: {angle_tangent}")
        print("-----------")


if __name__ == "__main__":
    test()

@bhky bhky changed the title Simply face alignment procedure Simplify face alignment procedure Oct 18, 2023
@serengil
Copy link
Owner

will test some edge cases before merging this, thank you for your contribution.

@bhky
Copy link
Contributor Author

bhky commented Dec 2, 2023

Hi @serengil !
Can I know the progress on this one?
Thanks again!

@serengil serengil merged commit bd6393d into serengil:master Dec 21, 2023
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants