New discussion

Update app.py

#8 opened about 2 months ago by
sheerab

import os import gradio as gr import numpy as np import random import spaces import torch import cv2 from PIL import Image from diffusers import Flux2KleinPipeline dtype = torch.bfloat16 device = "cuda" if torch.cuda.is_available() else "cpu" MAX_SEED = np.iinfo(np.int32).max REPO_ID_DISTILLED = "black-forest-labs/FLUX.2-klein-9B" LORA_REPO_ID = "Alissonerdx/BFS-Best-Face-Swap" LORA_FILENAME = "bfs_head_v1_flux-klein_9b_step3750_rank64.safetensors" # ====== تحميل كاشف الوجه ====== print("Loading face detector...") face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml') print("Loading FLUX.2 Klein 9B model...") pipe = Flux2KleinPipeline.from_pretrained(REPO_ID_DISTILLED, torch_dtype=dtype) pipe.to(device) pipe.load_lora_weights(LORA_REPO_ID, weight_name=LORA_FILENAME) print("All models loaded successfully!") # ============================================================ # ====== الطريقة الأساسية: نسخ ولصق مباشر (مثل الفوتوشوب) ====== # ====== يحافظ على مقاس القالب تماماً ولا يغيره أبداً ====== # ============================================================ def detect_face_landmarks(image_np): """اكتشاف الوجه والعينين للحصول على الزاوية والموقع والحجم""" gray = cv2.cvtColor(image_np, cv2.COLOR_RGB2GRAY) faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(30, 30)) if len(faces) == 0: return None # أخذ أكبر وجه face = max(faces, key=lambda f: f[2] * f[3]) x, y, w, h = face # توسيع المنطقة لتشمل الرأس كاملاً والشعر head_x = max(0, int(x - w * 0.15)) head_y = max(0, int(y - h * 0.35)) head_w = min(image_np.shape[1] - head_x, int(w * 1.3)) head_h = min(image_np.shape[0] - head_y, int(h * 1.5)) # اكتشاف العينين لحساب الزاوية roi_gray = gray[y:y+h, x:x+w] eyes = eye_cascade.detectMultiScale(roi_gray, 1.1, 3) angle = 0 if len(eyes) >= 2: eyes = sorted(eyes, key=lambda e: e[0])[:2] eye1_center = (x + eyes[0][0] + eyes[0][2]//2, y + eyes[0][1] + eyes[0][3]//2) eye2_center = (x + eyes[1][0] + eyes[1][2]//2, y + eyes[1][1] + eyes[1][3]//2) dx = eye2_center[0] - eye1_center[0] dy = eye2_center[1] - eye1_center[1] angle = np.degrees(np.arctan2(dy, dx)) return { 'head_rect': (head_x, head_y, head_w, head_h), 'face_rect': (x, y, w, h), 'angle': angle, 'center': (x + w//2, y + h//2) } def create_head_mask(head_w, head_h, feather_amount=30): """إنشاء قناع ناعم لدمج الرأس بشكل سلس""" mask = np.zeros((head_h, head_w), dtype=np.float32) center_x = head_w // 2 center_y = int(head_h * 0.45) axis_x = int(head_w * 0.45) axis_y = int(head_h * 0.48) y_grid, x_grid = np.ogrid[:head_h, :head_w] dist = ((x_grid - center_x) / axis_x) ** 2 + ((y_grid - center_y) / axis_y) ** 2 mask[dist <= 1.0] = 1.0 mask = cv2.GaussianBlur(mask, (feather_amount * 2 + 1, feather_amount * 2 + 1), feather_amount) return mask def direct_face_swap(reference_face_pil, target_image_pil, feather_amount=25): """ تبديل الوجه مباشرة مثل الفوتوشوب: ✅ يحافظ على مقاس الصورة القالب تماماً كما هو ✅ يحافظ على ملامح الوجه المرجعي تماماً كما هو ✅ يغير حجم الرأس فقط ليتناسب مع الرأس في القالب ✅ لا يغير أي شيء آخر في الصورة """ ref_np = np.array(reference_face_pil) target_np = np.array(target_image_pil) # ⚠️ مهم جداً: نحافظ على مقاس الصورة القالب كما هو original_height, original_width = target_np.shape[:2] # اكتشاف الوجوه ref_landmarks = detect_face_landmarks(ref_np) target_landmarks = detect_face_landmarks(target_np) if ref_landmarks is None: raise gr.Error("لم أتمكن من اكتشاف وجه في الصورة المرجعية!") if target_landmarks is None: raise gr.Error("لم أتمكن من اكتشاف وجه في الصورة القالب!") # استخراج الرأس من الصورة المرجعية rx, ry, rw, rh = ref_landmarks['head_rect'] ref_head = ref_np[ry:ry+rh, rx:rx+rw].copy() # معلومات الرأس في القالب tx, ty, tw, th = target_landmarks['head_rect'] target_angle = target_landmarks['angle'] ref_angle = ref_landmarks['angle'] # حساب زاوية الدوران المطلوبة rotation_angle = target_angle - ref_angle # 1. تدوير رأس الصورة المرجعية center = (rw // 2, rh // 2) rotation_matrix = cv2.getRotationMatrix2D(center, rotation_angle, 1.0) cos = np.abs(rotation_matrix[0, 0]) sin = np.abs(rotation_matrix[0, 1]) new_w = int((rh * sin) + (rw * cos)) new_h = int((rh * cos) + (rw * sin)) rotation_matrix[0, 2] += (new_w / 2) - center[0] rotation_matrix[1, 2] += (new_h / 2) - center[1] rotated_head = cv2.warpAffine(ref_head, rotation_matrix, (new_w, new_h), borderMode=cv2.BORDER_REPLICATE) # 2. تغيير حجم الرأس ليتناسب تماماً مع الرأس في القالب (وليس الصورة كاملة) resized_head = cv2.resize(rotated_head, (tw, th), interpolation=cv2.INTER_LANCZOS4) # 3. إنشاء الماسك الناعم mask = create_head_mask(tw, th, feather_amount) mask_3channel = np.stack([mask, mask, mask], axis=2) # 4. الدمج: وضع الرأس الجديد فوق القالب # ⚠️ مهم جداً: النتيجة بنفس مقاس الصورة القالب الأصلي result = target_np.copy() roi = result[ty:ty+th, tx:tx+tw] blended = (resized_head * mask_3channel + roi * (1 - mask_3channel)).astype(np.uint8) result[ty:ty+th, tx:tx+tw] = blended # ✅ التأكد من أن النتيجة بنفس المقاس الأصلي assert result.shape[0] == original_height and result.shape[1] == original_width, \ "خطأ: تغير مقاس الصورة!" return Image.fromarray(result) # ============================================================ # ====== واجهة Gradio (بسيطة جداً بدون أي تعديل مقاس) ====== # ============================================================ css = """ #col-container {margin: 0 auto; max-width: 1200px;} .image-container img {object-fit: contain;} """ with gr.Blocks(css=css, title="تبديل الوجوه الاحترافي") as demo: with gr.Column(elem_id="col-container"): gr.Markdown(""" # 🎭 تبديل الوجوه الاحترافي ### ✅ يحافظ على مقاس الصورة القالب تماماً كما هو ### ✅ يحافظ على ملامح الوجه المرجعي دون أي تغيير ### ✅ مثل الفوتوشوب تماماً - نسخ ولصق مباشر """) with gr.Row(): with gr.Column(): with gr.Row(): reference_face = gr.Image( label="📸 الوجه المرجعي", type="pil", sources=["upload"], elem_classes="image-container" ) target_image = gr.Image( label="👔 الصورة القالب", type="pil", sources=["upload"], elem_classes="image-container" ) with gr.Accordion("⚙️ إعدادات بسيطة", open=True): feather_amount = gr.Slider( label="نعومة الحواف (لدمج سلس)", minimum=5, maximum=60, step=1, value=25, info="قيمة أعلى = دمج أكثر سلاسة بين الرأس والبدلة" ) swap_button = gr.Button("🔄 تنفيذ تبديل الوجه", variant="primary", size="lg") comparison_slider = gr.ImageSlider(label="قبل / بعد", type="pil") # ====== الدالة الرئيسية ====== def swap_face(ref_face, target_img, feather): if ref_face is None or target_img is None: raise gr.Error("الرجاء رفع الصورتين أولاً!") # عرض معلومات المقاس للتأكيد original_size = target_img.size print(f"✅ مقاس الصورة القالب الأصلي: {original_size[0]} × {original_size[1]}") # تنفيذ التبديل result = direct_face_swap(ref_face, target_img, feather_amount=int(feather)) # التأكيد على أن المقاس لم يتغير result_size = result.size print(f"✅ مقاس الصورة الناتجة: {result_size[0]} × {result_size[1]}") assert original_size == result_size, "خطأ: تغير مقاس الصورة!" return (target_img, result) swap_button.click( fn=swap_face, inputs=[reference_face, target_image, feather_amount], outputs=[comparison_slider] ) # تشغيل تلقائي عند رفع الصورتين def auto_swap(ref_face, target_img, feather): if ref_face is not None and target_img is not None: return swap_face(ref_face, target_img, feather) return None reference_face.change( fn=auto_swap, inputs=[reference_face, target_image, feather_amount], outputs=[comparison_slider] ) target_image.change( fn=auto_swap, inputs=[reference_face, target_image, feather_amount], outputs=[comparison_slider] ) if __name__ == "__main__": demo.launch(share=True, theme=gr.themes.Citrus())

#7 opened about 2 months ago by
sheerab

Delete app.py

#6 opened about 2 months ago by
sheerab

Delete app.py

#5 opened about 2 months ago by
sheerab

Dynamic duration

#1 opened 9 months ago by
binaryreporter