Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Lipstick Virtual Makeup App | |
| Dedicated app for applying lipstick with various finishes and colors | |
| Features: Multiple shades, Gloss/Matte/Shimmer finishes, Before/After preview | |
| """ | |
| import cv2 | |
| import numpy as np | |
| import gradio as gr | |
| from PIL import Image | |
| import os | |
| from landmarks import detect_landmarks, normalize_landmarks | |
| # Lipstick color presets with RGB values | |
| LIPSTICK_COLORS = { | |
| "Classic Red": (0, 0, 255), | |
| "Rose Pink": (203, 192, 255), | |
| "Deep Burgundy": (75, 0, 130), | |
| "Coral Orange": (0, 165, 255), | |
| "Nude Beige": (180, 140, 200), | |
| "Wine Red": (0, 0, 120), | |
| "Peach Coral": (100, 165, 255), | |
| "Berry": (128, 0, 128), | |
| "Mauve": (150, 112, 224), | |
| "Crimson": (60, 20, 220), | |
| "Hot Pink": (180, 105, 255), | |
| "Plum": (142, 69, 133), | |
| } | |
| # Landmark indices for lips | |
| UPPER_LIP = [61, 185, 40, 39, 37, 0, 267, 269, 270, 408, 415, 272, 271, 268, 12, 38, 41, 42, 191, 78, 76] | |
| LOWER_LIP = [61, 146, 91, 181, 84, 17, 314, 405, 320, 307, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95] | |
| def apply_lipstick_with_finish(image, color_rgb, landmarks, alpha=0.5, finish="matte"): | |
| """ | |
| Apply lipstick to lips with different finishes. | |
| Args: | |
| image: Input image | |
| color_rgb: RGB color tuple | |
| landmarks: Facial landmarks | |
| alpha: Opacity/intensity (0-1) | |
| finish: "matte", "gloss", or "shimmer" | |
| Returns: | |
| Image with lipstick applied | |
| """ | |
| if landmarks is None: | |
| return image | |
| h, w = image.shape[:2] | |
| mask = np.zeros_like(image) | |
| # Get lip points | |
| lip_points = normalize_landmarks(landmarks, h, w, UPPER_LIP + LOWER_LIP) | |
| if len(lip_points) == 0: | |
| return image | |
| lip_points = lip_points.astype(np.int32) | |
| # Fill lip region with color | |
| cv2.fillPoly(mask, [lip_points], color_rgb) | |
| # Apply finish-specific effects | |
| if finish == "gloss": | |
| # Glossy finish: higher alpha, add highlights | |
| mask = cv2.GaussianBlur(mask, (15, 15), 3) | |
| output = cv2.addWeighted(image, 1.0, mask, alpha * 1.2, 0) | |
| # Add highlight effect on upper lip | |
| highlight_mask = np.zeros_like(image) | |
| upper_lip_points = normalize_landmarks(landmarks, h, w, UPPER_LIP) | |
| if len(upper_lip_points) > 0: | |
| upper_lip_points = upper_lip_points.astype(np.int32) | |
| cv2.fillPoly(highlight_mask, [upper_lip_points], (255, 255, 255)) | |
| highlight_mask = cv2.GaussianBlur(highlight_mask, (25, 25), 8) | |
| output = cv2.addWeighted(output, 1.0, highlight_mask, 0.15, 0) | |
| elif finish == "shimmer": | |
| # Shimmer finish: add sparkle effect | |
| mask = cv2.GaussianBlur(mask, (13, 13), 3) | |
| output = cv2.addWeighted(image, 1.0, mask, alpha, 0) | |
| # Add shimmer particles | |
| shimmer_mask = np.zeros_like(image) | |
| cv2.fillPoly(shimmer_mask, [lip_points], (255, 255, 255)) | |
| # Create random shimmer points | |
| np.random.seed(42) # For consistent shimmer pattern | |
| lip_region = cv2.fillPoly(np.zeros((h, w), dtype=np.uint8), [lip_points], 255) | |
| y_coords, x_coords = np.where(lip_region > 0) | |
| if len(y_coords) > 0: | |
| num_sparkles = min(len(y_coords) // 20, 100) | |
| indices = np.random.choice(len(y_coords), num_sparkles, replace=False) | |
| for idx in indices: | |
| y, x = y_coords[idx], x_coords[idx] | |
| cv2.circle(shimmer_mask, (x, y), 1, (255, 255, 255), -1) | |
| shimmer_mask = cv2.GaussianBlur(shimmer_mask, (5, 5), 1) | |
| output = cv2.addWeighted(output, 1.0, shimmer_mask, 0.2, 0) | |
| else: # matte finish | |
| # Matte finish: smooth, no shine | |
| mask = cv2.GaussianBlur(mask, (15, 15), 3) | |
| mask = cv2.GaussianBlur(mask, (7, 7), 2) | |
| output = cv2.addWeighted(image, 1.0, mask, alpha * 0.9, 0) | |
| return output | |
| def process_lipstick(image, color_name, finish, intensity): | |
| """ | |
| Process image to apply lipstick with selected parameters. | |
| Args: | |
| image: Input image (PIL or numpy) | |
| color_name: Name of the lipstick color | |
| finish: Type of finish (matte/gloss/shimmer) | |
| intensity: Intensity percentage (0-100) | |
| Returns: | |
| Tuple of (processed_image, status_message) | |
| """ | |
| if image is None: | |
| return None, "No image provided" | |
| # Convert PIL to OpenCV format | |
| if isinstance(image, Image.Image): | |
| img = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) | |
| else: | |
| img = image.copy() | |
| # Detect landmarks | |
| landmarks = detect_landmarks(img) | |
| if landmarks is None: | |
| return cv2.cvtColor(img, cv2.COLOR_BGR2RGB), "No face detected" | |
| # Get color | |
| color = LIPSTICK_COLORS.get(color_name, LIPSTICK_COLORS["Classic Red"]) | |
| # Convert intensity percentage to alpha | |
| alpha = intensity / 100.0 * 0.7 # Scale to reasonable range | |
| # Apply lipstick | |
| output = apply_lipstick_with_finish(img, color, landmarks, alpha=alpha, finish=finish.lower()) | |
| # Convert back to RGB | |
| output_rgb = cv2.cvtColor(output, cv2.COLOR_BGR2RGB) | |
| status = f"Applied {color_name} lipstick with {finish} finish at {intensity}% intensity" | |
| return output_rgb, status | |
| def create_comparison(original, processed): | |
| """Create a side-by-side before/after comparison.""" | |
| if original is None or processed is None: | |
| return None | |
| # Convert to numpy arrays if needed | |
| if isinstance(original, Image.Image): | |
| original = np.array(original) | |
| if isinstance(processed, Image.Image): | |
| processed = np.array(processed) | |
| # Ensure same dimensions | |
| if original.shape != processed.shape: | |
| return processed | |
| # Create side-by-side comparison | |
| h, w = original.shape[:2] | |
| comparison = np.zeros((h, w * 2, 3), dtype=np.uint8) | |
| comparison[:, :w] = original | |
| comparison[:, w:] = processed | |
| # Add labels | |
| font = cv2.FONT_HERSHEY_SIMPLEX | |
| cv2.putText(comparison, "BEFORE", (20, 40), font, 1, (255, 255, 255), 2) | |
| cv2.putText(comparison, "AFTER", (w + 20, 40), font, 1, (255, 255, 255), 2) | |
| return comparison | |
| # Create Gradio interface | |
| with gr.Blocks(title="Lipstick Virtual Makeup", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# π Lipstick Virtual Makeup App") | |
| gr.Markdown("Apply professional lipstick effects with customizable colors and finishes") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| # Image upload | |
| image_input = gr.Image(label="Upload Your Photo", type="pil") | |
| # Lipstick color selection | |
| gr.Markdown("### Choose Your Shade") | |
| color_dropdown = gr.Dropdown( | |
| choices=list(LIPSTICK_COLORS.keys()), | |
| value="Classic Red", | |
| label="Lipstick Color", | |
| interactive=True | |
| ) | |
| # Finish type | |
| gr.Markdown("### Select Finish") | |
| finish_radio = gr.Radio( | |
| choices=["Matte", "Gloss", "Shimmer"], | |
| value="Matte", | |
| label="Finish Type", | |
| interactive=True | |
| ) | |
| # Intensity slider | |
| gr.Markdown("### Adjust Intensity") | |
| intensity_slider = gr.Slider( | |
| minimum=0, | |
| maximum=100, | |
| value=60, | |
| step=5, | |
| label="Intensity (%)", | |
| interactive=True | |
| ) | |
| # Apply button | |
| apply_btn = gr.Button("π Apply Lipstick", variant="primary", size="lg") | |
| # Preview mode toggle | |
| preview_checkbox = gr.Checkbox( | |
| label="Show Before/After Comparison", | |
| value=False | |
| ) | |
| with gr.Column(scale=1): | |
| # Output image | |
| image_output = gr.Image(label="Result", type="pil") | |
| # Status message | |
| status_text = gr.Textbox( | |
| label="Status", | |
| interactive=False, | |
| lines=2 | |
| ) | |
| # Info box | |
| gr.Markdown(""" | |
| ### π‘ Tips for Best Results | |
| - Use a well-lit, front-facing photo | |
| - **Matte**: Classic, long-lasting look | |
| - **Gloss**: Shiny, plump appearance | |
| - **Shimmer**: Sparkly, party-ready finish | |
| - Adjust intensity for subtle or bold looks | |
| """) | |
| # Store original and processed images | |
| original_state = gr.State() | |
| processed_state = gr.State() | |
| # Process and store both images | |
| def process_and_store(img, color, finish, intensity): | |
| processed, status = process_lipstick(img, color, finish, intensity) | |
| return processed, status, img, processed | |
| # Handle preview mode toggle | |
| def handle_preview(show_preview, original, processed): | |
| if show_preview and original is not None and processed is not None: | |
| return create_comparison(original, processed) | |
| elif processed is not None: | |
| return processed | |
| return None | |
| # Link apply button | |
| apply_btn.click( | |
| fn=process_and_store, | |
| inputs=[image_input, color_dropdown, finish_radio, intensity_slider], | |
| outputs=[image_output, status_text, original_state, processed_state] | |
| ) | |
| # Link preview checkbox | |
| preview_checkbox.change( | |
| fn=handle_preview, | |
| inputs=[preview_checkbox, original_state, processed_state], | |
| outputs=[image_output] | |
| ) | |
| if __name__ == "__main__": | |
| print("\n" + "="*60) | |
| print("πΈ LipStick Virtual Makeup App") | |
| print("="*60 + "\n") | |
| demo.launch() | |