ukzada's picture
Rename app_foundation.py to app.py
7a0caf4 verified
Raw
History Blame Contribute Delete
12.5 kB
#!/usr/bin/env python3
"""
Foundation Virtual Makeup App
Dedicated app for applying foundation with adjustable coverage
Features: Coverage levels (Light/Medium/Full), Shade matching, Blending options
"""
import cv2
import numpy as np
import gradio as gr
from PIL import Image
import os
from utils import mask_skin, gamma_correction
# Foundation shade presets (RGB adjustments)
FOUNDATION_SHADES = {
"Fair": {"shift": (-10, -5, 0), "description": "Very light skin tones"},
"Light": {"shift": (-5, 0, 5), "description": "Light skin tones"},
"Medium": {"shift": (0, 5, 10), "description": "Medium skin tones"},
"Tan": {"shift": (5, 10, 15), "description": "Tan skin tones"},
"Deep": {"shift": (10, 15, 20), "description": "Deep skin tones"},
"Rich": {"shift": (15, 20, 25), "description": "Rich, dark skin tones"},
}
# Coverage level presets
COVERAGE_LEVELS = {
"Light": {
"intensity": 0.25,
"gamma": 1.15,
"smoothing": 5,
"description": "Sheer coverage for natural look"
},
"Medium": {
"intensity": 0.45,
"gamma": 1.30,
"smoothing": 7,
"description": "Balanced coverage for everyday wear"
},
"Full": {
"intensity": 0.70,
"gamma": 1.50,
"smoothing": 9,
"description": "Maximum coverage for flawless finish"
},
}
# Blending options
BLENDING_MODES = {
"Natural": {"blur_kernel": 11, "feather": 0.9, "description": "Soft, seamless blend"},
"Buildable": {"blur_kernel": 7, "feather": 0.7, "description": "Layered, controlled blend"},
"Airbrushed": {"blur_kernel": 15, "feather": 0.95, "description": "Ultra-smooth, flawless blend"},
}
def auto_detect_shade(image):
"""
Analyze skin tone to suggest the best foundation shade.
Returns suggested shade name.
"""
if image is None:
return "Medium"
# Get skin mask
skin_mask_binary = mask_skin(image)
if skin_mask_binary.ndim == 3:
skin_mask_binary = skin_mask_binary[:, :, 0]
# Extract skin pixels
skin_pixels = image[skin_mask_binary > 0]
if len(skin_pixels) == 0:
return "Medium"
# Calculate average skin tone (BGR format)
avg_b, avg_g, avg_r = np.mean(skin_pixels, axis=0)
# Calculate brightness (luminance)
luminance = 0.299 * avg_r + 0.587 * avg_g + 0.114 * avg_b
# Map luminance to shade
if luminance < 100:
return "Rich"
elif luminance < 130:
return "Deep"
elif luminance < 160:
return "Tan"
elif luminance < 190:
return "Medium"
elif luminance < 220:
return "Light"
else:
return "Fair"
def apply_foundation_advanced(image, shade_name="Medium", coverage="Medium",
blending="Natural", warmth_adjust=0):
"""
Apply foundation with advanced controls.
Args:
image: Input image (BGR format)
shade_name: Foundation shade name
coverage: Coverage level (Light/Medium/Full)
blending: Blending mode (Natural/Buildable/Airbrushed)
warmth_adjust: Warmth adjustment (-20 to +20)
Returns:
Image with foundation applied
"""
if image is None:
return image
# Get skin mask
skin_mask_binary = mask_skin(image)
if skin_mask_binary.ndim == 3:
skin_mask_binary = skin_mask_binary[:, :, 0]
# Get parameters
shade_info = FOUNDATION_SHADES.get(shade_name, FOUNDATION_SHADES["Medium"])
coverage_info = COVERAGE_LEVELS.get(coverage, COVERAGE_LEVELS["Medium"])
blend_info = BLENDING_MODES.get(blending, BLENDING_MODES["Natural"])
intensity = coverage_info["intensity"]
gamma_val = coverage_info["gamma"]
smoothing = coverage_info["smoothing"]
blur_kernel = blend_info["blur_kernel"]
feather = blend_info["feather"]
# Apply gamma correction for brightness
corrected = gamma_correction(image, gamma_val, coefficient=1)
# Apply shade adjustment
corrected = corrected.astype(np.float32)
b_shift, g_shift, r_shift = shade_info["shift"]
corrected[:, :, 0] = np.clip(corrected[:, :, 0] + b_shift, 0, 255)
corrected[:, :, 1] = np.clip(corrected[:, :, 1] + g_shift, 0, 255)
corrected[:, :, 2] = np.clip(corrected[:, :, 2] + r_shift, 0, 255)
# Apply warmth adjustment (affects red channel)
if warmth_adjust != 0:
warmth_factor = 1.0 + (warmth_adjust / 100.0)
corrected[:, :, 2] = np.clip(corrected[:, :, 2] * warmth_factor, 0, 255)
corrected = corrected.astype(np.uint8)
# Smooth skin texture based on coverage level
if smoothing > 0:
# Bilateral filter preserves edges while smoothing
corrected = cv2.bilateralFilter(corrected, smoothing, 75, 75)
# Create feathered mask for seamless blending
skin_mask_float = skin_mask_binary.astype(np.float32)
# Apply blur for soft edges
if blur_kernel % 2 == 0:
blur_kernel += 1
skin_mask_float = cv2.GaussianBlur(skin_mask_float, (blur_kernel, blur_kernel), 0)
# Apply feathering
skin_mask_float = np.power(skin_mask_float, feather)
# Expand mask to 3 channels
skin_mask_float = np.expand_dims(skin_mask_float, axis=-1)
skin_mask_float = np.repeat(skin_mask_float, 3, axis=2)
# Blend corrected with original using feathered mask
output = image.astype(np.float32)
corrected = corrected.astype(np.float32)
# Apply intensity-weighted blend only where skin is detected
output = output * (1.0 - skin_mask_float * intensity) + corrected * (skin_mask_float * intensity)
return output.astype(np.uint8)
def process_foundation(image, shade, coverage, blending, warmth, auto_match):
"""
Process image to apply foundation.
Args:
image: Input image
shade: Foundation shade name
coverage: Coverage level
blending: Blending mode
warmth: Warmth adjustment value
auto_match: Whether to auto-detect shade
Returns:
Tuple of (processed_image, status_message, suggested_shade)
"""
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()
# Auto-detect shade if requested
suggested_shade = ""
if auto_match:
suggested_shade = auto_detect_shade(img)
shade = suggested_shade
status_prefix = f"Auto-matched to {suggested_shade} shade. "
else:
status_prefix = ""
# Apply foundation
output = apply_foundation_advanced(img, shade, coverage, blending, warmth)
# Convert back to RGB
output_rgb = cv2.cvtColor(output, cv2.COLOR_BGR2RGB)
coverage_info = COVERAGE_LEVELS.get(coverage, COVERAGE_LEVELS["Medium"])
status = (f"{status_prefix}Applied {shade} foundation with {coverage} coverage "
f"({coverage_info['description']}) using {blending} blending")
return output_rgb, status, suggested_shade
# Create Gradio interface
with gr.Blocks(title="Foundation Virtual Makeup", theme=gr.themes.Soft()) as demo:
gr.Markdown("# ✨ Foundation Virtual Makeup App")
gr.Markdown("Apply professional foundation with AI-powered shade matching and customizable coverage")
with gr.Row():
with gr.Column(scale=1):
# Image upload
image_input = gr.Image(label="Upload Your Photo", type="pil")
# Shade matching section
gr.Markdown("### 🎨 Shade Selection")
auto_match_btn = gr.Button("πŸ” Auto-Match My Shade", variant="secondary")
shade_info_text = gr.Textbox(
label="Detected Shade",
interactive=False,
placeholder="Click 'Auto-Match' to detect your shade"
)
shade_dropdown = gr.Dropdown(
choices=list(FOUNDATION_SHADES.keys()),
value="Medium",
label="Foundation Shade",
interactive=True
)
# Show shade description
shade_desc = gr.Markdown(
f"*{FOUNDATION_SHADES['Medium']['description']}*",
visible=True
)
# Coverage level
gr.Markdown("### πŸ“Š Coverage Level")
coverage_radio = gr.Radio(
choices=list(COVERAGE_LEVELS.keys()),
value="Medium",
label="Coverage",
interactive=True
)
coverage_desc = gr.Markdown(
f"*{COVERAGE_LEVELS['Medium']['description']}*",
visible=True
)
# Blending mode
gr.Markdown("### πŸ–ŒοΈ Blending Mode")
blending_radio = gr.Radio(
choices=list(BLENDING_MODES.keys()),
value="Natural",
label="Blending Style",
interactive=True
)
# Warmth adjustment
gr.Markdown("### 🌑️ Warmth Adjustment")
warmth_slider = gr.Slider(
minimum=-20,
maximum=20,
value=0,
step=2,
label="Warmth (Cool ← β†’ Warm)",
interactive=True
)
# Apply button
apply_btn = gr.Button("✨ Apply Foundation", variant="primary", size="lg")
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=3
)
# Shade reference guide
gr.Markdown("""
### πŸ“– Shade Guide
- **Fair**: Very light, porcelain skin
- **Light**: Light, ivory skin
- **Medium**: Medium, beige skin
- **Tan**: Tan, bronze skin
- **Deep**: Deep, caramel skin
- **Rich**: Rich, dark cocoa skin
### πŸ’‘ Tips
- Use AI auto-match for best results
- Start with Light coverage, build up as needed
- Natural blending for everyday look
- Airbrushed for special occasions
""")
# Hidden state for auto-match flag
auto_match_state = gr.State(False)
# Update shade description when shade changes
def update_shade_desc(shade):
desc = FOUNDATION_SHADES.get(shade, FOUNDATION_SHADES["Medium"])["description"]
return f"*{desc}*"
shade_dropdown.change(
fn=update_shade_desc,
inputs=[shade_dropdown],
outputs=[shade_desc]
)
# Update coverage description when coverage changes
def update_coverage_desc(coverage):
desc = COVERAGE_LEVELS.get(coverage, COVERAGE_LEVELS["Medium"])["description"]
return f"*{desc}*"
coverage_radio.change(
fn=update_coverage_desc,
inputs=[coverage_radio],
outputs=[coverage_desc]
)
# Auto-match shade
def auto_match_shade(image):
if image is None:
return "Medium", "Please upload an image first", False
if isinstance(image, Image.Image):
img = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
else:
img = image.copy()
detected_shade = auto_detect_shade(img)
desc = FOUNDATION_SHADES.get(detected_shade, FOUNDATION_SHADES["Medium"])["description"]
return detected_shade, f"Detected: {detected_shade} ({desc})", True
auto_match_btn.click(
fn=auto_match_shade,
inputs=[image_input],
outputs=[shade_dropdown, shade_info_text, auto_match_state]
)
# Apply foundation
apply_btn.click(
fn=process_foundation,
inputs=[image_input, shade_dropdown, coverage_radio, blending_radio,
warmth_slider, auto_match_state],
outputs=[image_output, status_text, gr.State()]
)
if __name__ == "__main__":
print("\n" + "="*60)
print("🌸 Foundation Virtual Makeup App")
print("="*60 + "\n")
demo.launch()