Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image | |
| import dlib | |
| import os | |
| import math | |
| from constants import * | |
| MAX_EXPECTED_FACES=7 | |
| # get a list of faces in the image | |
| def face_detecting(image): | |
| detector = dlib.get_frontal_face_detector() | |
| faces = detector(image, 1) | |
| return faces | |
| # show all the faces in rectangles in the image | |
| def face_showing(image, faces): | |
| for face in faces: | |
| cv2.rectangle(image, (face.left(), face.top()), (face.right(), face.bottom()), (255, 255, 255), 2) | |
| return image | |
| # highlight the selected face in the image, using index to select the face | |
| def face_selecting(image, faces, index): | |
| face = faces[index] | |
| cv2.rectangle(image, (face.left(), face.top()), (face.right(), face.bottom()), (255, 255, 255), 2) | |
| return image | |
| # get the landmarks of the face | |
| def face_landmarking(image, face): | |
| predictor = dlib.shape_predictor('shape_predictor_81_face_landmarks.dat') | |
| landmarks = predictor(image, face) | |
| return landmarks | |
| # Function to overlay a transparent image onto another image | |
| def overlay_transparent(background, overlay, x, y): | |
| bg_height, bg_width = background.shape[:2] | |
| if x >= bg_width or y >= bg_height: | |
| return background | |
| h, w = overlay.shape[:2] | |
| if x + w > bg_width: | |
| w = bg_width - x | |
| overlay = overlay[:, :w] | |
| if y + h > bg_height: | |
| h = bg_height - y | |
| overlay = overlay[:h] | |
| if overlay.shape[2] < 4: | |
| overlay = np.concatenate([overlay, np.ones((overlay.shape[0], overlay.shape[1], 1), dtype=overlay.dtype) * 255], axis=2) | |
| overlay_img = overlay[..., :3] | |
| mask = overlay[..., 3:] / 255.0 | |
| background[y:y+h, x:x+w] = (1.0 - mask) * background[y:y+h, x:x+w] + mask * overlay_img | |
| return background | |
| def calculate_eye_angle(landmarks, left_eye_indices, right_eye_indices): | |
| # Calculate the center point of the left eye | |
| left_eye_center = ( | |
| sum([landmarks.part(i).x for i in left_eye_indices]) // len(left_eye_indices), | |
| sum([landmarks.part(i).y for i in left_eye_indices]) // len(left_eye_indices) | |
| ) | |
| # Calculate the center point of the right eye | |
| right_eye_center = ( | |
| sum([landmarks.part(i).x for i in right_eye_indices]) // len(right_eye_indices), | |
| sum([landmarks.part(i).y for i in right_eye_indices]) // len(right_eye_indices) | |
| ) | |
| # Calculate the differences in the x and y coordinates between the centers of the eyes | |
| dx = right_eye_center[0] - left_eye_center[0] | |
| dy = right_eye_center[1] - left_eye_center[1] | |
| # Calculate the angle using the arctangent of the differences | |
| angle = math.degrees(math.atan2(dy, dx)) | |
| return angle | |
| # Function to add ear stickers | |
| def add_ears_sticker(img_bgr, sticker_path, faces): | |
| ears_pil = Image.open(sticker_path) | |
| # Check the color mode and convert to RGBA | |
| ears_rgba = ears_pil.convert('RGBA') | |
| # Convert the ears_rgba to BGRA | |
| r, g, b, a = ears_rgba.split() | |
| ears_bgra = Image.merge("RGBA", (b, g, r, a)) | |
| # A copy of the original image | |
| img_with_stickers = img_bgr.copy() | |
| for face in faces: | |
| landmarks = face_landmarking(img_bgr, face) | |
| # the landmarks 68 to 80 are for the forehead | |
| forehead = [landmarks.part(i) for i in range(68, 81)] | |
| # The landmarks 36 to 41 are for the left eye, and 42 to 47 are for the right eye | |
| left_eye = [landmarks.part(i) for i in range(36, 42)] | |
| right_eye = [landmarks.part(i) for i in range(42, 48)] | |
| # Calculate the center point between the eyes | |
| left_eye_center = ((left_eye[0].x + left_eye[3].x) // 2, (left_eye[0].y + left_eye[3].y) // 2) | |
| right_eye_center = ((right_eye[0].x + right_eye[3].x) // 2, (right_eye[0].y + right_eye[3].y) // 2) | |
| # Calculate the angle of tilt | |
| dx = right_eye_center[0] - left_eye_center[0] | |
| dy = right_eye_center[1] - left_eye_center[1] | |
| angle = math.degrees(math.atan2(dy, dx)) | |
| # Calculate the bounding box for the ears based on the eye landmarks | |
| ears_width = int(abs(forehead[0].x - forehead[-1].x) * 2.1) | |
| ears_height = int(ears_width * ears_bgra.height / ears_bgra.width) | |
| # Resize the ears image | |
| resized_ears_pil = ears_bgra.resize((ears_width, ears_height)) | |
| rotated_ears = resized_ears_pil.rotate(-angle, expand=True, resample=Image.BICUBIC) | |
| # Calculate the position for the ears | |
| y1 = min([point.y for point in forehead]) - int(0.7 * ears_height) | |
| x1 = forehead[0].x - int(0.2 * ears_width) | |
| # Convert PIL image to NumPy array | |
| # ears_np = np.array(resized_ears_pil) | |
| ears_np = np.array(rotated_ears) | |
| # Overlay the ears on the image | |
| img_with_stickers = overlay_transparent(img_with_stickers, ears_np, x1, y1) | |
| return img_with_stickers | |
| # Function to add hats stickers | |
| def add_hats_sticker(img_bgr, sticker_path, faces): | |
| hat_pil = Image.open(sticker_path) | |
| # Check the color mode and convert to RGBA | |
| hat_rgba = hat_pil.convert('RGBA') | |
| # Convert the hat_rgba to BGRA | |
| r, g, b, a = hat_rgba.split() | |
| hat_bgra = Image.merge("RGBA", (b, g, r, a)) | |
| # A copy of the original image | |
| img_with_stickers = img_bgr.copy() | |
| for face in faces: | |
| landmarks = face_landmarking(img_bgr, face) | |
| # The landmarks 36 to 41 are for the left eye, and 42 to 47 are for the right eye | |
| left_eye = [landmarks.part(i) for i in range(36, 42)] | |
| right_eye = [landmarks.part(i) for i in range(42, 48)] | |
| forehead = [landmarks.part(i) for i in range(68, 81)] | |
| # Calculate the center point between the eyes | |
| left_eye_center = ((left_eye[0].x + left_eye[3].x) // 2, (left_eye[0].y + left_eye[3].y) // 2) | |
| right_eye_center = ((right_eye[0].x + right_eye[3].x) // 2, (right_eye[0].y + right_eye[3].y) // 2) | |
| eye_center_x = (left_eye_center[0] + right_eye_center[0]) // 2 | |
| eye_center_y = (left_eye_center[1] + right_eye_center[1]) // 2 | |
| # Calculate the angle of tilt | |
| dx = right_eye_center[0] - left_eye_center[0] | |
| dy = right_eye_center[1] - left_eye_center[1] | |
| angle = math.degrees(math.atan2(dy, dx)) | |
| # Calculate the size of the hat based on the width between the eyes | |
| hat_width = int(abs(left_eye[0].x - right_eye[3].x) * 1.75) | |
| hat_height = int(hat_width * hat_bgra.height / hat_bgra.width) | |
| # Resize and rotate the hat image | |
| resized_hat = hat_bgra.resize((hat_width, hat_height)) | |
| rotated_hat = resized_hat.rotate(-0.8*angle, expand=True, resample=Image.BICUBIC) | |
| # Calculate the position for the hat | |
| y1 = eye_center_y - hat_height - int(0.3 * hat_height) | |
| # x1 = eye_center_x - (hat_width // 2) # Centering the hat on the midpoint between the eyes | |
| # x1 = eye_center_x - (hat_width // 2) - int(0.03 * hat_width) # Moving the hat a bit further to the left | |
| x1 = forehead[0].x - int(0.2 * hat_width) | |
| # Convert PIL image to NumPy array | |
| hat_np = np.array(rotated_hat) | |
| # Overlay the hat on the image | |
| img_with_stickers = overlay_transparent(img_with_stickers, hat_np, x1, y1) | |
| return img_with_stickers | |
| # Function to add glasses stickers | |
| def add_glasses_sticker(img_bgr, sticker_path, faces): | |
| glasses_pil = Image.open(sticker_path) | |
| # Check the color mode and convert to RGBA | |
| glasses_rgba = glasses_pil.convert('RGBA') | |
| # Convert the glasses_rgba to BGRA | |
| r, g, b, a = glasses_rgba.split() | |
| glasses_bgra = Image.merge("RGBA", (b, g, r, a)) | |
| # A copy of the original image | |
| img_with_stickers = img_bgr.copy() | |
| for face in faces: | |
| landmarks = face_landmarking(img_bgr, face) | |
| # the landmarks 36 to 41 are for the left eye, and 42 to 47 are for the right eye | |
| left_eye = [landmarks.part(i) for i in range(36, 42)] | |
| right_eye = [landmarks.part(i) for i in range(42, 48)] | |
| # Calculate the center points of the eyes | |
| left_eye_center = (sum([p.x for p in left_eye]) // len(left_eye), sum([p.y for p in left_eye]) // len(left_eye)) | |
| right_eye_center = (sum([p.x for p in right_eye]) // len(right_eye), sum([p.y for p in right_eye]) // len(right_eye)) | |
| # Calculate the angle of tilt | |
| dx = right_eye_center[0] - left_eye_center[0] | |
| dy = right_eye_center[1] - left_eye_center[1] | |
| angle = math.degrees(math.atan2(dy, dx)) # Angle in degrees | |
| # Calculate the bounding box for the glasses based on the eye landmarks | |
| glasses_width = int(abs(left_eye_center[0] - right_eye_center[0]) * 2) | |
| glasses_height = int(glasses_width * glasses_bgra.height / glasses_bgra.width) | |
| # Resize and rotate the glasses image | |
| resized_glasses = glasses_bgra.resize((glasses_width, glasses_height)) | |
| rotated_glasses = resized_glasses.rotate(-0.8*angle, expand=True, resample=Image.BICUBIC) # Negative angle to correct orientation | |
| # Calculate the position for the glasses, adjusting for the rotation | |
| x1 = left_eye_center[0] - int(0.25 * glasses_width) | |
| y1 = min(left_eye_center[1], right_eye_center[1]) - int(0.45 * glasses_height) | |
| # Convert PIL image to NumPy array | |
| glasses_np = np.array(rotated_glasses) | |
| # Overlay the glasses on the image | |
| img_with_stickers = overlay_transparent(img_with_stickers, glasses_np, x1, y1) | |
| return img_with_stickers | |
| def add_noses_sticker(img_bgr, sticker_path, faces): | |
| nose_pil = Image.open(sticker_path) | |
| # Check the color mode and convert to RGBA | |
| nose_rgba = nose_pil.convert('RGBA') | |
| # Convert the nose_rgba to BGRA | |
| r, g, b, a = nose_rgba.split() | |
| nose_bgra = Image.merge("RGBA", (b, g, r, a)) | |
| # A copy of the original image | |
| img_with_stickers = img_bgr.copy() | |
| for face in faces: | |
| landmarks = face_landmarking(img_bgr, face) | |
| # Assuming that the landmarks 27 to 35 are for the nose area | |
| nose_area = [landmarks.part(i) for i in range(27, 36)] | |
| # Calculate the bounding box for the nose based on the nose landmarks | |
| nose_width = int(abs(nose_area[0].x - nose_area[-1].x) * 2.1) | |
| nose_height = int(nose_width * nose_bgra.height / nose_bgra.width) | |
| # the landmarks 31 and 35 are the leftmost and rightmost points of the nose area | |
| nose_left = landmarks.part(31) | |
| nose_right = landmarks.part(35) | |
| # Calculate the center point of the nose | |
| nose_center_x = (nose_left.x + nose_right.x) // 2 | |
| nose_top = landmarks.part(27) # Use 28 if it's more accurate | |
| nose_bottom = landmarks.part(33) | |
| # Calculate the midpoint of the vertical length of the nose | |
| nose_center_y = (nose_top.y + nose_bottom.y) // 2 | |
| # Calculate the angle of tilt using the eyes as reference | |
| left_eye_indices = range(36, 42) | |
| right_eye_indices = range(42, 48) | |
| angle = calculate_eye_angle(landmarks, left_eye_indices, right_eye_indices) | |
| # Resize the nose image | |
| resized_nose_pil = nose_bgra.resize((nose_width, nose_height)) | |
| rotated_nose = resized_nose_pil.rotate(-angle, expand=True, resample=Image.BICUBIC) | |
| # the position for the nose | |
| x1 = nose_center_x - (nose_width // 2) | |
| y1 = nose_center_y - (nose_height // 2)+ int(0.1 * nose_height) # Adding a slight downward offset | |
| # Convert PIL image to NumPy array | |
| nose_np = np.array(rotated_nose) | |
| # Overlay the nose on the image | |
| img_with_stickers = overlay_transparent(img_with_stickers, nose_np, x1, y1) | |
| return img_with_stickers | |
| def add_animal_faces_sticker(img_bgr, sticker_path, faces): | |
| animal_face_pil = Image.open(sticker_path) | |
| # Check the color mode and convert to RGBA | |
| animal_face_rgba = animal_face_pil.convert('RGBA') | |
| # Convert the animal_face_rgba to BGRA | |
| r, g, b, a = animal_face_rgba.split() | |
| animal_face_bgra = Image.merge("RGBA", (b, g, r, a)) | |
| # A copy of the original image | |
| img_with_stickers = img_bgr.copy() | |
| for face in faces: | |
| landmarks = face_landmarking(img_bgr, face) | |
| # Find the top of the forehead using landmarks above the eyes | |
| # Assuming landmarks 19 to 24 represent the eyebrows | |
| forehead_top = min(landmarks.part(i).y for i in range(68, 81)) | |
| # Calculate the center point between the eyes as an anchor | |
| left_eye = [landmarks.part(i) for i in range(36, 42)] | |
| right_eye = [landmarks.part(i) for i in range(42, 48)] | |
| eye_center_x = (left_eye[0].x + right_eye[3].x) // 2 | |
| eye_center_y = (left_eye[3].y + right_eye[0].y) // 2 | |
| # Calculate the size of the animal face sticker based on the width between the temples | |
| head_width = int(abs(landmarks.part(0).x - landmarks.part(16).x)*1.4) | |
| head_height = int(head_width * animal_face_bgra.height *1.2 / animal_face_bgra.width) | |
| # Calculate the angle of tilt using the eyes as reference | |
| left_eye_indices = range(36, 42) | |
| right_eye_indices = range(42, 48) | |
| angle = calculate_eye_angle(landmarks, left_eye_indices, right_eye_indices) | |
| # Resize the animal face sticker | |
| resized_animal_face = animal_face_bgra.resize((head_width, head_height)) | |
| rotated_animal_face = resized_animal_face.rotate(-angle, expand=True, resample=Image.BICUBIC) | |
| # Calculate the position for the animal face sticker | |
| x1 = eye_center_x - (head_width // 2) | |
| y1 = forehead_top - int(0.18 * head_height) | |
| # Convert PIL image to NumPy array | |
| animal_face_np = np.array(rotated_animal_face) | |
| # Overlay the animal face on the image | |
| img_with_stickers = overlay_transparent(img_with_stickers, animal_face_np, x1, y1) | |
| return img_with_stickers | |
| # This dictionary will hold the user's sticker selections | |
| # sticker_selections = {} | |
| # Function to update sticker selections | |
| def update_selections(category, selection): | |
| sticker_selections[category] = None if selection == "None" else selection | |
| return "" | |
| # Function to load an example image | |
| def load_example_image(image_path): | |
| return gr.Image.from_file(image_path) | |
| def resize_image(image, target_width, target_height): | |
| # Maintain aspect ratio | |
| original_width, original_height = image.size | |
| ratio = min(target_width/original_width, target_height/original_height) | |
| new_width = int(original_width * ratio) | |
| new_height = int(original_height * ratio) | |
| # Use Image.LANCZOS for high-quality downsampling | |
| resized_image = image.resize((new_width, new_height), Image.LANCZOS) | |
| return resized_image | |
| def get_face_crops(image_bgr, faces, target_width=500, target_height=130): | |
| face_crops = [] | |
| for face in faces: | |
| x, y, w, h = face.left(), face.top(), face.width(), face.height() | |
| face_crop = image_bgr[y:y+h, x:x+w] | |
| face_pil = Image.fromarray(cv2.cvtColor(face_crop, cv2.COLOR_BGR2RGB)) | |
| # Resize image to fit the display while maintaining aspect ratio | |
| resized_face = resize_image(face_pil, target_width, target_height) | |
| face_crops.append(resized_face) | |
| return face_crops | |
| # Function to process uploaded images and display face crops | |
| def process_and_show_faces(image_input): | |
| # Convert PIL image to OpenCV format BGR | |
| image_bgr = cv2.cvtColor(np.array(image_input), cv2.COLOR_RGB2BGR) | |
| # Detect faces | |
| faces = face_detecting(image_bgr) | |
| # Get individual face crops | |
| face_crops = get_face_crops(image_bgr, faces) | |
| # Return face crops to display them in the interface | |
| return face_crops | |
| face_outputs = [] | |
| for i in range(MAX_EXPECTED_FACES): | |
| face_output = gr.Image(label=f"Face {i+1}") | |
| face_outputs.append(face_output) | |
| # This list will hold the Checkbox components for each face | |
| checkboxes = [] | |
| def process_selected_faces(image_input, selected_face_indices): | |
| # Convert PIL image to OpenCV format BGR | |
| image_bgr = cv2.cvtColor(np.array(image_input), cv2.COLOR_RGB2BGR) | |
| # Detect all faces | |
| all_faces = face_detecting(image_bgr) | |
| # Filter faces to get only those selected | |
| faces = [all_faces[i] for i in selected_face_indices] | |
| img_with_stickers = image_bgr.copy() | |
| for category, sticker_name in sticker_selections.items(): | |
| if sticker_name: # Check if a sticker was selected in this category | |
| # the sticker file path | |
| if sticker_name != 'None': | |
| sticker_path = os.path.join('stickers', category, sticker_name + '.png') | |
| # Apply the selected sticker based on its category | |
| if category == 'ears': | |
| img_with_stickers = add_ears_sticker(img_with_stickers, sticker_path, faces) | |
| elif category == 'glasses': | |
| img_with_stickers = add_glasses_sticker(img_with_stickers, sticker_path, faces) | |
| elif category == 'noses': | |
| img_with_stickers = add_noses_sticker(img_with_stickers, sticker_path, faces) | |
| elif category == 'headbands': | |
| img_with_stickers = add_hats_sticker(img_with_stickers, sticker_path, faces) | |
| elif category == 'hats': | |
| img_with_stickers = add_hats_sticker(img_with_stickers, sticker_path, faces) | |
| elif category == 'animal face': | |
| img_with_stickers = add_animal_faces_sticker(img_with_stickers, sticker_path, faces) | |
| else: | |
| img_with_stickers = img_with_stickers | |
| # Convert back to PIL image | |
| img_with_stickers_pil = Image.fromarray(cv2.cvtColor(img_with_stickers, cv2.COLOR_BGR2RGB)) | |
| print("Selected stickers:") | |
| for category, selection in sticker_selections.items(): | |
| print(f"{category}: {selection}") | |
| return img_with_stickers_pil | |
| def handle_face_selection(image_input, *checkbox_states): | |
| selected_face_indices = [i for i, checked in enumerate(checkbox_states) if checked] | |
| print("selected_face_indices:",selected_face_indices) | |
| return process_selected_faces(image_input, selected_face_indices) | |
| def update_interface_with_faces(image_input): | |
| image_bgr = cv2.cvtColor(np.array(image_input), cv2.COLOR_RGB2BGR) | |
| faces = face_detecting(image_bgr) | |
| face_crops = get_face_crops(image_bgr, faces) | |
| return [(face, f"Face {i+1}") for i, face in enumerate(face_crops)] | |
| def detect_and_display_faces(image_input): | |
| image_bgr = cv2.cvtColor(np.array(image_input), cv2.COLOR_RGB2BGR) | |
| faces = face_detecting(image_bgr) | |
| face_crops = get_face_crops(image_bgr, faces) | |
| if not face_crops: | |
| # Return empty images and unchecked boxes if no faces are detected | |
| return [None] * MAX_EXPECTED_FACES + [False] * MAX_EXPECTED_FACES | |
| # Return face crops and True for each checkbox to indicate they should be checked | |
| # Pad the list with None and False if fewer faces than MAX_EXPECTED_FACES are detected | |
| output = face_crops + [None] * (MAX_EXPECTED_FACES - len(face_crops)) | |
| output += [True] * len(face_crops) + [False] * (MAX_EXPECTED_FACES - len(face_crops)) | |
| return output | |
| css = """ | |
| #category { | |
| padding-left: 100px; | |
| font-size: 20px; | |
| font-weight: bold; | |
| margin-top: 20px; | |
| } | |
| #sticker { | |
| height: 130px; | |
| width: 30px; | |
| padding: 10px; | |
| } | |
| .radio { | |
| display: flex; | |
| justify-content: space-around; | |
| } | |
| """ | |
| def handle_image_upload(image): | |
| global sticker_selections | |
| sticker_selections = {category: "None" for category in STICKER_PATHS.keys()} | |
| print("reset sticker_selections called") # Reset selections when a new image is loaded | |
| # Print out the sticker selections state for each category | |
| for category, selection in sticker_selections.items(): | |
| print(f"{category}: {selection}") | |
| return image | |
| # Initialize the sticker selections dictionary | |
| def initialize_sticker_selections(): | |
| return { | |
| 'hats': None, | |
| 'animal face': None, | |
| 'ears': None, | |
| 'glasses': None, | |
| 'noses': None, | |
| 'headbands': None | |
| } | |
| sticker_selections = initialize_sticker_selections() | |
| radio_components = {} | |
| # Create the Gradio interface | |
| with gr.Blocks(css=css) as demo: | |
| with gr.Row(): | |
| with gr.Column(): | |
| image_input = gr.Image(type="pil", label="Original Image") | |
| image_input.change( | |
| handle_image_upload, | |
| inputs=[image_input], | |
| outputs=[image_input] | |
| ) | |
| with gr.Column(): | |
| output_image = gr.Image(label="Image with Stickers") | |
| # Prepare the checkboxes and image placeholders | |
| detect_faces_btn = gr.Button("Detect Faces") | |
| with gr.Row(): | |
| face_checkboxes = [gr.Checkbox(label=f"Face {i+1}") for i in range(7)] | |
| with gr.Row(): | |
| face_images = [gr.Image(height=150, width=100, min_width=30, interactive=False, show_download_button=False) for i in range(7)] | |
| detect_faces_btn.click( | |
| detect_and_display_faces, | |
| inputs=[image_input], | |
| outputs=face_images + face_checkboxes | |
| ) | |
| process_button = gr.Button("Apply Stickers To Selected Faces") | |
| process_button.click( | |
| handle_face_selection, | |
| # inputs=[image_input, face_checkboxes, sticker_selections], | |
| inputs=[image_input] + face_checkboxes, | |
| outputs=output_image | |
| ) | |
| # Iterate over each category to create a row for the category | |
| for category, stickers in STICKER_PATHS.items(): | |
| with gr.Row(): | |
| with gr.Column(scale=1, elem_id="category_row"): | |
| gr.Markdown(f"## {category}", elem_id="category") | |
| with gr.Column(scale=10): | |
| # Iterate over stickers in sets of 10 | |
| for i in range(0, len(stickers), 10): | |
| with gr.Row(): | |
| for sticker_path in stickers[i:i+10]: | |
| gr.Image(value=sticker_path, min_width=50, interactive=False, show_download_button=False, container=False, elem_id="sticker") | |
| with gr.Row(): | |
| # radio = gr.Radio(label=' ', choices=[stickers[i].split('/')[-1].replace('.png', '') for i in range(len(stickers))], container=False, min_width=50) | |
| choices = [sticker.split('/')[-1].replace('.png', '') for sticker in stickers] | |
| radio = gr.Radio(label='', choices=choices, value="None", container=False, min_width=50, elem_classes="radio") | |
| radio.change(lambda selection, cat=category: update_selections(cat, selection), inputs=[radio], outputs=[]) | |
| radio_components[category] = radio # Store the radio component | |
| demo.launch(share=True) | |