aflammkhtart5
Update app.py
19f439c verified
raw
history blame
14.9 kB
import gradio as gr
import numpy as np
from PIL import Image
import cv2
import time
# دالة لقياس أداء الدوال
def timing_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"⏱️ {func.__name__} استغرقت: {end_time - start_time:.2f} ثانية")
return result
return wrapper
@timing_decorator
def get_mask_bbox(mask: Image.Image):
"""نسخة محسنة من الدالة"""
try:
mask_np = np.array(mask)
if len(mask_np.shape) == 3:
# استخدام القناة الأخيرة فقط (عادةً ما تكون الألفا)
mask_np = mask_np[..., -1] if mask_np.shape[2] == 4 else mask_np[..., 0]
# إيجاد النقاط غير الصفرية بشكل أكثر كفاءة
nonzero_indices = np.where(mask_np > 0)
if len(nonzero_indices[0]) == 0:
return None
y_min, y_max = np.min(nonzero_indices[0]), np.max(nonzero_indices[0])
x_min, x_max = np.min(nonzero_indices[1]), np.max(nonzero_indices[1])
return int(x_min), int(y_min), int(x_max), int(y_max)
except Exception as e:
print(f"خطأ في get_mask_bbox: {e}")
return None
def mask_to_alpha(mask: Image.Image, smooth_edges=True, morph_level=0):
# تحسين استخراج قناة ألفا من القناع
if mask.mode == 'RGBA':
mask_np = np.array(mask)[..., -1] # استخدام قناة ألفا مباشرة
else:
mask_np = np.array(mask.convert("L"))
if not smooth_edges:
mask_np = np.where(mask_np > 127, 255, 0).astype(np.uint8)
if morph_level > 0:
mask_np = cv2.GaussianBlur(mask_np, (0, 0), sigmaX=morph_level, sigmaY=morph_level, borderType=cv2.BORDER_DEFAULT)
mask_np = np.clip(mask_np, 0, 255).astype(np.uint8)
return Image.fromarray(mask_np)
@timing_decorator
def crop_masked_object(img: Image.Image, mask: Image.Image, smooth_edges=True, morph_level=0, scale=1.0, rotation=0, transparency=1.0):
bbox = get_mask_bbox(mask)
if bbox is None:
return None, None
xmin, ymin, xmax, ymax = bbox
# قص الجزء المحدد فقط من الصورة
cropped_img = img.crop((xmin, ymin, xmax + 1, ymax + 1)).convert("RGBA")
cropped_mask = mask.crop((xmin, ymin, xmax + 1, ymax + 1))
# تطبيق القناع على الصورة لاستخراج العنصر فقط
alpha_mask = mask_to_alpha(cropped_mask, smooth_edges, morph_level)
cropped_img.putalpha(alpha_mask)
if scale != 1.0:
new_size = (int(cropped_img.size[0] * scale), int(cropped_img.size[1] * scale))
cropped_img = cropped_img.resize(new_size, Image.LANCZOS)
if rotation != 0:
cropped_img = cropped_img.rotate(rotation, expand=True)
if transparency < 1.0:
alpha = cropped_img.split()[-1].point(lambda p: int(p * transparency))
cropped_img.putalpha(alpha)
return cropped_img, bbox
def paste_object_on_mask(base_img, base_mask, obj_img, obj_bbox, fit_mode="size"):
base_bbox = get_mask_bbox(base_mask)
if base_bbox is None or obj_bbox is None:
return None
bxmin, bymin, bxmax, bymax = base_bbox
bw, bh = bxmax - bxmin + 1, bymax - bymin + 1
ow, oh = obj_img.size
if fit_mode == "size":
# حساب عامل التحجيم لتكبير أو تصغير الكائن ليتناسب مع القناع (الحفاظ على التناسب)
scale_factor = min(bw / ow, bh / oh)
new_ow, new_oh = int(ow * scale_factor), int(oh * scale_factor)
obj_img = obj_img.resize((new_ow, new_oh), Image.LANCZOS)
x_paste = bxmin + (bw - new_ow) // 2
y_paste = bymin + (bh - new_oh) // 2
else: # fit_mode == "shape"
# تغيير حجم الكائن ليملأ القناع تماماً (قد يؤدي إلى تشويه)
obj_img = obj_img.resize((bw, bh), Image.LANCZOS)
x_paste = bxmin
y_paste = bymin
# إنشاء نتيجة بنفس حجم الصورة الأساسية
result = base_img.copy().convert("RGBA")
result.paste(obj_img, (x_paste, y_paste), obj_img)
return result
def extract_mask_from_editor(editor_data):
"""استخراج القناع من بيانات ImageMask - نسخة محسنة"""
if editor_data is None:
return None
# إذا تم رفع قناع جاهز
if isinstance(editor_data, Image.Image):
return editor_data.convert("L")
# إذا تم استخدام أداة الرسم
if isinstance(editor_data, dict):
# محاولة استخراج القناع من الطبقات
if "layers" in editor_data and len(editor_data["layers"]) > 0:
layer = editor_data["layers"][0]
if isinstance(layer, Image.Image):
# تحويل الطبقة إلى تدرج رمادي
if layer.mode == 'RGBA':
# استخدام قناة ألفا كمقنع
return layer.split()[-1]
else:
return layer.convert("L")
# إذا لم توجد طبقات، استخدام الخلفية لإنشاء قناع فارغ
elif "background" in editor_data:
bg = editor_data["background"]
if isinstance(bg, Image.Image):
return Image.new("L", bg.size, 0)
return None
@timing_decorator
def process_transfer(image1_editor, mask1_upload, image2_editor, mask2_upload,
scale, transparency, rotation, smooth_edges, morph_level, fit_mode):
# التحقق من وجود الصور
if image1_editor is None or image2_editor is None:
return None, None, None, "**❌ يرجى رفع الصورتين وتحديد الأقنعة."
# استخراج الصور الأساسية
if isinstance(image1_editor, dict) and "background" in image1_editor:
base_img = image1_editor["background"].convert("RGBA")
else:
base_img = image1_editor.convert("RGBA") if isinstance(image1_editor, Image.Image) else None
# استخراج الصورة الثانية
if isinstance(image2_editor, dict) and "background" in image2_editor:
obj_img = image2_editor["background"].convert("RGBA")
else:
obj_img = image2_editor.convert("RGBA") if isinstance(image2_editor, Image.Image) else None
if base_img is None or obj_img is None:
return None, None, None, "**❌ يرجى رفع الصورتين."
# استخراج الأقنعة للصورة الأولى
base_mask = extract_mask_from_editor(mask1_upload) if mask1_upload is not None else extract_mask_from_editor(image1_editor)
# استخراج الأقنعة للصورة الثانية (إما من الرسم أو الرفع)
obj_mask = extract_mask_from_editor(mask2_upload) if mask2_upload is not None else extract_mask_from_editor(image2_editor)
if base_mask is None or obj_mask is None:
return None, None, None, "**❌ يجب رسم أو رفع قناع على كلتا الصورتين."
# التحقق من أن الأقنعة تحتوي على مناطق محددة
base_bbox = get_mask_bbox(base_mask)
obj_bbox = get_mask_bbox(obj_mask)
if base_bbox is None:
return None, None, None, "**❌ لم يتم تحديد أي منطقة في الصورة الأولى. يرجى الرسم أو رفع قناع."
if obj_bbox is None:
return None, None, None, "**❌ لم يتم تحديد أي كائن في الصورة الثانية. يرجى الرسم أو رفع قناع."
# قص الجزء المحدد من الصورة الثانية (العنصر المحدد فقط)
cropped_obj, obj_bbox = crop_masked_object(obj_img, obj_mask, smooth_edges, morph_level, scale, rotation, transparency)
if cropped_obj is None:
return None, None, None, "**❌ لم يتم تحديد أي كائن في الصورة الثانية."
# لصق الجزء في منطقة القناع بالصورة الأولى
result_img = paste_object_on_mask(base_img, base_mask, cropped_obj, obj_bbox, fit_mode)
if result_img is None:
return None, None, None, "**❌ لم يتم تحديد منطقة مناسبة للصق في الصورة الأولى."
# التأكد من أن الصورة الناتجة بنفس حجم الصورة الأساسية
if result_img.size != base_img.size:
result_img = result_img.resize(base_img.size, Image.LANCZOS)
return base_mask, obj_mask, result_img, "**✅ تم نقل الكائن بنجاح!"
def create_interface():
css = """
.silver-border {
border: 3px solid #666666 !important;
border-radius: 12px !important;
box-shadow: 0 0 8px #b0b0b0;
background: #f8f8f8;
}
.transfer-btn {
background: linear-gradient(145deg, #FF8C00, #FF6B00) !important;
color: white !important;
border: none !important;
padding: 12px 24px !important;
font-size: 16px !important;
font-weight: bold !important;
border-radius: 8px !important;
transition: all 0.2s ease !important;
}
.transfer-btn:hover {
background: linear-gradient(145deg, #FF6B00, #E55D00) !important;
transform: translateY(-2px) !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important;
}
.transfer-btn:active {
background: linear-gradient(145deg, #E55D00, #CC5500) !important;
transform: translateY(2px) !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) !important;
}
"""
with gr.Blocks(title="نقل كائن من صورة إلى أخرى مع القناع والمحاذاة", css=css) as demo:
gr.Markdown("""
⏳ **ملاحظة:** عند أول استخدام، قد يستغرق التطبيق 20-30 ثانية للبدء بسبب تحميل النماذج.
بعد ذلك ستعمل الأداة بسرعة.
""")
gr.Markdown("""
# 🖼️ نقل كائن من صورة إلى أخرى مع القناع والمحاذاة
يمكنك تعديل إعدادات نقل الكائن من هنا، أو تركها على الإعدادات الافتراضية.
**التعليمات:**
1. ارفع الصورة الأولى (صورة الخلفية) وارسم أو ارفع قناع على مكان اللصق.
2. ارفع الصورة الثانية (الصورة التي بها الكائن) وحدد عليها بالقلم.
3. أو ارفع قناع جاهز للكائن في الصورة الثانية (يجب أن يكون القناع أبيض على أسود).
4. اضغط زر "نقل الكائن" وستظهر النتيجة!
**ملاحظة:** سيتم تكبير أو تصغير الكائن تلقائيًا ليتناسب مع حجم القناع في الصورة الأولى.
""")
with gr.Row():
with gr.Column():
image1_editor = gr.ImageMask(
label="الصورة الأولى (اختر مكان اللصق بالقلم)",
type="pil",
height=400,
elem_classes=["silver-border"]
)
mask1_upload = gr.Image(
label="أو رفع قناع جاهز للصورة الأولى (اختياري)",
type="pil",
height=200,
elem_classes=["silver-border"]
)
with gr.Column():
image2_editor = gr.ImageMask(
label="الصورة الثانية (ارفع الصورة وحدد عليها بالقلم)",
type="pil",
height=400,
elem_classes=["silver-border"]
)
mask2_upload = gr.Image(
label="أو رفع قناع جاهز للصورة الثانية (بدون تحديد بالقلم)",
type="pil",
height=200,
elem_classes=["silver-border"]
)
with gr.Row():
scale = gr.Slider(0.5, 2.0, value=1.0, step=0.05, label="حجم الكائن (Scale) - قبل التكيف مع القناع", elem_classes=["silver-border"])
transparency = gr.Slider(0.2, 1.0, value=1.0, step=0.05, label="شفافية الكائن (Transparency)", elem_classes=["silver-border"])
rotation = gr.Slider(-180, 180, value=0, step=1, label="تدوير الكائن (Rotation)", elem_classes=["silver-border"])
with gr.Row():
smooth_edges = gr.Checkbox(value=True, label="تفعيل الحواف الناعمة (Smooth Edges/Anti-aliasing)", elem_classes=["silver-border"])
morph_level = gr.Slider(0, 10, value=0, step=1, label="مستوى تنعيم القناع (Morphological Smoothing)", elem_classes=["silver-border"])
fit_mode = gr.Radio(
choices=["size", "shape"],
value="size",
label="وضع المحاذاة",
info="size: يحافظ على التناسب | shape: يملأ المساحة (قد يشوه الصورة)",
elem_classes=["silver-border"]
)
with gr.Row():
transfer_btn = gr.Button("🚀 نقل الكائن", elem_classes=["transfer-btn"])
with gr.Row():
base_mask = gr.Image(label="قناع الصورة الأولى المستخدم", type="pil", height=200, elem_classes=["silver-border"])
obj_mask = gr.Image(label="قناع الصورة الثانية المستخدم", type="pil", height=200, elem_classes=["silver-border"])
result_img = gr.Image(label="النتيجة النهائية (بنفس حجم الصورة الأولى)", type="pil", height=400, elem_classes=["silver-border"])
status = gr.Markdown()
transfer_btn.click(
fn=process_transfer,
inputs=[image1_editor, mask1_upload, image2_editor, mask2_upload, scale, transparency, rotation, smooth_edges, morph_level, fit_mode],
outputs=[base_mask, obj_mask, result_img, status]
)
return demo
def main():
demo = create_interface()
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=True,
max_file_size="100MB"
)
if __name__ == "__main__":
main()