ALIMHGFY commited on
Commit
0dfd0b8
·
verified ·
1 Parent(s): 04495dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +106 -153
app.py CHANGED
@@ -1,172 +1,125 @@
1
  import gradio as gr
2
  import numpy as np
3
- import cv2
4
  from PIL import Image
5
- from ultralytics import SAM
6
 
7
- class ImageSegmentationApp:
8
- def __init__(self) -> None:
9
- """Initialize the segmentation app and load the SAM2 model with fallback."""
10
- try:
11
- # Attempt to load the SAM2 model weights
12
- self.model = SAM("sam2.1_t.pt")
13
- self.model_available = True # Model loaded successfully
14
- except Exception as e:
15
- # If loading fails, set model as unavailable and print error
16
- print(f"Failed to load SAM2 model: {e}")
17
- self.model = None
18
- self.model_available = False
19
 
20
- def process_segmentation(
21
- self,
22
- image_editor: dict,
23
- replacement_image: Image.Image
24
- ) -> list[Image.Image | None] | None:
25
- """
26
- Process the segmentation and replacement using the drawn mask and SAM2 model.
27
- Returns [drawn_mask, sam_mask, result_image, markdown_message].
28
- """
29
- # Check if both images are provided
30
- if image_editor["background"] is None or replacement_image is None:
31
- return [None, None, None, "**❌ Error:** Please upload both images."]
32
- try:
33
- # Extract the original image and the user-drawn mask
34
- original_image = image_editor["background"]
35
- drawn_mask = image_editor["layers"][0]
36
-
37
- # Use the alpha channel of the mask as the binary mask
38
- drawn_mask = drawn_mask.split()[-1]
39
- drawn_mask_np = np.array(drawn_mask)
40
 
41
- # Find contours in the mask to determine segmentation points
42
- points = []
43
- contours, _ = cv2.findContours(drawn_mask_np, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
44
- for contour in contours:
45
- M = cv2.moments(contour)
46
- if M["m00"] != 0:
47
- # Use centroid of contour as a point
48
- cx = float(M["m10"] / M["m00"])
49
- cy = float(M["m01"] / M["m00"])
50
- points.append([cx, cy])
51
- else:
52
- # Fallback: use the first point if the area is zero
53
- x, y = contour[0][0]
54
- points.append([float(x), float(y)])
 
 
 
 
 
 
 
 
 
 
55
 
56
- # If no points are found, return original image and a message indicating no mask was drawn
57
- if not points:
58
- return [None, None, original_image, "**❌ Error:** No mask drawn. Please draw a mask on the original image."]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- # If the SAM2 model is unavailable, use the drawn mask directly
61
- if not self.model_available or not self.model:
62
- sam_mask = drawn_mask
63
- model_message = "**⚠️ Warning:** SAM2 model unavailable, using drawn mask as mask."
64
- else:
65
- # Run the SAM2 model to refine the mask
66
- results = self.model(
67
- source=original_image,
68
- points=[points],
69
- )
70
- # Extract the mask from the model output
71
- result_numpy_arr = results[0].masks.data.numpy()
72
- sam_mask_arr = np.squeeze(result_numpy_arr)
73
- sam_mask_arr = (sam_mask_arr * 255).astype(np.uint8) # Convert bool to uint8
74
- sam_mask = Image.fromarray(sam_mask_arr)
75
- model_message = "**✅ Success:** Segmentation completed with SAM2."
76
-
77
- # Resize the replacement image to match the original image size
78
- replacement_image = replacement_image.resize(original_image.size)
79
- # Composite the replacement image onto the original using the mask
80
- result_image = Image.composite(replacement_image, original_image, sam_mask)
81
 
82
- return [drawn_mask, sam_mask, result_image, model_message]
83
- except Exception as e:
84
- # Catch and report any errors during segmentation
85
- print(f"Segmentation error: {e}")
86
- return [None, None, None, f"**❌ Error:** Segmentation error: {e}"]
87
 
88
- def create_interface(self) -> gr.Blocks:
89
- """Create and return the Gradio interface"""
90
- with gr.Blocks(title="SAM2 Image Segmentation & Replacement", theme=gr.themes.Soft(), css=".center-status-message {text-align: center;}") as demo:
91
- # App title and instructions
92
- gr.Markdown(
93
- f"""
94
- # 🎨 SAM2 Image Segmentation & Replacement
95
-
96
- Upload an original image and a replacement image, then draw a rough mask on the original image.
97
-
98
- **Instructions:**
99
- 1. Upload your original image
100
- 2. Upload your replacement image
101
- 3. Draw a mask on the original image by painting over the area you want to replace
102
- 4. Click "Process Segmentation" to see the result
103
- """
104
- )
105
- gr.Markdown("### 📸 Upload Images")
106
- with gr.Row():
107
- with gr.Column():
108
- # ImageMask for original image and mask drawing
109
- image_editor = gr.ImageMask(
110
- label="Original Image",
111
- type="pil",
112
- height=400
113
- )
114
- with gr.Column():
115
- # Upload for replacement image
116
- replacement_image = gr.Image(
117
- label="Replacement Image",
118
- type="pil",
119
- height=400
120
- )
121
- with gr.Row():
122
- # Button to trigger segmentation
123
- process_btn = gr.Button("🚀 Process Segmentation", variant="primary", size="lg")
124
- with gr.Row():
125
- # Status message for feedback
126
- status_message = gr.Markdown(value="", elem_id="status_message", elem_classes=["center-status-message"])
127
- with gr.Row():
128
- # Display the drawn mask, SAM2 mask, and result image
129
- drawn_mask = gr.Image(
130
- label="Drawn Mask",
131
  type="pil",
132
  height=400
133
  )
134
- result_mask = gr.Image(
135
- label="SAM2 Mask",
 
136
  type="pil",
137
  height=400
138
  )
139
- result_image = gr.Image(
140
- label="Result",
141
- type="pil",
142
- height=400
143
- )
144
- with gr.Row():
145
- # Display copywrite information
146
- gr.Markdown(
147
- value="© 2025 Kenny Santanu. All rights reserved.",
148
- elem_classes=["center-status-message"]
149
- )
150
-
151
- # Connect button click to segmentation function
152
- process_btn.click(
153
- fn=self.process_segmentation,
154
- inputs=[image_editor, replacement_image],
155
- outputs=[drawn_mask, result_mask, result_image, status_message]
156
- )
157
- return demo
158
 
159
- def main() -> None:
160
- """Main function to run the application"""
161
- # Instantiate the app
162
- app = ImageSegmentationApp()
163
- # Create the Gradio interface
164
- demo = app.create_interface()
165
- # Launch the interface (web server)
166
- demo.launch(
167
- show_api=False
168
- )
169
 
170
- # Run the app if this script is executed directly
171
  if __name__ == "__main__":
172
- main()
 
1
  import gradio as gr
2
  import numpy as np
 
3
  from PIL import Image
 
4
 
5
+ def get_mask_bbox(mask: Image.Image):
6
+ """احصل على إطار القناع (Bounding Box) كـ (xmin, ymin, xmax, ymax)"""
7
+ mask_np = np.array(mask)
8
+ if len(mask_np.shape) == 3:
9
+ mask_np = mask_np[..., -1] # قناة ألفا
10
+ ys, xs = np.where(mask_np > 0)
11
+ if ys.size == 0 or xs.size == 0:
12
+ return None
13
+ return int(xs.min()), int(ys.min()), int(xs.max()), int(ys.max())
 
 
 
14
 
15
+ def crop_masked_object(img: Image.Image, mask: Image.Image):
16
+ """اقطع المنطقة المحددة من الصورة حسب القناع وأرجعها"""
17
+ bbox = get_mask_bbox(mask)
18
+ if bbox is None:
19
+ return None, None
20
+ xmin, ymin, xmax, ymax = bbox
21
+ # قص الصورة والقناع للمنطقة المحددة فقط
22
+ cropped_img = img.crop((xmin, ymin, xmax + 1, ymax + 1))
23
+ cropped_mask = mask.crop((xmin, ymin, xmax + 1, ymax + 1))
24
+ # طبّق القناع على الجزء المقصوص (شفافية)
25
+ cropped_img.putalpha(cropped_mask.split()[-1])
26
+ return cropped_img, bbox
 
 
 
 
 
 
 
 
27
 
28
+ def paste_object_on_mask(
29
+ base_img: Image.Image,
30
+ base_mask: Image.Image,
31
+ obj_img: Image.Image,
32
+ obj_bbox,
33
+ ):
34
+ """الصق الجزء في منطقة القناع على الصورة الأساسية مع التحجيم والمحاذاة"""
35
+ base_bbox = get_mask_bbox(base_mask)
36
+ if base_bbox is None or obj_bbox is None:
37
+ return None
38
+ bxmin, bymin, bxmax, bymax = base_bbox
39
+ ow, oh = obj_img.size
40
+ # حجم منطقة اللصق في الصورة الأصلية
41
+ bw, bh = bxmax - bxmin + 1, bymax - bymin + 1
42
+ # غيّر حجم الجزء المقصوص ليناسب منطقة اللصق
43
+ obj_img_resized = obj_img.resize((bw, bh), resample=Image.LANCZOS)
44
+ # لصق الجزء على الصورة الأصلية في مكان القناع
45
+ result = base_img.convert("RGBA")
46
+ mask = base_mask.split()[-1]
47
+ empty = Image.new("RGBA", result.size, (0, 0, 0, 0))
48
+ empty.paste(obj_img_resized, (bxmin, bymin), obj_img_resized)
49
+ # الدمج: الجزء الجديد مكان القناع فقط
50
+ result = Image.composite(empty, result, mask)
51
+ return result
52
 
53
+ def process_transfer(
54
+ image1_editor, # dict: {"background": Image, "layers": [Image,...]}
55
+ image2_editor # dict: {"background": Image, "layers": [Image,...]}
56
+ ):
57
+ # التحقق من المدخلات
58
+ if not image1_editor or not image2_editor:
59
+ return None, None, None, "**❌ يرجى رفع الصورتين وتحديد الأقنعة."
60
+ if (image1_editor.get("background") is None or
61
+ not image1_editor.get("layers") or
62
+ image2_editor.get("background") is None or
63
+ not image2_editor.get("layers")
64
+ ):
65
+ return None, None, None, "**❌ يجب رسم قناع على كلتا الصورتين."
66
+ base_img = image1_editor["background"].convert("RGBA")
67
+ base_mask = image1_editor["layers"][0].convert("RGBA")
68
+ obj_img = image2_editor["background"].convert("RGBA")
69
+ obj_mask = image2_editor["layers"][0].convert("RGBA")
70
 
71
+ # قص الجزء المحدد من الصورة الثانية
72
+ cropped_obj, obj_bbox = crop_masked_object(obj_img, obj_mask)
73
+ if cropped_obj is None:
74
+ return None, None, None, "**❌ لم يتم تحديد أي كائن في الصورة الثانية."
75
+ # لصق الجزء في منطقة القناع بالصورة الأولى
76
+ result_img = paste_object_on_mask(base_img, base_mask, cropped_obj, obj_bbox)
77
+ if result_img is None:
78
+ return None, None, None, "**❌ لم يتم تحديد منطقة مناسبة للصق في الصورة الأولى."
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
+ return base_mask, obj_mask, result_img, "**✅ تم نقل الكائن بنجاح!"
 
 
 
 
81
 
82
+ def create_interface():
83
+ with gr.Blocks(title="نقل كائن من صورة إلى أخرى مع القناع والمحاذاة") as demo:
84
+ gr.Markdown("""
85
+ # 🖼️ نقل كائن من صورة إلى أخرى مع القناع والمحاذاة
86
+
87
+ 1. ارفع الصورة الأولى (صورة الخلفية).
88
+ 2. ارفع الصورة الثانية (الصورة التي بها الكائن).
89
+ 3. ارسم قناعًا على الكائن في الصورة الثانية (صورة الاستبدال).
90
+ 4. ارسم قناعًا في مكان اللصق في الصورة الأولى.
91
+ 5. اضغط زر "نقل الكائن" وستظهر النتيجة!
92
+ """)
93
+ with gr.Row():
94
+ with gr.Column():
95
+ image1_editor = gr.ImageMask(
96
+ label="الصورة الأولى (اختر مكان اللصق)",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  type="pil",
98
  height=400
99
  )
100
+ with gr.Column():
101
+ image2_editor = gr.ImageMask(
102
+ label="الصورة الثانية (حدد الكائن المراد نقله)",
103
  type="pil",
104
  height=400
105
  )
106
+ with gr.Row():
107
+ transfer_btn = gr.Button("🚀 نقل الكائن", variant="primary")
108
+ with gr.Row():
109
+ base_mask = gr.Image(label="قناع الصورة الأولى", type="pil", height=200)
110
+ obj_mask = gr.Image(label="قناع الصورة الثانية", type="pil", height=200)
111
+ result_img = gr.Image(label="النتيجة النهائية", type="pil", height=400)
112
+ status = gr.Markdown()
113
+ transfer_btn.click(
114
+ fn=process_transfer,
115
+ inputs=[image1_editor, image2_editor],
116
+ outputs=[base_mask, obj_mask, result_img, status]
117
+ )
118
+ return demo
 
 
 
 
 
 
119
 
120
+ def main():
121
+ demo = create_interface()
122
+ demo.launch()
 
 
 
 
 
 
 
123
 
 
124
  if __name__ == "__main__":
125
+ main()