feat: draft avatar, need homography
This commit is contained in:
parent
6b751757dd
commit
51a42e0b4b
39
src/bodyparts/body.py
Normal file
39
src/bodyparts/body.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pygame as pg
|
||||
|
||||
from utils import landmark2vec
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from environment import Environment
|
||||
|
||||
LANDMARKS = [152]
|
||||
|
||||
|
||||
class Body:
|
||||
def __init__(self, env: Environment) -> None:
|
||||
self.env = env
|
||||
self.texture = pg.image.load("assets/body.png").convert_alpha()
|
||||
|
||||
def draw(self, screen) -> None:
|
||||
|
||||
# compute position
|
||||
if self.env.results.multi_face_landmarks:
|
||||
for face_landmarks in self.env.results.multi_face_landmarks:
|
||||
|
||||
body_pos = landmark2vec(face_landmarks.landmark[LANDMARKS[0]], screen)
|
||||
|
||||
texture_scaled = pg.transform.scale(
|
||||
self.texture,
|
||||
(300, 200),
|
||||
)
|
||||
|
||||
texture_pos = body_pos - pg.Vector2(
|
||||
150,
|
||||
25,
|
||||
)
|
||||
|
||||
# draw texture
|
||||
screen.blit(texture_scaled, texture_pos)
|
46
src/bodyparts/crown.py
Normal file
46
src/bodyparts/crown.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pygame as pg
|
||||
|
||||
from utils import landmark2vec
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from environment import Environment
|
||||
|
||||
LANDMARKS = [10]
|
||||
|
||||
|
||||
class Crown:
|
||||
def __init__(self, env: Environment) -> None:
|
||||
self.env = env
|
||||
self.texture = pg.image.load("assets/crown.png").convert_alpha()
|
||||
|
||||
def draw(self, screen) -> None:
|
||||
|
||||
# compute position
|
||||
if self.env.results.multi_face_landmarks:
|
||||
for face_landmarks in self.env.results.multi_face_landmarks:
|
||||
|
||||
# head_top = landmark2vec(face_landmarks.landmark[LANDMARKS[1]], screen)
|
||||
# head_bottom = landmark2vec(face_landmarks.landmark[LANDMARKS[3]], screen)
|
||||
|
||||
# angle = (head_bottom - head_top).angle_to(pg.Vector2(0, 1))
|
||||
# height = (head_bottom - head_top).length()
|
||||
# ratio = self.texture.get_width() / self.texture.get_height()
|
||||
|
||||
crown_pos = landmark2vec(face_landmarks.landmark[LANDMARKS[0]], screen)
|
||||
|
||||
texture_scaled = pg.transform.scale(
|
||||
self.texture,
|
||||
(100, 100),
|
||||
)
|
||||
|
||||
texture_pos = crown_pos - pg.Vector2(
|
||||
texture_scaled.get_width() / 2,
|
||||
texture_scaled.get_height() / 2,
|
||||
)
|
||||
|
||||
# draw texture
|
||||
screen.blit(texture_scaled, texture_pos)
|
53
src/bodyparts/ear.py
Normal file
53
src/bodyparts/ear.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pygame as pg
|
||||
|
||||
from utils import landmark2vec
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from environment import Environment
|
||||
|
||||
LANDMARKS = [103, 332]
|
||||
|
||||
|
||||
class Ear:
|
||||
def __init__(self, side: bool, env: Environment) -> None:
|
||||
self.env = env
|
||||
self.side = side
|
||||
self.texture = (
|
||||
pg.image.load("assets/earL.png").convert_alpha()
|
||||
if side
|
||||
else pg.image.load("assets/earR.png").convert_alpha()
|
||||
)
|
||||
|
||||
def draw(self, screen) -> None:
|
||||
|
||||
# compute position
|
||||
if self.env.results.multi_face_landmarks:
|
||||
for face_landmarks in self.env.results.multi_face_landmarks:
|
||||
|
||||
texture_scaled = pg.transform.scale(
|
||||
self.texture,
|
||||
(100, 250),
|
||||
)
|
||||
|
||||
if self.side: # right moustache
|
||||
moustache_pos = landmark2vec(face_landmarks.landmark[LANDMARKS[0]], screen)
|
||||
|
||||
texture_pos = moustache_pos - pg.Vector2(
|
||||
3 * texture_scaled.get_width() / 4,
|
||||
0.8 * texture_scaled.get_height(),
|
||||
)
|
||||
|
||||
else: # left moustache
|
||||
moustache_pos = landmark2vec(face_landmarks.landmark[LANDMARKS[1]], screen)
|
||||
|
||||
texture_pos = moustache_pos - pg.Vector2(
|
||||
texture_scaled.get_width() / 4,
|
||||
0.8 * texture_scaled.get_height(),
|
||||
)
|
||||
|
||||
# draw texture
|
||||
screen.blit(texture_scaled, texture_pos)
|
69
src/bodyparts/eye.py
Normal file
69
src/bodyparts/eye.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pygame as pg
|
||||
|
||||
from utils import blitRotate, landmark2vec
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from environment import Environment
|
||||
|
||||
LANDMARK_LEFT_EYELID = [386, 374]
|
||||
LANDMARK_RIGHT_EYELID = [159, 145]
|
||||
LANDMARK_LEFT_IRIS = [473, 474, 475, 476, 477]
|
||||
LANDMARK_RIGHT_IRIS = [468, 469, 470, 471, 472]
|
||||
|
||||
|
||||
class Eye:
|
||||
"""The eye class is responsible of drawing an eye."""
|
||||
|
||||
def __init__(self, side: bool, env: Environment) -> None:
|
||||
"""Initialize the eye."""
|
||||
self.env = env
|
||||
self.side = side
|
||||
self.texture = (
|
||||
pg.image.load("assets/eyeL.png").convert_alpha()
|
||||
if side
|
||||
else pg.image.load("assets/eyeR.png").convert_alpha()
|
||||
)
|
||||
|
||||
def draw(self, screen) -> None:
|
||||
"""Draw the eye."""
|
||||
# compute position
|
||||
if self.env.results.multi_face_landmarks:
|
||||
for face_landmarks in self.env.results.multi_face_landmarks:
|
||||
if self.side: # right eye
|
||||
iris_pos = landmark2vec(face_landmarks.landmark[LANDMARK_RIGHT_IRIS[0]], screen)
|
||||
|
||||
iris_top = landmark2vec(face_landmarks.landmark[LANDMARK_RIGHT_IRIS[2]], screen)
|
||||
iris_bottom = landmark2vec(face_landmarks.landmark[LANDMARK_RIGHT_IRIS[4]], screen)
|
||||
|
||||
eyelid_top = landmark2vec(face_landmarks.landmark[LANDMARK_RIGHT_EYELID[0]], screen)
|
||||
eyelid_bottom = landmark2vec(face_landmarks.landmark[LANDMARK_RIGHT_EYELID[1]], screen)
|
||||
|
||||
else: # left eye
|
||||
iris_pos = landmark2vec(face_landmarks.landmark[LANDMARK_LEFT_IRIS[0]], screen)
|
||||
|
||||
iris_top = landmark2vec(face_landmarks.landmark[LANDMARK_LEFT_IRIS[2]], screen)
|
||||
iris_bottom = landmark2vec(face_landmarks.landmark[LANDMARK_LEFT_IRIS[4]], screen)
|
||||
|
||||
eyelid_top = landmark2vec(face_landmarks.landmark[LANDMARK_LEFT_EYELID[0]], screen)
|
||||
eyelid_bottom = landmark2vec(face_landmarks.landmark[LANDMARK_LEFT_EYELID[1]], screen)
|
||||
|
||||
angle = (iris_bottom - iris_top).angle_to(pg.Vector2(0, 1))
|
||||
width = (iris_bottom - iris_top).length()
|
||||
height = (eyelid_bottom - eyelid_top).length() * 5
|
||||
|
||||
texture_scaled = pg.transform.scale(
|
||||
self.texture,
|
||||
(width, height),
|
||||
)
|
||||
|
||||
texture_pos = iris_pos - pg.Vector2(
|
||||
texture_scaled.get_width() / 2,
|
||||
texture_scaled.get_height() / 2,
|
||||
)
|
||||
|
||||
# draw texture
|
||||
blitRotate(screen, texture_scaled, texture_pos, angle)
|
46
src/bodyparts/head.py
Normal file
46
src/bodyparts/head.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pygame as pg
|
||||
|
||||
from utils import blitRotate, landmark2vec
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from environment import Environment
|
||||
|
||||
LANDMARKS = [234, 10, 454, 152]
|
||||
|
||||
|
||||
class Head:
|
||||
def __init__(self, env: Environment) -> None:
|
||||
self.env = env
|
||||
self.texture = pg.image.load("assets/head.png").convert_alpha()
|
||||
|
||||
def draw(self, screen) -> None:
|
||||
|
||||
# compute position
|
||||
if self.env.results.multi_face_landmarks:
|
||||
for face_landmarks in self.env.results.multi_face_landmarks:
|
||||
|
||||
head_top = landmark2vec(face_landmarks.landmark[LANDMARKS[1]], screen)
|
||||
head_bottom = landmark2vec(face_landmarks.landmark[LANDMARKS[3]], screen)
|
||||
|
||||
angle = (head_bottom - head_top).angle_to(pg.Vector2(0, 1))
|
||||
height = (head_bottom - head_top).length()
|
||||
ratio = self.texture.get_width() / self.texture.get_height()
|
||||
|
||||
head_pos = (head_bottom + head_top) / 2
|
||||
|
||||
texture_scaled = pg.transform.scale(
|
||||
self.texture,
|
||||
(height * ratio, height),
|
||||
)
|
||||
|
||||
texture_pos = head_pos - pg.Vector2(
|
||||
texture_scaled.get_width() / 2,
|
||||
texture_scaled.get_height() / 2,
|
||||
)
|
||||
|
||||
# draw texture
|
||||
blitRotate(screen, texture_scaled, texture_pos, angle)
|
53
src/bodyparts/moustache.py
Normal file
53
src/bodyparts/moustache.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pygame as pg
|
||||
|
||||
from utils import landmark2vec
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from environment import Environment
|
||||
|
||||
LANDMARKS = [138, 367]
|
||||
|
||||
|
||||
class Moustache:
|
||||
def __init__(self, side: bool, env: Environment) -> None:
|
||||
self.env = env
|
||||
self.side = side
|
||||
self.texture = (
|
||||
pg.image.load("assets/moustacheL.png").convert_alpha()
|
||||
if side
|
||||
else pg.image.load("assets/moustacheR.png").convert_alpha()
|
||||
)
|
||||
|
||||
def draw(self, screen) -> None:
|
||||
|
||||
# compute position
|
||||
if self.env.results.multi_face_landmarks:
|
||||
for face_landmarks in self.env.results.multi_face_landmarks:
|
||||
|
||||
texture_scaled = pg.transform.scale(
|
||||
self.texture,
|
||||
(200, 100),
|
||||
)
|
||||
|
||||
if self.side: # right moustache
|
||||
moustache_pos = landmark2vec(face_landmarks.landmark[LANDMARKS[0]], screen)
|
||||
|
||||
texture_pos = moustache_pos - pg.Vector2(
|
||||
texture_scaled.get_width(),
|
||||
texture_scaled.get_height() / 2,
|
||||
)
|
||||
|
||||
else: # left moustache
|
||||
moustache_pos = landmark2vec(face_landmarks.landmark[LANDMARKS[1]], screen)
|
||||
|
||||
texture_pos = moustache_pos - pg.Vector2(
|
||||
0,
|
||||
texture_scaled.get_height() / 2,
|
||||
)
|
||||
|
||||
# draw texture
|
||||
screen.blit(texture_scaled, texture_pos)
|
49
src/bodyparts/mouth.py
Normal file
49
src/bodyparts/mouth.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pygame as pg
|
||||
|
||||
from utils import blitRotate, landmark2vec
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from environment import Environment
|
||||
|
||||
LANDMARKS = [78, 13, 308, 14]
|
||||
|
||||
|
||||
class Mouth:
|
||||
def __init__(self, env: Environment) -> None:
|
||||
self.env = env
|
||||
self.texture = pg.image.load("assets/mouth.png").convert_alpha()
|
||||
|
||||
def draw(self, screen) -> None:
|
||||
|
||||
# compute position
|
||||
if self.env.results.multi_face_landmarks:
|
||||
for face_landmarks in self.env.results.multi_face_landmarks:
|
||||
|
||||
mouth_top = landmark2vec(face_landmarks.landmark[LANDMARKS[1]], screen)
|
||||
mouth_bottom = landmark2vec(face_landmarks.landmark[LANDMARKS[3]], screen)
|
||||
|
||||
mouth_left = landmark2vec(face_landmarks.landmark[LANDMARKS[0]], screen)
|
||||
mouth_right = landmark2vec(face_landmarks.landmark[LANDMARKS[2]], screen)
|
||||
|
||||
angle = (mouth_bottom - mouth_top).angle_to(pg.Vector2(0, 1))
|
||||
height = (mouth_bottom - mouth_top).length()
|
||||
width = (mouth_right - mouth_left).length()
|
||||
|
||||
head_pos = (mouth_bottom + mouth_top) / 2
|
||||
|
||||
texture_scaled = pg.transform.scale(
|
||||
self.texture,
|
||||
(width, height),
|
||||
)
|
||||
|
||||
texture_pos = head_pos - pg.Vector2(
|
||||
texture_scaled.get_width() / 2,
|
||||
texture_scaled.get_height() / 2,
|
||||
)
|
||||
|
||||
# draw texture
|
||||
blitRotate(screen, texture_scaled, texture_pos, angle)
|
|
@ -4,7 +4,13 @@ import cv2
|
|||
import mediapipe as mp
|
||||
import pygame as pg
|
||||
|
||||
from parts.eye import Eye
|
||||
from bodyparts.body import Body
|
||||
from bodyparts.crown import Crown
|
||||
from bodyparts.ear import Ear
|
||||
from bodyparts.eye import Eye
|
||||
from bodyparts.head import Head
|
||||
from bodyparts.moustache import Moustache
|
||||
from bodyparts.mouth import Mouth
|
||||
|
||||
|
||||
class Environment:
|
||||
|
@ -42,8 +48,16 @@ class Environment:
|
|||
|
||||
# create body parts
|
||||
self.body_parts = {
|
||||
"body": Body(self),
|
||||
"left_ear": Ear(False, self),
|
||||
"right_ear": Ear(True, self),
|
||||
"head": Head(self),
|
||||
"left_moustache": Moustache(False, self),
|
||||
"right_moustache": Moustache(True, self),
|
||||
"left_eye": Eye(False, self),
|
||||
"right_eye": Eye(True, self),
|
||||
"crown": Crown(self),
|
||||
"mouth": Mouth(self),
|
||||
}
|
||||
|
||||
def start(self) -> None:
|
||||
|
@ -104,26 +118,16 @@ class Environment:
|
|||
if self.results.multi_face_landmarks:
|
||||
for face_landmarks in self.results.multi_face_landmarks:
|
||||
self.mp_drawing.draw_landmarks(
|
||||
image=self.frame,
|
||||
landmark_list=face_landmarks,
|
||||
connections=self.mp_face_mesh.FACEMESH_TESSELATION,
|
||||
landmark_drawing_spec=None,
|
||||
connection_drawing_spec=self.mp_drawing_styles.get_default_face_mesh_tesselation_style(),
|
||||
)
|
||||
self.mp_drawing.draw_landmarks(
|
||||
image=self.frame,
|
||||
landmark_list=face_landmarks,
|
||||
connections=self.mp_face_mesh.FACEMESH_CONTOURS,
|
||||
landmark_drawing_spec=None,
|
||||
connection_drawing_spec=self.mp_drawing_styles.get_default_face_mesh_contours_style(),
|
||||
)
|
||||
self.mp_drawing.draw_landmarks(
|
||||
image=self.frame,
|
||||
landmark_list=face_landmarks,
|
||||
connections=self.mp_face_mesh.FACEMESH_IRISES,
|
||||
landmark_drawing_spec=None,
|
||||
connection_drawing_spec=self.mp_drawing_styles.get_default_face_mesh_iris_connections_style(),
|
||||
self.frame,
|
||||
face_landmarks,
|
||||
landmark_drawing_spec=self.mp_drawing_styles.DrawingSpec((0, 0, 255), 0, 0),
|
||||
)
|
||||
# self.mp_drawing.draw_landmarks(
|
||||
# self.frame,
|
||||
# face_landmarks,
|
||||
# [(469, 470), (470, 471)],
|
||||
# None,
|
||||
# )
|
||||
|
||||
# flip the image horizontally for a selfie-view display
|
||||
cv2.imshow("MediaPipe Face Mesh", cv2.flip(self.frame, 1))
|
||||
|
@ -135,4 +139,6 @@ class Environment:
|
|||
for part in self.body_parts.values():
|
||||
part.draw(self.screen)
|
||||
|
||||
self.screen.blit(pg.transform.flip(self.screen, True, False), (0, 0))
|
||||
|
||||
pg.display.flip()
|
||||
|
|
22
src/utils.py
Normal file
22
src/utils.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
import numpy as np
|
||||
import pygame as pg
|
||||
from mediapipe.python.solutions.drawing_utils import _normalized_to_pixel_coordinates
|
||||
|
||||
|
||||
def blitRotate(surf, image, topleft, angle):
|
||||
"""Rotate an image while drawing it."""
|
||||
rotated_image = pg.transform.rotate(image, angle)
|
||||
new_rect = rotated_image.get_rect(center=image.get_rect(topleft=topleft).center)
|
||||
surf.blit(rotated_image, new_rect.topleft)
|
||||
|
||||
|
||||
def landmark2vec(landmark, screen):
|
||||
"""Convert a landmark to a pygame Vector2."""
|
||||
return pg.Vector2(
|
||||
_normalized_to_pixel_coordinates(
|
||||
np.clip(landmark.x, 0, 1),
|
||||
np.clip(landmark.y, 0, 1),
|
||||
screen.get_width(),
|
||||
screen.get_height(),
|
||||
) # type: ignore
|
||||
)
|
Loading…
Reference in a new issue