| import gradio as gr |
| import cv2 |
| import numpy as np |
| import requests |
| import os |
| import tempfile |
|
|
| |
| |
| |
| FACE_CASCADE_PATH = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' |
| |
| |
| |
| LBF_MODEL_PATH = "lbfmodel.yaml" |
|
|
| |
| def edit_image_with_ai(input_image_np_rgb, stability_api_key, prompt_text="perfect white teeth"): |
| """ |
| Processes an input image to detect a mouth, then uses Stability AI to edit it. |
| Args: |
| input_image_np_rgb (numpy.ndarray): The input image in RGB format. |
| stability_api_key (str): The Stability AI API key. |
| prompt_text (str): The prompt for the AI image editing. |
| Returns: |
| tuple: (original_image, edited_image, status_message) |
| original_image is the input image. |
| edited_image is the AI-edited image (or None on failure). |
| status_message provides feedback on the process. |
| """ |
| if input_image_np_rgb is None: |
| return None, None, "Please upload an image or use the webcam." |
| if not stability_api_key: |
| return input_image_np_rgb, None, "⚠️ Please enter your Stability AI API Key." |
| if not prompt_text: |
| return input_image_np_rgb, None, "⚠️ Please enter a prompt for the edit." |
|
|
| try: |
| |
| img_bgr = cv2.cvtColor(input_image_np_rgb, cv2.COLOR_RGB2BGR) |
| gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) |
| mouth_mask = np.zeros_like(gray) |
|
|
| |
| face_cascade = cv2.CascadeClassifier(FACE_CASCADE_PATH) |
| if face_cascade.empty(): |
| |
| raise gr.Error("Fatal Error: Could not load face cascade model. Check OpenCV installation.") |
|
|
| if not os.path.exists(LBF_MODEL_PATH): |
| raise gr.Error( |
| f"Error: LBF Facial Landmark Model ('{LBF_MODEL_PATH}') not found. " |
| "Please download it and add it to your Hugging Face Space's root directory." |
| ) |
| facemark = cv2.face.createFacemarkLBF() |
| try: |
| facemark.loadModel(LBF_MODEL_PATH) |
| except cv2.error as e: |
| raise gr.Error(f"Error loading LBF model from '{LBF_MODEL_PATH}': {e}. Ensure the file is valid.") |
|
|
| |
| faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(50, 50)) |
|
|
| if len(faces) == 0: |
| return input_image_np_rgb, None, "No faces detected in the image. Try a clearer image or different pose." |
|
|
| |
| ok, landmarks_all_faces = facemark.fit(img_bgr, faces) |
| num_mouths_masked = 0 |
| if ok: |
| for landmarks_one_face in landmarks_all_faces: |
| shape = landmarks_one_face[0].astype(np.int32) |
| |
| mouth_points = shape[48:60] |
| if len(mouth_points) > 2: |
| mouth_hull = cv2.convexHull(mouth_points) |
| |
| cv2.drawContours(mouth_mask, [mouth_hull], -1, (255), -1) |
| num_mouths_masked += 1 |
| if num_mouths_masked > 0: |
| |
| kernel = np.ones((5,5),np.uint8) |
| mouth_mask = cv2.dilate(mouth_mask, kernel, iterations = 1) |
| else: |
| return input_image_np_rgb, None, "Facial landmark detection failed. Could not identify facial features." |
|
|
| if num_mouths_masked == 0: |
| return input_image_np_rgb, None, "Could not identify or create a mask for any mouth regions." |
|
|
| _, mouth_mask_binary = cv2.threshold(mouth_mask, 127, 255, cv2.THRESH_BINARY) |
|
|
| |
| |
| with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_image_file, \ |
| tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_mask_file: |
| cv2.imwrite(tmp_image_file.name, img_bgr) |
| cv2.imwrite(tmp_mask_file.name, mouth_mask_binary) |
| tmp_image_path = tmp_image_file.name |
| tmp_mask_path = tmp_mask_file.name |
|
|
| edited_image_np_rgb = None |
| status_message = "Calling Stability AI API..." |
|
|
| try: |
| response = requests.post( |
| "https://api.stability.ai/v2beta/stable-image/edit/inpaint", |
| headers={ |
| "authorization": f"Bearer {stability_api_key}", |
| "accept": "image/*" |
| }, |
| files={ |
| "image": open(tmp_image_path, "rb"), |
| "mask": open(tmp_mask_path, "rb"), |
| }, |
| data={ |
| "prompt": prompt_text, |
| "output_format": "png", |
| |
| |
| }, |
| timeout=90 |
| ) |
|
|
| if response.status_code == 200: |
| edited_image_bytes = response.content |
| nparr = np.frombuffer(edited_image_bytes, np.uint8) |
| |
| edited_image_cv_bgr = cv2.imdecode(nparr, cv2.IMREAD_COLOR) |
|
|
| if edited_image_cv_bgr is not None: |
| |
| edited_image_np_rgb = cv2.cvtColor(edited_image_cv_bgr, cv2.COLOR_BGR2RGB) |
| status_message = "✅ Image edited successfully!" |
| else: |
| status_message = "⚠️ Stability API returned an invalid image format." |
| return input_image_np_rgb, None, status_message |
| else: |
| |
| try: |
| error_detail = response.json() |
| |
| error_messages = error_detail.get('errors', [response.text]) |
| status_message = f"⚠️ Stability API Error ({response.status_code}): {error_messages[0]}" |
| except requests.exceptions.JSONDecodeError: |
| status_message = f"⚠️ Stability API Error ({response.status_code}): {response.text}" |
| return input_image_np_rgb, None, status_message |
|
|
| except requests.exceptions.Timeout: |
| status_message = "⚠️ Stability API request timed out. The server might be busy or the image too large. Please try again later." |
| return input_image_np_rgb, None, status_message |
| except requests.exceptions.RequestException as e: |
| status_message = f"⚠️ Request to Stability API failed: {e}" |
| return input_image_np_rgb, None, status_message |
| except Exception as e: |
| status_message = f"⚠️ An unexpected error occurred during API processing: {e}" |
| return input_image_np_rgb, None, status_message |
| finally: |
| os.remove(tmp_image_path) |
| os.remove(tmp_mask_path) |
|
|
| return input_image_np_rgb, edited_image_np_rgb, status_message |
|
|
| except gr.Error as e: |
| return input_image_np_rgb, None, str(e) |
| except Exception as e: |
| |
| return input_image_np_rgb, None, f"⚠️ An unexpected error occurred in processing: {e}" |
|
|
| |
| with gr.Blocks(theme=gr.themes.Soft(primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.sky)) as demo: |
| gr.Markdown( |
| "<h1 style='text-align: center;'>👄 Your Best Smile With Keen Dental 😄 </h1>" |
| "<p style='text-align: center;'>Upload an image or use your webcam. The app detects faces, " |
| " and uses AI to change your teeth based on your prompt.</p>" |
| ) |
| gr.Markdown( |
| "**Instructions:**\n" |
| "1. Enter your Stability AI API key (e.g., `sk-xxxxxxxx`). Get one from [Stability AI Developer Platform](https://platform.stability.ai/).\n" |
| "2. Upload an image or use your webcam.\n" |
| "3. Click 'Smile!'\n\n" |
| ) |
|
|
| with gr.Row(): |
| api_key_input = gr.Textbox( |
| label="🔑 Stability AI API Key", |
| placeholder="Enter your sk-xxxxxxxx API key here", |
| type="password", |
| lines=1, |
| elem_id="api_key_input" |
| ) |
|
|
| with gr.Row(): |
| input_image = gr.Image(label="🖼️ Input Image", type="numpy", sources=["upload", "webcam"], height=400, interactive=True) |
| output_image = gr.Image(label="✨ Edited Image", type="numpy", height=400, interactive=False) |
|
|
| process_button = gr.Button("🚀 Smile!", variant="primary") |
| status_output = gr.Textbox(label="ℹ️ Status", interactive=False, lines=2) |
|
|
| process_button.click( |
| fn=edit_image_with_ai, |
| inputs=[input_image, api_key_input], |
| outputs=[input_image, output_image, status_output], |
| api_name="edit_mouth" |
| ) |
|
|
| |
| if __name__ == "__main__": |
| demo.launch(debug=True) |
|
|
| |
| |
|
|