bee's picture

bee

sheerab

AI & ML interests

None yet

Recent Activity

new activity about 10 hours ago
linoyts/Flux2-Klein-Face-Swap:Update app.py
new activity about 10 hours ago
linoyts/Flux2-Klein-Face-Swap: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())
new activity about 10 hours ago
linoyts/Flux2-Klein-Face-Swap:Delete app.py
View all activity

Organizations

None yet