refactor!: perspective? (orthogonal) projection

This commit is contained in:
Laureηt 2022-12-29 17:42:46 +01:00
parent 0aa7c42781
commit 7212e2e8ec
Signed by: Laurent
SSH key fingerprint: SHA256:kZEpW8cMJ54PDeCvOhzreNr4FSh6R13CMGH/POoO8DI
12 changed files with 161 additions and 727 deletions

View file

@ -1,8 +1 @@
from .body import Body
from .crown import Crown
from .ear import Ear
from .eye import Eye
from .head import Head from .head import Head
from .moustache import Moustache
from .mouth import Mouth
from .test import Test

View file

@ -1,39 +0,0 @@
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,
self.env.size,
)
texture_pos = body_pos - pg.Vector2(
self.env.size[0] / 2,
self.env.size[1] / 10,
)
# draw texture
screen.blit(texture_scaled, texture_pos)

View file

@ -1,34 +0,0 @@
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 = [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:
crown_pos = landmark2vec(face_landmarks.landmark[LANDMARKS[0]], screen)
texture_scaled = pg.transform.scale(
self.texture,
(self.env.size[0] / 3, self.env.size[1] / 3),
)
# draw texture
blitRotate(screen, texture_scaled, crown_pos, self.env.angle_x)

View file

@ -1,53 +0,0 @@
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 = [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,
(self.env.size[0] / 3, self.env.size[1]),
)
if self.side: # right moustache
moustache_pos = landmark2vec(face_landmarks.landmark[LANDMARKS[0]], screen)
pivot = 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)
pivot = pg.Vector2(
texture_scaled.get_width() / 4,
0.8 * texture_scaled.get_height(),
)
# draw texture
blitRotate(screen, texture_scaled, moustache_pos, self.env.angle_x, pivot)

View file

@ -1,64 +0,0 @@
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),
)
# draw texture
blitRotate(screen, texture_scaled, iris_pos, angle)

View file

@ -2,41 +2,84 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import pygame as pg import cv2
import numpy as np
from utils import blitRotate, landmark2vec
if TYPE_CHECKING: if TYPE_CHECKING:
from environment import Environment from environment import Environment
LANDMARKS = [234, 10, 454, 152]
class Head: class Head:
"""The head body part."""
def __init__(self, env: Environment) -> None: def __init__(self, env: Environment) -> None:
"""Initialize the head."""
self.env = env self.env = env
self.texture = pg.image.load("assets/head.png").convert_alpha() self.image = cv2.imread("assets/head.png", cv2.IMREAD_UNCHANGED)
self.ratio = self.image.shape[1] / self.image.shape[0]
def draw(self, screen) -> None: self.bouding_box = np.array(
[
[0, 0],
[self.image.shape[1], 0],
[self.image.shape[1], self.image.shape[0]],
[0, self.image.shape[0]],
],
dtype=np.float32,
)
def draw(self) -> None:
"""Draw the head on the screen."""
# compute position # compute position
if self.env.results.multi_face_landmarks: if self.env.results.multi_face_landmarks:
for face_landmarks in self.env.results.multi_face_landmarks: for face_landmarks in self.env.results.multi_face_landmarks:
head_box = np.array(
[
100 * self.ratio * self.env.x + 100 * self.env.y + 80 * self.env.z,
100 * self.ratio * -self.env.x + 100 * self.env.y + 80 * self.env.z,
100 * self.ratio * -self.env.x + 100 * -self.env.y + 80 * self.env.z,
100 * self.ratio * self.env.x + 100 * -self.env.y + 80 * self.env.z,
]
)[:, :2]
head_top = landmark2vec(face_landmarks.landmark[LANDMARKS[1]], screen) self.translated_head_box = (
head_bottom = landmark2vec(face_landmarks.landmark[LANDMARKS[3]], screen) head_box + self.env.center[:2] * np.array([self.env.camera_width, self.env.camera_height])
).astype(np.float32)
height = (head_bottom - head_top).length() # get perspective transform
ratio = self.texture.get_width() / self.texture.get_height() transform_mat = cv2.getPerspectiveTransform(
self.bouding_box,
self.env.size = (height * ratio, height) self.translated_head_box,
head_pos = (head_bottom + head_top) / 2
texture_scaled = pg.transform.scale(
self.texture,
self.env.size,
) )
# draw texture # apply perspective transform to image
blitRotate(screen, texture_scaled, head_pos, self.env.angle_x) warped = cv2.warpPerspective(
self.image,
transform_mat,
self.env.frame.shape[1::-1],
)
# replace non black pixels of warped image by frame
self.env.avatar[warped[:, :, 3] != 0] = warped[warped[:, :, 3] != 0][:, :3]
def draw_debug(self) -> None:
"""Draw debug information on the screen."""
# link points
for i in range(4):
cv2.line(
self.env.frame,
tuple(self.translated_head_box[i].astype(np.int32)),
tuple(self.translated_head_box[(i + 1) % 4].astype(np.int32)),
(255, 255, 255),
2,
)
# TODO: faire un POC où je place juste un cvGetPerspectiveTransform suivi d'un cvWarpPerspective
# -> comme sur cet exemple, mais où l'image de gauche et droite sont inversées
# https://docs.adaptive-vision.com/studio/filters/GeometricImageTransformations/cvGetPerspectiveTransform.html
# instrisics ? -> https://github.dev/google/mediapipe/blob/master/mediapipe/modules/face_geometry/libs/effect_renderer.cc#L573-L599
# TODO: https://github.com/Rassibassi/mediapipeFacegeometryPython/blob/main/head_posture_rt.py
# -> pnp -> pose estimation -> paramètres extrinsèques
# -> + param intrasèque (supposé connu, check site mediapipe)
# -> placer dans l'espace les textures -> et projeter dans le plan image

View file

@ -1,53 +0,0 @@
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 = [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,
(self.env.size[0] / 1.5, self.env.size[1] / 1.5),
)
if self.side: # right moustache
moustache_pos = landmark2vec(face_landmarks.landmark[LANDMARKS[0]], screen)
pivot = pg.Vector2(
texture_scaled.get_width(),
texture_scaled.get_height() / 2,
)
else: # left moustache
moustache_pos = landmark2vec(face_landmarks.landmark[LANDMARKS[1]], screen)
pivot = pg.Vector2(
0,
texture_scaled.get_height() / 2,
)
# draw texture
blitRotate(screen, texture_scaled, moustache_pos, self.env.angle_x, pivot)

View file

@ -1,55 +0,0 @@
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]
MIN_HEIGHT = 4
MAX_HEIGHT = 40
class Mouth:
def __init__(self, env: Environment) -> None:
self.env = env
self.texture = pg.image.load("assets/mouth.png").convert_alpha()
self.closed_texture = pg.image.load("assets/mouth.png").convert_alpha()
self.closed_texture.fill((0, 0, 0, 255), special_flags=pg.BLEND_RGBA_MULT)
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)
height = (mouth_bottom - mouth_top).length()
height = min(height, MAX_HEIGHT)
width = (mouth_right - mouth_left).length()
mouth_pos = (mouth_bottom + mouth_top) / 2
if height < MIN_HEIGHT:
height = MIN_HEIGHT
texture = self.closed_texture
else:
height *= 1.5
texture = self.texture
texture_scaled = pg.transform.scale(
texture,
(width, height),
)
# draw texture
blitRotate(screen, texture_scaled, mouth_pos, self.env.angle_x)

View file

@ -1,134 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING
import cv2
import numpy as np
import pygame as pg
if TYPE_CHECKING:
from environment import Environment
class Test:
def __init__(self, env: Environment) -> None:
self.env = env
self.image = cv2.imread("assets/head.png", cv2.IMREAD_UNCHANGED)
def draw(self, screen: pg.Surface) -> None:
# compute position
if self.env.results.multi_face_landmarks:
for face_landmarks in self.env.results.multi_face_landmarks:
ratio = self.image.shape[1] / self.image.shape[0]
head_box = np.array(
[
100 * ratio * self.env.x + 100 * self.env.y + 80 * self.env.z,
100 * ratio * -self.env.x + 100 * self.env.y + 80 * self.env.z,
100 * ratio * -self.env.x + 100 * -self.env.y + 80 * self.env.z,
100 * ratio * self.env.x + 100 * -self.env.y + 80 * self.env.z,
]
)[:, :2]
# link points
cv2.line(
self.env.frame,
(
int(self.env.center.x * self.env.screen.get_width() + head_box[0][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[0][1]),
),
(
int(self.env.center.x * self.env.screen.get_width() + head_box[1][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[1][1]),
),
(0, 0, 255),
2,
)
cv2.line(
self.env.frame,
(
int(self.env.center.x * self.env.screen.get_width() + head_box[1][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[1][1]),
),
(
int(self.env.center.x * self.env.screen.get_width() + head_box[2][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[2][1]),
),
(0, 0, 255),
2,
)
cv2.line(
self.env.frame,
(
int(self.env.center.x * self.env.screen.get_width() + head_box[2][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[2][1]),
),
(
int(self.env.center.x * self.env.screen.get_width() + head_box[3][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[3][1]),
),
(0, 0, 255),
2,
)
cv2.line(
self.env.frame,
(
int(self.env.center.x * self.env.screen.get_width() + head_box[3][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[3][1]),
),
(
int(self.env.center.x * self.env.screen.get_width() + head_box[0][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[0][1]),
),
(0, 0, 255),
2,
)
a = np.array(
[
[0, 0],
[self.image.shape[1], 0],
[self.image.shape[1], self.image.shape[0]],
[0, self.image.shape[0]],
],
dtype=np.float32,
)
b = np.array(
[
[
int(self.env.center.x * self.env.screen.get_width() + head_box[0][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[0][1]),
],
[
int(self.env.center.x * self.env.screen.get_width() + head_box[1][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[1][1]),
],
[
int(self.env.center.x * self.env.screen.get_width() + head_box[2][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[2][1]),
],
[
int(self.env.center.x * self.env.screen.get_width() + head_box[3][0]),
int(self.env.center.y * self.env.screen.get_height() + head_box[3][1]),
],
],
dtype=np.float32,
)
# get perspective transform
transform_mat = cv2.getPerspectiveTransform(
a,
b,
)
# apply perspective transform to image
warped = cv2.warpPerspective(
self.image,
transform_mat,
self.env.frame.shape[1::-1],
)
# replace non black pixels of warped image by frame
self.env.frame[warped[:, :, 3] != 0] = warped[warped[:, :, 3] != 0][:, :3]

View file

@ -1,126 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
import pygame as pg
import skimage
import skimage.io
import skimage.transform
from skimage import data, img_as_float
if TYPE_CHECKING:
from environment import Environment
LANDMARKS = [6]
# https://scikit-image.org/docs/stable/auto_examples/transform/plot_geometric.html#sphx-glr-auto-examples-transform-plot-geometric-py
# https://scikit-image.org/docs/stable/auto_examples/transform/plot_transform_types.html#sphx-glr-auto-examples-transform-plot-transform-types-py
# https://github.com/google/mediapipe/issues/1379
# https://en.wikipedia.org/wiki/3D_projection#Perspective_projection
class Test:
def __init__(self, env: Environment) -> None:
self.env = env
self.image = skimage.io.imread("assets/head.png")[:, :, :3]
self.image = skimage.transform.resize(
self.image, (self.image.shape[0] // 10, self.image.shape[1] // 10), anti_aliasing=False
)
self.anglex = 0
self.angley = 0
self.anglez = 0
def draw(self, screen: pg.Surface) -> None:
# compute position
if self.env.results.multi_face_landmarks:
for face_landmarks in self.env.results.multi_face_landmarks:
body_pos = pg.Vector2(*screen.get_size()) / 2
img = img_as_float(data.chelsea())
self.anglez += 1 / 180 * np.pi
self.anglez += 1 / 180 * np.pi
# matrix = np.array(
# [
# [1, -0.5, 100],
# [0.1, 0.9, 50],
# [0.0015, 0.0015, 1],
# ]
# )
# self.angley += 0.1 / 180 * np.pi
# print(self.angley * 180 / np.pi)
# scale = np.array(
# [
# [0.5, 0, 0],
# [0, 0.5, 0],
# [0, 0, 1],
# ]
# )
translation = np.array(
[
[1, 0, -img.shape[1] / 2],
[0, 1, img.shape[0] / 2],
[0, 0, 1],
]
)
anti_translation = np.array(
[
[1, 0, img.shape[1] / 2],
[0, 1, -img.shape[0] / 2],
[0, 0, 1],
]
)
rotation_x = np.array(
[
[1, 0, 0],
[0, np.cos(self.anglex), -np.sin(self.anglex)],
[0, np.sin(self.anglex), np.cos(self.anglex)],
]
)
rotation_y = np.array(
[
[np.cos(self.angley), 0, np.sin(self.angley)],
[0, 1, 0],
[-np.sin(self.angley), 0, np.cos(self.angley)],
]
)
rotation_z = np.array(
[
[np.cos(self.anglez), -np.sin(self.anglez), 0],
[np.sin(self.anglez), np.cos(self.anglez), 0],
[0, 0, 1],
]
)
# matrix = translation @ rotation_x @ rotation_y @ rotation_z @ anti_translation
matrix = rotation_x @ rotation_y @ rotation_z
matrix = translation @ rotation_x @ rotation_y @ rotation_z
tform = skimage.transform.ProjectiveTransform(matrix=matrix)
tf_img = skimage.transform.warp(img, tform.inverse)
yes = pg.surfarray.make_surface((tf_img * 255).astype(np.uint8).transpose(1, 0, 2))
yes.set_colorkey((0, 0, 0))
texture_scaled = pg.transform.scale(
yes,
(yes.get_width() / 2, yes.get_height() / 2),
)
texture_pos = body_pos - pg.Vector2(
texture_scaled.get_width() / 2,
texture_scaled.get_height() / 2,
)
screen.blit(texture_scaled, texture_pos)

View file

@ -2,10 +2,16 @@ import logging
import cv2 import cv2
import mediapipe as mp import mediapipe as mp
import pygame as pg import numpy as np
from bodyparts import Body, Crown, Ear, Eye, Head, Moustache, Mouth, Test from bodyparts import Head
from utils import landmark3vec from utils import (
LANDMARKS_BOTTOM_SIDE,
LANDMARKS_LEFT_SIDE,
LANDMARKS_RIGHT_SIDE,
LANDMARKS_TOP_SIDE,
landmark2vec,
)
class Environment: class Environment:
@ -27,70 +33,56 @@ class Environment:
self.mp_drawing_styles = mp.solutions.drawing_styles # type: ignore self.mp_drawing_styles = mp.solutions.drawing_styles # type: ignore
self.mp_face_mesh = mp.solutions.face_mesh # type: ignore self.mp_face_mesh = mp.solutions.face_mesh # type: ignore
# init pygame
pg.init()
# get screen size from webcam # get screen size from webcam
self.screen_width = camera.get(3) self.camera_width = camera.get(3)
self.screen_height = camera.get(4) self.camera_height = camera.get(4)
# create screen
self.screen: pg.Surface = pg.display.set_mode(
(self.screen_width, self.screen_height),
pg.DOUBLEBUF | pg.HWSURFACE,
) # type: ignore
pg.display.set_caption("Projet APP")
self.size = (0.0, 0.0)
# create body parts # create body parts
self.body_parts = { self.body_parts = [
# "body": Body(self), # Body(self),
# "left_ear": Ear(False, self), # Ear(False, self),
# "right_ear": Ear(True, self), # Ear(True, self),
# "head": Head(self), Head(self),
# "left_moustache": Moustache(False, self), # Moustache(False, self),
# "right_moustache": Moustache(True, self), # Moustache(True, self),
# "left_eye": Eye(False, self), # Eye(False, self),
# "right_eye": Eye(True, self), # Eye(True, self),
# "crown": Crown(self), # Crown(self),
# "mouth": Mouth(self), # Mouth(self),
"test": Test(self), ]
}
def start(self) -> None: def start(self) -> None:
"""Start the environment.""" """Start the environment."""
while self.cam.isOpened(): while self.cam.isOpened():
# stop if q is pressed (opencv)
if cv2.waitKey(5) & 0xFF == ord("q"):
break
# read webcam # read webcam
success, self.frame = self.cam.read() success, self.frame = self.cam.read()
if not success: if not success:
logging.debug("Ignoring empty camera frame.") logging.debug("Ignoring empty camera frame.")
continue continue
# stop if q is pressed (opencv)
if cv2.waitKey(5) & 0xFF == ord("q"):
break
# quit the game
if any(
[event.type == pg.KEYDOWN and event.key == pg.K_q for event in pg.event.get()],
):
break
# detect keypoints on frame # detect keypoints on frame
self.detect_keypoints() self.detect_keypoints()
# compute face axis # compute face axis
self.compute_face_axis() self.compute_face_axis()
self.draw_axis()
# draw keypoints on top of frame # draw keypoints on top of frame
# self.draw_keypoints() self.draw_keypoints()
# draw avatar # draw avatar
self.draw_avatar() self.draw_avatar()
# tmp # show frame
cv2.imshow("MediaPipe Face Mesh", cv2.flip(self.frame, 1)) cv2.imshow("Camera", cv2.flip(self.frame, 1))
# show avatar
cv2.imshow("Avatar", cv2.flip(self.avatar, 1))
def detect_keypoints(self) -> None: def detect_keypoints(self) -> None:
"""Detect the keypoints on the frame.""" """Detect the keypoints on the frame."""
@ -121,58 +113,44 @@ class Environment:
if self.results.multi_face_landmarks: if self.results.multi_face_landmarks:
for face_landmarks in self.results.multi_face_landmarks: for face_landmarks in self.results.multi_face_landmarks:
# retreive points # retreive points
left = landmark3vec(face_landmarks.landmark[234], self.screen) left_points = np.array([landmark2vec(face_landmarks.landmark[i]) for i in LANDMARKS_LEFT_SIDE])
right = landmark3vec(face_landmarks.landmark[454], self.screen) right_points = np.array([landmark2vec(face_landmarks.landmark[i]) for i in LANDMARKS_RIGHT_SIDE])
bottom = landmark3vec(face_landmarks.landmark[152], self.screen) bottom_points = np.array([landmark2vec(face_landmarks.landmark[i]) for i in LANDMARKS_BOTTOM_SIDE])
top = landmark3vec(face_landmarks.landmark[10], self.screen) top_points = np.array([landmark2vec(face_landmarks.landmark[i]) for i in LANDMARKS_TOP_SIDE])
self.center = (left + right + bottom + top) / 4
# compute center
self.center = np.mean(np.concatenate((left_points, right_points, bottom_points, top_points)), axis=0)
# compute axis # compute axis
self.x = (right - left) / 2 self.x = np.mean(right_points - left_points, axis=0)
self.y = (top - bottom) / 2 self.y = np.mean(top_points - bottom_points, axis=0)
self.z = self.x.cross(self.y) self.z = np.cross(self.x, self.y)
# normalize axis # normalize axis
self.x.normalize_ip() self.x = self.x / np.linalg.norm(self.x)
self.y.normalize_ip() self.y = self.y / np.linalg.norm(self.y)
self.z.normalize_ip() self.z = self.z / np.linalg.norm(self.z)
# print horizontal angle to screen def draw_axis(self) -> None:
self.angle_x = self.x.angle_to(pg.math.Vector3(0, 1, 0)) - 90 """Draw the face axis on the frame."""
self.angle_y = self.x.angle_to(pg.math.Vector3(0, 0, 1)) - 90 for (axis, color, letter) in [
self.angle_z = self.y.angle_to(pg.math.Vector3(0, 0, 1)) - 90 (self.x, (0, 0, 255), "X"),
(self.y, (0, 255, 0), "Y"),
(self.z, (255, 0, 0), "Z"),
]:
# compute start and end of axis
start = (
int(self.center[0] * self.camera_width),
int(self.center[1] * self.camera_height),
)
end = (
int(self.center[0] * self.camera_width + axis[0] * 100),
int(self.center[1] * self.camera_height + axis[1] * 100),
)
# draw axis on opencv screen # draw axis + letter
# cv2.line( cv2.line(self.frame, start, end, color, 2)
# self.frame, cv2.putText(self.frame, letter, end, cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
# (int(self.center.x * self.screen.get_width()), int(self.center.y * self.screen.get_height())),
# (
# int(self.center.x * self.screen.get_width() + self.x.x * 100),
# int(self.center.y * self.screen.get_height() + self.x.y * 100),
# ),
# (0, 0, 255),
# 2,
# )
# cv2.line(
# self.frame,
# (int(self.center.x * self.screen.get_width()), int(self.center.y * self.screen.get_height())),
# (
# int(self.center.x * self.screen.get_width() + self.y.x * 100),
# int(self.center.y * self.screen.get_height() + self.y.y * 100),
# ),
# (0, 255, 0),
# 2,
# )
# cv2.line(
# self.frame,
# (int(self.center.x * self.screen.get_width()), int(self.center.y * self.screen.get_height())),
# (
# int(self.center.x * self.screen.get_width() + self.z.x * 100),
# int(self.center.y * self.screen.get_height() + self.z.y * 100),
# ),
# (255, 0, 0),
# 2,
# )
def draw_keypoints(self) -> None: def draw_keypoints(self) -> None:
"""Draw the keypoints on the screen.""" """Draw the keypoints on the screen."""
@ -185,20 +163,12 @@ class Environment:
landmark_drawing_spec=self.mp_drawing_styles.DrawingSpec((0, 0, 255), 0, 0), landmark_drawing_spec=self.mp_drawing_styles.DrawingSpec((0, 0, 255), 0, 0),
) )
# flip the image horizontally for a selfie-view display
cv2.imshow("MediaPipe Face Mesh", cv2.flip(self.frame, 1))
def draw_avatar(self) -> None: def draw_avatar(self) -> None:
"""Draw the avatar on the screen.""" """Draw the avatar on the screen."""
# clear image with green background # clear avatar frame
self.screen.fill(pg.Color("green")) self.avatar = np.zeros(self.frame.shape, dtype=np.uint8)
# draw each body part # draw each body part
for part in self.body_parts.values(): for part in self.body_parts:
part.draw(self.screen) part.draw()
part.draw_debug()
# flip screen
self.screen.blit(pg.transform.flip(self.screen, True, False), (0, 0))
# display screen
pg.display.flip()

View file

@ -1,46 +1,32 @@
import numpy as np import numpy as np
import pygame as pg
from mediapipe.python.solutions.drawing_utils import _normalized_to_pixel_coordinates from mediapipe.python.solutions.drawing_utils import _normalized_to_pixel_coordinates
# def blitRotate(surf, image, topleft, angle): # def landmark2vec(landmark, screen):
# """Rotate an image while drawing it.""" # """Convert a landmark to a pygame Vector2."""
# rotated_image = pg.transform.rotate(image, angle) # return pg.Vector2(
# new_rect = rotated_image.get_rect(center=image.get_rect(topleft=topleft).center) # _normalized_to_pixel_coordinates(
# surf.blit(rotated_image, new_rect.topleft) # np.clip(landmark.x, 0, 1),
# np.clip(landmark.y, 0, 1),
# screen.get_width(),
# screen.get_height(),
# ) # type: ignore
# )
# https://stackoverflow.com/questions/70819750/rotating-and-scaling-an-image-around-a-pivot-while-scaling-width-and-height-sep/70820034#70820034 def landmark2vec(landmark) -> np.ndarray:
def blitRotate(surf, image, origin, angle, pivot=None): """Convert a landmark to numpy array."""
return np.clip(
if pivot is None: [
pivot = pg.math.Vector2(image.get_size()) / 2 landmark.x,
landmark.y,
image_rect = image.get_rect(topleft=(origin[0] - pivot[0], origin[1] - pivot[1])) landmark.z,
offset_center_to_pivot = pg.math.Vector2(origin) - image_rect.center ],
rotated_offset = offset_center_to_pivot.rotate(-angle) a_min=0,
rotated_image_center = (origin[0] - rotated_offset.x, origin[1] - rotated_offset.y) a_max=1,
rotated_image = pg.transform.rotate(image, angle)
rect = rotated_image.get_rect(center=rotated_image_center)
surf.blit(rotated_image, rect)
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
) )
def landmark3vec(landmark, screen): LANDMARKS_LEFT_SIDE = [109, 67, 103, 54, 21, 162, 127, 234, 93, 132, 58, 172, 136, 150, 149, 176, 148]
"""Convert a landmark to a pygame Vector3.""" LANDMARKS_RIGHT_SIDE = [338, 297, 332, 284, 251, 389, 356, 454, 323, 361, 288, 397, 365, 379, 378, 400, 377]
return pg.Vector3( LANDMARKS_BOTTOM_SIDE = [93, 132, 152, 361, 323]
np.clip(landmark.x, 0, 1), LANDMARKS_TOP_SIDE = [127, 162, 10, 389, 356]
np.clip(landmark.y, 0, 1),
np.clip(landmark.z, 0, 1),
)