clockclock commited on
Commit
7709c11
·
verified ·
1 Parent(s): aa271fe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +188 -86
app.py CHANGED
@@ -1,105 +1,207 @@
1
- # app.py
2
-
3
  import gradio as gr
4
- import torch
5
- from diffusers import AutoPipelineForInpainting
6
  from PIL import Image
7
- import time
8
-
9
- # --- Model Loading (Final, Most Stable Version) ---
10
- print("Loading the definitive model for low-RAM CPU environment...")
11
- # We are using the more modern and reliable SD 2.0 Inpainting model.
12
- # This model is better packaged and less prone to loading errors.
13
- model_id = "stabilityai/stable-diffusion-2-inpainting"
14
-
15
- try:
16
- pipe = AutoPipelineForInpainting.from_pretrained(
17
- model_id,
18
- torch_dtype=torch.float32, # Use float32 for CPU compatibility
19
- safety_checker=None # Proactively disable the safety checker to save memory
20
- )
21
-
22
- # Enable CPU offloading to prevent memory crashes. This is essential.
23
- pipe.enable_model_cpu_offload()
24
-
25
- print("Model loaded successfully. The application is ready.")
26
 
27
- except Exception as e:
28
- print("="*80)
29
- print("A FATAL ERROR OCCURRED DURING MODEL LOADING. The app cannot start.")
30
- print(f"Error: {e}")
31
- print("This is likely due to the free hardware tier not having enough resources.")
32
- print("="*80)
33
- raise e
34
 
 
 
 
35
 
36
- # --- Default "Magic" Prompts ---
37
- DEFAULT_PROMPT = "photorealistic, 4k, ultra high quality, sharp focus, masterpiece, high detail, professional photo"
38
- DEFAULT_NEGATIVE_PROMPT = "blurry, pixelated, distorted, deformed, ugly, disfigured, cartoon, anime, low quality, watermark, text"
39
 
40
- # --- The Inpainting Function ---
41
- # This function signature is correct for how Gradio's Image tool works.
42
- def inpaint_image(image_and_mask, user_prompt, guidance_scale, num_steps, progress=gr.Progress(track_tqdm=True)):
43
-
44
- if image_and_mask is None or "image" not in image_and_mask or "mask" not in image_and_mask:
45
- raise gr.Error("Please upload an image and draw a mask on it first!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
- # The input is a dictionary with 'image' and 'mask' keys
48
- image = image_and_mask["image"].convert("RGB")
49
- mask = image_and_mask["mask"].convert("RGB")
50
 
51
- if user_prompt and user_prompt.strip():
52
- prompt = user_prompt
53
- negative_prompt = DEFAULT_NEGATIVE_PROMPT
54
- print(f"Using custom prompt: '{prompt}'")
55
- else:
56
- prompt = DEFAULT_PROMPT
57
- negative_prompt = DEFAULT_NEGATIVE_PROMPT
58
- print(f"User prompt is empty. Using default 'General Fix' prompt.")
59
 
60
- print(f"Starting inpainting on CPU (with offloading)... This will be very slow.")
61
- start_time = time.time()
 
62
 
63
- result_image = pipe(
64
- prompt=prompt,
65
- image=image,
66
- mask_image=mask,
67
- negative_prompt=negative_prompt,
68
- guidance_scale=guidance_scale,
69
- num_inference_steps=int(num_steps),
70
- ).images[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- end_time = time.time()
73
- print(f"Inpainting finished in {end_time - start_time:.2f} seconds.")
74
- return result_image
75
-
76
-
77
- # --- Gradio User Interface ---
78
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
79
- gr.Markdown("# 🎨 AI Image Fixer (Definitive Version)")
80
- gr.Warning(
81
- "‼️ **PATIENCE REQUIRED!** This app is running on a free CPU. "
82
- "Generation will be **extremely slow (potentially 20-40 minutes)** due to memory-saving measures. "
83
- "This is necessary to prevent crashes. The progress bar will appear after you click the button."
84
- )
85
 
86
  with gr.Row():
87
- with gr.Column(scale=2):
88
- input_image = gr.Image(label="1. Upload & Mask Image", source="upload", tool="brush", type="pil")
89
- prompt_textbox = gr.Textbox(label="2. Describe Your Fix (Optional)", placeholder="Leave empty for a general fix")
90
- with gr.Accordion("Advanced Settings", open=False):
91
- guidance_scale = gr.Slider(minimum=0, maximum=20, value=8.0, label="Guidance Scale")
92
- num_steps = gr.Slider(minimum=10, maximum=50, step=1, value=20, label="Inference Steps (Fewer is faster)")
93
  with gr.Column(scale=1):
94
- output_image = gr.Image(label="Result", type="pil")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
- submit_button = gr.Button("Fix It!", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- submit_button.click(
99
- fn=inpaint_image,
100
- inputs=[input_image, prompt_textbox, guidance_scale, num_steps],
101
- outputs=output_image
102
  )
103
 
 
 
 
 
 
 
104
  if __name__ == "__main__":
105
- demo.launch()
 
 
 
 
1
  import gradio as gr
2
+ import cv2
3
+ import numpy as np
4
  from PIL import Image
5
+ import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
+ # Load the Haar Cascade classifier for face detection
8
+ face_cascade_path = os.path.join(os.path.dirname(__file__), "haarcascade_frontalface_default.xml")
9
+ face_cascade = cv2.CascadeClassifier(face_cascade_path)
 
 
 
 
10
 
11
+ def process_image(image, x, y, effect_type):
12
+ if image is None:
13
+ return None, "Please upload an image first."
14
 
15
+ img_np = np.array(image)
16
+ img_np_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
17
+ processed_img_np_bgr = img_np_bgr.copy()
18
 
19
+ gray = cv2.cvtColor(img_np_bgr, cv2.COLOR_BGR2GRAY)
20
+ faces = face_cascade.detectMultiScale(gray, 1.1, 4)
21
+
22
+ target_roi = None
23
+ target_x, target_y, target_w, target_h = None, None, None, None
24
+ status_message = ""
25
+
26
+ # Find the face closest to the clicked coordinates
27
+ if x is not None and y is not None:
28
+ min_distance = float('inf')
29
+ for (fx, fy, fw, fh) in faces:
30
+ # Calculate center of the face
31
+ face_center_x = fx + fw // 2
32
+ face_center_y = fy + fh // 2
33
+ distance = np.sqrt((face_center_x - x)**2 + (face_center_y - y)**2)
34
+ if distance < min_distance and distance < 100: # Only consider faces within 100 pixels
35
+ min_distance = distance
36
+ target_x, target_y, target_w, target_h = fx, fy, fw, fh
37
+
38
+ if target_x is not None:
39
+ # Apply effect to the detected face
40
+ roi = processed_img_np_bgr[target_y:target_y+target_h, target_x:target_x+target_w]
41
+ status_message = f"Applied {effect_type} effect to detected face."
42
+
43
+ if effect_type == "blur":
44
+ processed_roi = cv2.GaussianBlur(roi, (35, 35), 0)
45
+ elif effect_type == "sharpen":
46
+ kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
47
+ processed_roi = cv2.filter2D(roi, -1, kernel)
48
+ elif effect_type == "grayscale":
49
+ processed_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
50
+ processed_roi = cv2.cvtColor(processed_roi, cv2.COLOR_GRAY2BGR)
51
+ elif effect_type == "pixelate":
52
+ h, w = roi.shape[:2]
53
+ temp = cv2.resize(roi, (w//10, h//10), interpolation=cv2.INTER_LINEAR)
54
+ processed_roi = cv2.resize(temp, (w, h), interpolation=cv2.INTER_NEAREST)
55
+ else:
56
+ processed_roi = roi # No effect
57
+
58
+ processed_img_np_bgr[target_y:target_y+target_h, target_x:target_x+target_w] = processed_roi
59
+ elif x is not None and y is not None: # If no face detected near click, apply to a general region
60
+ region_size = 100
61
+ x1 = max(0, x - region_size // 2)
62
+ y1 = max(0, y - region_size // 2)
63
+ x2 = min(image.width, x + region_size // 2)
64
+ y2 = min(image.height, y + region_size // 2)
65
+
66
+ roi = processed_img_np_bgr[y1:y2, x1:x2]
67
+ status_message = f"Applied {effect_type} effect to clicked region."
68
+
69
+ if effect_type == "blur":
70
+ processed_roi = cv2.GaussianBlur(roi, (15, 15), 0)
71
+ elif effect_type == "sharpen":
72
+ kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
73
+ processed_roi = cv2.filter2D(roi, -1, kernel)
74
+ elif effect_type == "grayscale":
75
+ processed_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
76
+ processed_roi = cv2.cvtColor(processed_roi, cv2.COLOR_GRAY2BGR)
77
+ elif effect_type == "pixelate":
78
+ h, w = roi.shape[:2]
79
+ temp = cv2.resize(roi, (w//10, h//10), interpolation=cv2.INTER_LINEAR)
80
+ processed_roi = cv2.resize(temp, (w, h), interpolation=cv2.INTER_NEAREST)
81
+ else:
82
+ processed_roi = roi # No effect
83
+
84
+ processed_img_np_bgr[y1:y1+roi.shape[0], x1:x1+roi.shape[1]] = processed_roi
85
+ else:
86
+ status_message = "Please click on the image to select a region."
87
+
88
+ img_pil = Image.fromarray(cv2.cvtColor(processed_img_np_bgr, cv2.COLOR_BGR2RGB))
89
+ return img_pil, status_message
90
+
91
+ def detect_faces_only(image):
92
+ if image is None:
93
+ return None, "Please upload an image first."
94
 
95
+ img_np = np.array(image)
96
+ img_np_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
 
97
 
98
+ gray = cv2.cvtColor(img_np_bgr, cv2.COLOR_BGR2GRAY)
99
+ faces = face_cascade.detectMultiScale(gray, 1.1, 4)
 
 
 
 
 
 
100
 
101
+ # Draw rectangles around detected faces
102
+ for (x, y, w, h) in faces:
103
+ cv2.rectangle(img_np_bgr, (x, y), (x+w, y+h), (255, 0, 0), 2)
104
 
105
+ img_pil = Image.fromarray(cv2.cvtColor(img_np_bgr, cv2.COLOR_BGR2RGB))
106
+ return img_pil, f"Detected {len(faces)} face(s)."
107
+
108
+ # Custom CSS for better styling
109
+ css = """
110
+ .gradio-container {
111
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
112
+ }
113
+ .main-header {
114
+ text-align: center;
115
+ color: #2c3e50;
116
+ margin-bottom: 20px;
117
+ }
118
+ .instruction-text {
119
+ background-color: #f8f9fa;
120
+ padding: 15px;
121
+ border-radius: 8px;
122
+ border-left: 4px solid #007bff;
123
+ margin-bottom: 20px;
124
+ }
125
+ """
126
+
127
+ # Gradio interface
128
+ with gr.Blocks(css=css, title="AI Image Editor") as demo:
129
+ gr.HTML("<h1 class='main-header'>🎨 AI Image Editor (CPU-friendly)</h1>")
130
 
131
+ gr.HTML("""
132
+ <div class='instruction-text'>
133
+ <strong>Instructions:</strong>
134
+ <ol>
135
+ <li>Upload an image using the file uploader</li>
136
+ <li>Click on any part of the image to select a region</li>
137
+ <li>Choose an effect from the dropdown menu</li>
138
+ <li>Click "Apply Effect" to process the selected region</li>
139
+ <li>Use "Detect Faces" to see all detected faces with blue rectangles</li>
140
+ </ol>
141
+ <em>Note: The app will prioritize faces near your click location, or apply effects to a general region if no face is detected nearby.</em>
142
+ </div>
143
+ """)
144
 
145
  with gr.Row():
 
 
 
 
 
 
146
  with gr.Column(scale=1):
147
+ input_image = gr.Image(
148
+ type="pil",
149
+ label="📁 Upload Image",
150
+ interactive=True,
151
+ height=400
152
+ )
153
+
154
+ with gr.Row():
155
+ effect_dropdown = gr.Dropdown(
156
+ ["None", "blur", "sharpen", "grayscale", "pixelate"],
157
+ label="🎭 Select Effect",
158
+ value="blur"
159
+ )
160
+
161
+ with gr.Row():
162
+ process_button = gr.Button("✨ Apply Effect", variant="primary", size="lg")
163
+ detect_button = gr.Button("👤 Detect Faces", variant="secondary", size="lg")
164
+
165
+ status_text = gr.Textbox(
166
+ label="📊 Status",
167
+ interactive=False,
168
+ placeholder="Ready to process..."
169
+ )
170
+
171
+ with gr.Column(scale=1):
172
+ output_image = gr.Image(
173
+ type="pil",
174
+ label="🖼️ Processed Image",
175
+ height=400
176
+ )
177
 
178
+ # Store click coordinates
179
+ clicked_x = gr.State(None)
180
+ clicked_y = gr.State(None)
181
+
182
+ def get_coords(evt: gr.SelectData):
183
+ return evt.index[0], evt.index[1]
184
+
185
+ input_image.select(get_coords, None, [clicked_x, clicked_y])
186
+
187
+ process_button.click(
188
+ fn=process_image,
189
+ inputs=[input_image, clicked_x, clicked_y, effect_dropdown],
190
+ outputs=[output_image, status_text]
191
+ )
192
 
193
+ detect_button.click(
194
+ fn=detect_faces_only,
195
+ inputs=[input_image],
196
+ outputs=[output_image, status_text]
197
  )
198
 
199
+ gr.HTML("""
200
+ <div style='text-align: center; margin-top: 20px; color: #6c757d;'>
201
+ <p>Built with ❤️ for CPU-friendly image processing | Powered by OpenCV & Gradio</p>
202
+ </div>
203
+ """)
204
+
205
  if __name__ == "__main__":
206
+ demo.launch()
207
+