Skip to content

Commit

Permalink
Merge pull request #85 from serengil/feat-task-2502-align-first-by-de…
Browse files Browse the repository at this point in the history
…fault

align first by default
  • Loading branch information
serengil authored Feb 25, 2024
2 parents 57a0d91 + 7908b50 commit 3a38aab
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 47 deletions.
13 changes: 13 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Tickets

https://github.com/serengil/retinaface/issues/XXX

### What has been done

With this PR, ...

## How to test

```shell
make lint && make test
```
42 changes: 22 additions & 20 deletions retinaface/RetinaFace.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ def extract_faces(
align: bool = True,
allow_upscaling: bool = True,
expand_face_area: int = 0,
align_first: bool = False,
) -> list:
"""
Extract detected and aligned faces
Expand All @@ -223,8 +222,6 @@ def extract_faces(
align (bool): enable or disable alignment
allow_upscaling (bool): allowing up-scaling
expand_face_area (int): expand detected facial area with a percentage
align_first (bool): set this True to align first and detect second
this can be applied only if input image has just one face
"""
resp = []

Expand All @@ -238,12 +235,6 @@ def extract_faces(
img_path=img, threshold=threshold, model=model, allow_upscaling=allow_upscaling
)

if align_first is True and len(obj) > 1:
logger.warn(
f"Even though align_first is set to True, there are {len(obj)} faces in input image."
"Align first functionality can be applied only if there is single face in the input"
)

if not isinstance(obj, dict):
return resp

Expand All @@ -263,10 +254,7 @@ def extract_faces(
x2 = min(img.shape[1], w + int((w * expand_face_area) / 100)) # expand right
y2 = min(img.shape[0], h + int((h * expand_face_area) / 100)) # expand bottom

if align_first is False or (align_first is True and len(obj) > 1):
facial_img = img[y1:y2, x1:x2]
else:
facial_img = img.copy()
facial_img = img[y1:y2, x1:x2]

if align is True:
landmarks = identity["landmarks"]
Expand All @@ -275,19 +263,33 @@ def extract_faces(
nose = landmarks["nose"]
# mouth_right = landmarks["mouth_right"]
# mouth_left = landmarks["mouth_left"]

# notice that left eye of one is seen on the right from your perspective
facial_img, rotate_angle, rotate_direction = postprocess.alignment_procedure(
facial_img, right_eye, left_eye, nose
img=img, left_eye=right_eye, right_eye=left_eye, nose=nose
)

if align_first is True and len(obj) == 1:
facial_area = postprocess.rotate_facial_area(
# find new facial area coordinates after alignment
projected_facial_area = postprocess.rotate_facial_area(
facial_area, rotate_angle, rotate_direction, img.shape
)
# Expand the facial area to be extracted and stay within img.shape limits
x1 = max(0, facial_area[0] - int((facial_area[2] * expand_face_area) / 100))
y1 = max(0, facial_area[1] - int((facial_area[3] * expand_face_area) / 100))
x2 = min(img.shape[1], facial_area[2] + int((facial_area[2] * expand_face_area) / 100))
y2 = min(img.shape[0], facial_area[3] + int((facial_area[3] * expand_face_area) / 100))
x1 = max(
0,
projected_facial_area[0] - int((projected_facial_area[2] * expand_face_area) / 100),
)
y1 = max(
0,
projected_facial_area[1] - int((projected_facial_area[3] * expand_face_area) / 100),
)
x2 = min(
img.shape[1],
projected_facial_area[2] + int((projected_facial_area[2] * expand_face_area) / 100),
)
y2 = min(
img.shape[0],
projected_facial_area[3] + int((projected_facial_area[3] * expand_face_area) / 100),
)
facial_img = facial_img[y1:y2, x1:x2]

resp.append(facial_img[:, :, ::-1])
Expand Down
2 changes: 1 addition & 1 deletion tests/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_alignment_for_clock_way():


def do_alignment_checks(img: np.ndarray, expected_faces: int) -> None:
faces = RetinaFace.extract_faces(img_path=img, align=True, expand_face_area=100)
faces = RetinaFace.extract_faces(img_path=img, align=True, expand_face_area=10)

# it has one clear face
assert len(faces) == expected_faces
Expand Down
35 changes: 9 additions & 26 deletions tests/test_align_first.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,15 @@

THRESHOLD = 1000


def test_detect_first():
"""
Test the default behavior. Detect first and align second causes
so many black pixels
"""
faces = RetinaFace.extract_faces(img_path="tests/dataset/img11.jpg")
num_black_pixels = np.sum(np.all(faces[0] == 0, axis=2))
assert num_black_pixels > THRESHOLD
logger.info("✅ Disabled align_first test for single face photo done")
VISUAL_TEST = False


def test_align_first():
"""
Test align first behavior. Align first and detect second do not cause
so many black pixels in contrast to default behavior
"""
faces = RetinaFace.extract_faces(img_path="tests/dataset/img11.jpg", align_first=True)
faces = RetinaFace.extract_faces(img_path="tests/dataset/img11.jpg")
num_black_pixels = np.sum(np.all(faces[0] == 0, axis=2))
assert num_black_pixels < THRESHOLD
logger.info("✅ Enabled align_first test for single face photo done")
Expand All @@ -34,22 +25,14 @@ def test_align_first_for_group_photo():
Align first will not work if the given image has many faces and
it will cause so many black pixels
"""
faces = RetinaFace.extract_faces(img_path="tests/dataset/couple.jpg", align_first=True)
for face in faces:
num_black_pixels = np.sum(np.all(face == 0, axis=2))
assert num_black_pixels > THRESHOLD

logger.info("✅ Enabled align_first test for group photo done")


def test_default_behavior_for_group_photo():
"""
Align first will not work in the default behaviour and
it will cause so many black pixels
"""
faces = RetinaFace.extract_faces(img_path="tests/dataset/couple.jpg")
for face in faces:
num_black_pixels = np.sum(np.all(face == 0, axis=2))
assert num_black_pixels > THRESHOLD
assert num_black_pixels < THRESHOLD
if VISUAL_TEST is True:
import matplotlib.pyplot as plt

logger.info("✅ Disabled align_first test for group photo done")
plt.imshow(face)
plt.show()

logger.info("✅ Enabled align_first test for group photo done")

0 comments on commit 3a38aab

Please sign in to comment.