From 9b48e5bc4273df45fe95d08da03d72b511dcac75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20T=C5=82uchowski?= Date: Sun, 26 Jan 2025 15:10:39 +0100 Subject: [PATCH] commit: Fix landmark scaling issues with different bbox_size values Core changes: - Normalized boundary points to 0-1 range in MediapipeBaseLandmarks - Fixed scaling inconsistencies between landmarks and boundary points - Updated edge coordinates to use normalized values - Improved y_translation handling for boundary landmarks Files modified: - core/base_landmarks.py --- core/base_landmarks.py | 136 +++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 65 deletions(-) diff --git a/core/base_landmarks.py b/core/base_landmarks.py index fa6872f..d049bd1 100644 --- a/core/base_landmarks.py +++ b/core/base_landmarks.py @@ -99,19 +99,6 @@ class MediapipeBaseLandmarks: cls._faces = faces return cls._base_landmarks, cls._faces - @staticmethod - def interpolate_edge_points(start_pos, end_pos, n_segments): - """ - Interpolate points along an edge - """ - points = [] - for i in range(n_segments + 1): - t = i / n_segments - x = round(start_pos[0] + t * (end_pos[0] - start_pos[0])) - y = round(start_pos[1] + t * (end_pos[1] - start_pos[1])) - points.append((x, y)) - return points - @classmethod def create_boundary_triangles(cls, size): """ @@ -131,28 +118,28 @@ class MediapipeBaseLandmarks: 'top': { 'landmarks': [54, 103, 67, 109, 10, 338, 297, 332, 284], 'start': (0, 0), - 'middle': (width / 2, 0), - 'end': (width, 0), + 'middle': (0.5, 0), + 'end': (1, 0), 'segments': 8 }, 'right': { 'landmarks': [284, 251, 389, 356, 454, 323, 361, 288, 397, 365], - 'start': (width, 0), - 'middle': (width, height / 2), - 'end': (width, height), + 'start': (1, 0), + 'middle': (1, 0.5), + 'end': (1, 1), 'segments': 9 }, 'bottom': { 'landmarks': [365, 379, 378, 400, 377, 152, 148, 176, 149, 150, 136], - 'start': (width, height), - 'middle': (width / 2, height), - 'end': (0, height), + 'start': (1, 1), + 'middle': (0.5, 1), + 'end': (0, 1), 'segments': 10 }, 'left': { 'landmarks': [136, 172, 58, 132, 93, 234, 127, 162, 21, 54], - 'start': (0, height), - 'middle': (0, height / 2), + 'start': (0, 1), + 'middle': (0, 0.5), 'end': (0, 0), 'segments': 9 } @@ -167,7 +154,7 @@ class MediapipeBaseLandmarks: n_segments = edge_info['segments'] print(f"Creating {n_segments} segments for {len(landmarks)} landmarks") - # Generate edge points + # Generate normalized edge points edge_points = cls.interpolate_edge_points( edge_info['start'], edge_info['end'], @@ -201,6 +188,66 @@ class MediapipeBaseLandmarks: return cls._boundary_faces + @staticmethod + def interpolate_edge_points(start_pos, end_pos, n_segments): + """ + Interpolate points along an edge + """ + points = [] + for i in range(n_segments + 1): + t = i / n_segments + x = start_pos[0] + t * (end_pos[0] - start_pos[0]) + y = start_pos[1] + t * (end_pos[1] - start_pos[1]) + points.append((x, y)) + return points + + @classmethod + def get_face_triangles(cls, size=None, x_scale=1.0, y_translation=0.0): + """ + Get face triangulation including boundary triangles with optional transformations + + Args: + size: Target size (int or tuple) + x_scale: Horizontal scaling factor (0.5 to 1.0) + y_translation: Vertical translation (-0.5 to 0.5) + + Returns: + tuple: (triangles array, landmarks array) + """ + landmarks, faces = cls._load_obj_data() + + # Create boundary triangles if needed (using normalized coordinates) + if cls._boundary_faces is None: + cls.create_boundary_triangles(size) + + # Apply transformations to landmarks + landmarks = cls._transform_landmarks(landmarks, x_scale, y_translation) + + if size is not None: + if isinstance(size, tuple): + scaled_landmarks = landmarks * np.array(size) + if cls._boundary_landmarks is not None: + boundary_landmarks = cls._boundary_landmarks * np.array(size) + if y_translation != 0: + boundary_landmarks = boundary_landmarks.copy() + boundary_landmarks[:, 1] += y_translation * size[1] + else: + scaled_landmarks = landmarks * size + if cls._boundary_landmarks is not None: + boundary_landmarks = cls._boundary_landmarks * size + if y_translation != 0: + boundary_landmarks = boundary_landmarks.copy() + boundary_landmarks[:, 1] += y_translation * size + + if cls._boundary_faces is not None: + all_points = np.vstack([scaled_landmarks, boundary_landmarks]) + all_triangles = np.vstack([faces, cls._boundary_faces]) + return all_triangles, all_points + + return faces, landmarks + + + @classmethod def get_base_landmarks(cls, size=None, x_scale=1.0, y_translation=0.0): """ @@ -227,48 +274,7 @@ class MediapipeBaseLandmarks: return landmarks - @classmethod - def get_face_triangles(cls, size=None, x_scale=1.0, y_translation=0.0): - """ - Get face triangulation including boundary triangles with optional transformations - Args: - size: Target size (int or tuple) - x_scale: Horizontal scaling factor (0.5 to 1.0) - y_translation: Vertical translation (-0.5 to 0.5) - - Returns: - tuple: (triangles array, landmarks array) - """ - landmarks, faces = cls._load_obj_data() - - # Apply transformations - landmarks = cls._transform_landmarks(landmarks, x_scale, y_translation) - - if size is not None: - if isinstance(size, tuple): - scaled_landmarks = landmarks * np.array(size) - else: - scaled_landmarks = landmarks * size - - if cls._boundary_faces is None: - cls.create_boundary_triangles(size) - - if cls._boundary_faces is not None: - # Transform boundary landmarks if they exist - if cls._boundary_landmarks is not None: - boundary_landmarks = cls._boundary_landmarks - # Note: We don't apply x_scale to boundary landmarks as they should - # stay at the image edges, but we do apply y_translation - if y_translation != 0: - boundary_landmarks = boundary_landmarks.copy() - boundary_landmarks[:, 1] += y_translation * (size if isinstance(size, int) else size[1]) - - all_points = np.vstack([scaled_landmarks, boundary_landmarks]) - all_triangles = np.vstack([faces, cls._boundary_faces]) - return all_triangles, all_points - - return faces, landmarks @staticmethod def validate_size(size):