Fix landmark detection
This commit is contained in:
parent
9a6b859f37
commit
32c74cc627
1 changed files with 17 additions and 34 deletions
49
timelapse.py
49
timelapse.py
|
|
@ -252,7 +252,7 @@ def get_head_pose(shape, img_size):
|
||||||
return pitch, yaw, roll
|
return pitch, yaw, roll
|
||||||
|
|
||||||
|
|
||||||
def align_face(image, face_data, desired_face_width, desired_face_height, left_eye_pos, pose_threshold):
|
def align_face(image, desired_face_width, desired_face_height, left_eye_pos, pose_threshold, padding_percent):
|
||||||
"""
|
"""
|
||||||
Aligns the face in the image using facial landmarks and head pose estimation.
|
Aligns the face in the image using facial landmarks and head pose estimation.
|
||||||
|
|
||||||
|
|
@ -263,6 +263,7 @@ def align_face(image, face_data, desired_face_width, desired_face_height, left_e
|
||||||
desired_face_height (int): The desired output face height.
|
desired_face_height (int): The desired output face height.
|
||||||
left_eye_pos (tuple): The desired relative position of the left eye.
|
left_eye_pos (tuple): The desired relative position of the left eye.
|
||||||
pose_threshold (float): The maximum allowable head pose deviation.
|
pose_threshold (float): The maximum allowable head pose deviation.
|
||||||
|
padding_percent (float): The padding percentage used to create the crop.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PIL.Image or None: The aligned face image if successful, otherwise None.
|
PIL.Image or None: The aligned face image if successful, otherwise None.
|
||||||
|
|
@ -270,24 +271,19 @@ def align_face(image, face_data, desired_face_width, desired_face_height, left_e
|
||||||
image_np = np.array(image)
|
image_np = np.array(image)
|
||||||
gray = cv2.cvtColor(image_np, cv2.COLOR_RGB2GRAY)
|
gray = cv2.cvtColor(image_np, cv2.COLOR_RGB2GRAY)
|
||||||
|
|
||||||
# Get the original face bounding box from metadata and scale it to the cropped image
|
# Get the face rectangle in the cropped image coordinates
|
||||||
face_img_width = face_data.get("imageWidth")
|
|
||||||
face_img_height = face_data.get("imageHeight")
|
|
||||||
img_width, img_height = image.size
|
img_width, img_height = image.size
|
||||||
scale_x = img_width / face_img_width
|
face_width = img_width / (1 + 2 * padding_percent)
|
||||||
scale_y = img_height / face_img_height
|
face_height = img_height / (1 + 2 * padding_percent)
|
||||||
|
|
||||||
# Calculate the face rectangle in the cropped image coordinates
|
# Calculate the face rectangle coordinates
|
||||||
x1 = int(face_data.get("boundingBoxX1", 0) * scale_x)
|
x1 = int((img_width - face_width) / 2)
|
||||||
x2 = int(face_data.get("boundingBoxX2", 0) * scale_x)
|
x2 = int(x1 + face_width)
|
||||||
y1 = int(face_data.get("boundingBoxY1", 0) * scale_y)
|
y1 = int((img_height - face_height) / 2)
|
||||||
y2 = int(face_data.get("boundingBoxY2", 0) * scale_y)
|
y2 = int(y1 + face_height)
|
||||||
|
|
||||||
# Calculate optimal size for landmark detection (between 200-400px)
|
optimal_size = 256 # optimal image resolution for landmark detection (between 200-400px)
|
||||||
face_width = x2 - x1
|
scale_factor = optimal_size / face_width
|
||||||
face_height = y2 - y1
|
|
||||||
optimal_size = 300 # Target size for landmark detection
|
|
||||||
scale_factor = optimal_size / max(face_width, face_height)
|
|
||||||
|
|
||||||
# Resize the image for landmark detection
|
# Resize the image for landmark detection
|
||||||
resized_width = int(img_width * scale_factor)
|
resized_width = int(img_width * scale_factor)
|
||||||
|
|
@ -315,13 +311,8 @@ def align_face(image, face_data, desired_face_width, desired_face_height, left_e
|
||||||
shape_np = np.array([(shape.part(i).x / scale_factor, shape.part(i).y / scale_factor)
|
shape_np = np.array([(shape.part(i).x / scale_factor, shape.part(i).y / scale_factor)
|
||||||
for i in range(68)], dtype="float32")
|
for i in range(68)], dtype="float32")
|
||||||
|
|
||||||
# Get head pose using the original image size and scaled-back landmarks
|
|
||||||
# Create a new shape object with the scaled-back coordinates
|
head_pose = get_head_pose(shape, (img_width, img_height))
|
||||||
scaled_shape = dlib.full_object_detection(
|
|
||||||
dlib.rectangle(0, 0, img_width, img_height),
|
|
||||||
[dlib.point(int(x), int(y)) for x, y in shape_np]
|
|
||||||
)
|
|
||||||
head_pose = get_head_pose(scaled_shape, (img_width, img_height))
|
|
||||||
if head_pose is None:
|
if head_pose is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -348,7 +339,6 @@ def align_face(image, face_data, desired_face_width, desired_face_height, left_e
|
||||||
eyes_center = ((left_eye_center[0] + right_eye_center[0]) / 2.0,
|
eyes_center = ((left_eye_center[0] + right_eye_center[0]) / 2.0,
|
||||||
(left_eye_center[1] + right_eye_center[1]) / 2.0)
|
(left_eye_center[1] + right_eye_center[1]) / 2.0)
|
||||||
|
|
||||||
|
|
||||||
# Create transformation matrix
|
# Create transformation matrix
|
||||||
M = cv2.getRotationMatrix2D(eyes_center, angle, scale)
|
M = cv2.getRotationMatrix2D(eyes_center, angle, scale)
|
||||||
|
|
||||||
|
|
@ -358,7 +348,6 @@ def align_face(image, face_data, desired_face_width, desired_face_height, left_e
|
||||||
M[0, 2] += (tX - eyes_center[0])
|
M[0, 2] += (tX - eyes_center[0])
|
||||||
M[1, 2] += (tY - eyes_center[1])
|
M[1, 2] += (tY - eyes_center[1])
|
||||||
|
|
||||||
|
|
||||||
# Apply transformation
|
# Apply transformation
|
||||||
aligned_face_np = cv2.warpAffine(
|
aligned_face_np = cv2.warpAffine(
|
||||||
image_np,
|
image_np,
|
||||||
|
|
@ -406,14 +395,13 @@ def process_asset_worker(asset, config: ProcessConfig):
|
||||||
logger.info(f"Face resolution too low ({face_width}x{face_height}).")
|
logger.info(f"Face resolution too low ({face_width}x{face_height}).")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Pass face_data to align_face
|
|
||||||
aligned_face = align_face(
|
aligned_face = align_face(
|
||||||
cropped_face,
|
cropped_face,
|
||||||
face_data,
|
|
||||||
desired_face_width=config.resize_width,
|
desired_face_width=config.resize_width,
|
||||||
desired_face_height=config.resize_height,
|
desired_face_height=config.resize_height,
|
||||||
left_eye_pos=config.left_eye_pos,
|
left_eye_pos=config.left_eye_pos,
|
||||||
pose_threshold=config.pose_threshold
|
pose_threshold=config.pose_threshold,
|
||||||
|
padding_percent=config.padding_percent
|
||||||
)
|
)
|
||||||
|
|
||||||
if aligned_face is None:
|
if aligned_face is None:
|
||||||
|
|
@ -425,11 +413,6 @@ def process_asset_worker(asset, config: ProcessConfig):
|
||||||
aligned_face.save(filename)
|
aligned_face.save(filename)
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
def draw_landmarks(image, shape_np):
|
|
||||||
for (x, y) in shape_np:
|
|
||||||
cv2.circle(image, (x, y), 2, (0, 255, 0), -1)
|
|
||||||
return image
|
|
||||||
|
|
||||||
def process_faces(config: ProcessConfig, max_workers=1, progress_callback=None, date_from=None, date_to=None,
|
def process_faces(config: ProcessConfig, max_workers=1, progress_callback=None, date_from=None, date_to=None,
|
||||||
cancel_flag=None):
|
cancel_flag=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue