gk2291 commited on
Commit
54c43f9
·
verified ·
1 Parent(s): 1b482f0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -308
app.py CHANGED
@@ -1,30 +1,29 @@
1
  import gradio as gr
2
  import spaces
3
- from PIL import Image, ImageEnhance, ImageFilter
4
  import numpy as np
5
  import logging
6
  import cv2
7
  from rembg import remove
8
- import io
9
 
10
  # Setup logging
11
  logging.basicConfig(level=logging.INFO)
12
  logger = logging.getLogger(__name__)
13
 
14
- logger.info("🚀 Initializing Mirro Virtual Try-On Server...")
15
 
16
  def create_clothing_mask(image):
17
  """Create mask for upper body clothing area"""
18
  width, height = image.size
19
  mask = np.zeros((height, width), dtype=np.uint8)
20
 
21
- # Define upper body region
22
  y_start = int(height * 0.15)
23
  y_end = int(height * 0.70)
24
  x_start = int(width * 0.20)
25
  x_end = int(width * 0.80)
26
 
27
- # Create elliptical mask for more natural look
28
  center_x = (x_start + x_end) // 2
29
  center_y = (y_start + y_end) // 2
30
  radius_x = (x_end - x_start) // 2
@@ -41,22 +40,19 @@ def create_clothing_mask(image):
41
 
42
  def enhance_image(image):
43
  """Enhance image quality"""
44
- # Increase sharpness
45
  enhancer = ImageEnhance.Sharpness(image)
46
  image = enhancer.enhance(1.3)
47
 
48
- # Increase contrast slightly
49
  enhancer = ImageEnhance.Contrast(image)
50
  image = enhancer.enhance(1.1)
51
 
52
- # Increase color saturation
53
  enhancer = ImageEnhance.Color(image)
54
  image = enhancer.enhance(1.1)
55
 
56
  return image
57
 
58
  def remove_garment_background(garment_img):
59
- """Remove background from garment image"""
60
  try:
61
  logger.info("Removing garment background...")
62
  output = remove(garment_img)
@@ -66,16 +62,13 @@ def remove_garment_background(garment_img):
66
  return garment_img
67
 
68
  def blend_images(person_img, garment_img, mask):
69
- """Blend garment onto person with smooth transitions"""
70
- # Convert to numpy arrays
71
  person_array = np.array(person_img).astype(float)
72
  garment_array = np.array(garment_img).astype(float)
73
  mask_array = np.array(mask).astype(float) / 255.0
74
 
75
- # Expand mask to RGB
76
  mask_3ch = np.stack([mask_array] * 3, axis=2)
77
 
78
- # Blend
79
  blended = (garment_array * mask_3ch + person_array * (1 - mask_3ch))
80
 
81
  return Image.fromarray(blended.astype(np.uint8))
@@ -86,14 +79,11 @@ def match_colors(person_img, garment_img, mask):
86
  garment_array = np.array(garment_img)
87
  mask_array = np.array(mask) / 255.0
88
 
89
- # Calculate average color in non-masked region of person
90
  person_sample = person_array * (1 - mask_array[:, :, np.newaxis])
91
  person_mean = np.mean(person_sample[person_sample > 0])
92
 
93
- # Calculate average color of garment
94
  garment_mean = np.mean(garment_array)
95
 
96
- # Adjust garment brightness to match person
97
  if garment_mean > 0:
98
  adjustment = person_mean / garment_mean
99
  garment_adjusted = np.clip(garment_array * adjustment, 0, 255).astype(np.uint8)
@@ -103,58 +93,44 @@ def match_colors(person_img, garment_img, mask):
103
 
104
  @spaces.GPU(duration=60)
105
  def virtual_tryon(person_image, garment_image, progress=gr.Progress()):
106
- """
107
- Virtual try-on function
108
- """
109
  try:
110
- # Validation
111
  if person_image is None:
112
  raise gr.Error("❌ Please upload a person's photo!")
113
 
114
  if garment_image is None:
115
  raise gr.Error("❌ Please upload a garment photo!")
116
 
117
- logger.info("=" * 50)
118
- logger.info("🎯 Starting Virtual Try-On Process")
119
- logger.info(f"Person: {person_image.size}, Garment: {garment_image.size}")
120
 
121
- # Step 1: Preprocess
122
- progress(0.1, desc="📸 Preprocessing images...")
123
  person_img = person_image.convert("RGB")
124
  garment_img = garment_image.convert("RGB")
125
 
126
- # Resize to standard size
127
  target_size = (512, 768)
128
  person_img = person_img.resize(target_size, Image.Resampling.LANCZOS)
129
  garment_img = garment_img.resize(target_size, Image.Resampling.LANCZOS)
130
 
131
- # Step 2: Remove garment background
132
  progress(0.3, desc="🎨 Processing garment...")
133
  garment_nobg = remove_garment_background(garment_img)
134
 
135
- # Step 3: Enhance garment
136
- progress(0.4, desc="✨ Enhancing garment...")
137
  garment_enhanced = enhance_image(garment_nobg)
138
 
139
- # Step 4: Create mask
140
- progress(0.5, desc="🎯 Creating clothing mask...")
141
  mask = create_clothing_mask(person_img)
142
 
143
- # Step 5: Match colors
144
  progress(0.6, desc="🎨 Matching colors...")
145
  garment_matched = match_colors(person_img, garment_enhanced, mask)
146
 
147
- # Step 6: Blend
148
- progress(0.8, desc="✨ Applying garment...")
149
  result = blend_images(person_img, garment_matched, mask)
150
 
151
- # Step 7: Final enhancement
152
  progress(0.9, desc="🎨 Final touches...")
153
  result = enhance_image(result)
154
 
155
  progress(1.0, desc="✅ Complete!")
156
- logger.info("✅ Virtual try-on completed successfully!")
157
- logger.info("=" * 50)
158
 
159
  return result
160
 
@@ -162,293 +138,94 @@ def virtual_tryon(person_image, garment_image, progress=gr.Progress()):
162
  logger.error(f"❌ Error: {str(e)}")
163
  import traceback
164
  traceback.print_exc()
165
- raise gr.Error(f"❌ Processing failed: {str(e)}")
166
 
167
- # Custom CSS
168
- custom_css = """
169
- #col-container {
170
- margin: 0 auto;
171
- max-width: 1400px;
172
- }
173
- .gradio-container {
174
- font-family: 'Inter', sans-serif;
175
- }
176
- .gr-button-primary {
177
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
178
  border: none !important;
179
- font-weight: 600 !important;
180
- }
181
- .gr-button-primary:hover {
182
- transform: translateY(-2px);
183
- box-shadow: 0 10px 20px rgba(0,0,0,0.2);
184
- transition: all 0.3s;
185
  }
186
  footer {display: none !important;}
187
  """
188
 
189
- # Gradio Interface
190
- with gr.Blocks(css=custom_css, title="Mirro Virtual Try-On", theme=gr.themes.Soft()) as demo:
 
 
 
 
 
 
191
 
192
- with gr.Column(elem_id="col-container"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
- # Header
 
 
 
195
  gr.Markdown("""
196
- # 👗 Mirro Virtual Try-On API
197
- ### AI-Powered Virtual Clothing Try-On for iOS Apps
198
-
199
- Upload a person's photo and a garment image to generate realistic virtual try-on results.
200
- Perfect for fashion e-commerce, styling apps, and virtual wardrobes!
201
  """)
 
 
 
 
202
 
203
- gr.Markdown("🟢 **Status:** Ready to process images!")
204
-
205
- # Main Interface
206
- with gr.Row():
207
- # Left - Inputs
208
- with gr.Column(scale=1):
209
- gr.Markdown("### 📤 Upload Images")
210
-
211
- person_input = gr.Image(
212
- label="👤 Person Photo",
213
- type="pil",
214
- sources=["upload", "clipboard"],
215
- height=400
216
- )
217
-
218
- garment_input = gr.Image(
219
- label="👔 Garment Photo",
220
- type="pil",
221
- sources=["upload", "clipboard"],
222
- height=400
223
- )
224
-
225
- with gr.Row():
226
- clear_btn = gr.ClearButton(
227
- components=[person_input, garment_input],
228
- value="🗑️ Clear All",
229
- scale=1
230
- )
231
- submit_btn = gr.Button(
232
- "✨ Generate Try-On",
233
- variant="primary",
234
- scale=2,
235
- size="lg"
236
- )
237
-
238
- # Right - Output
239
- with gr.Column(scale=1):
240
- gr.Markdown("### 🎯 Try-On Result")
241
-
242
- output_image = gr.Image(
243
- label="Virtual Try-On Result",
244
- type="pil",
245
- height=850
246
- )
247
-
248
- # Tips Section
249
- with gr.Accordion("💡 Tips for Best Results", open=False):
250
- gr.Markdown("""
251
- ### Person Photo Tips:
252
- - ✅ Use clear, well-lit photos
253
- - ✅ Person should face forward
254
- - ✅ Upper body should be fully visible
255
- - ✅ Plain or simple backgrounds work best
256
- - ✅ Avoid busy patterns on current clothing
257
-
258
- ### Garment Photo Tips:
259
- - ✅ Clear photo of the garment (flat lay or on hanger)
260
- - ✅ Good lighting showing fabric details
261
- - ✅ Full garment visible
262
- - ✅ Plain background preferred
263
- - ✅ No models wearing the garment (for best results)
264
-
265
- ### Expected Results:
266
- - ⏱️ Processing time: 10-20 seconds
267
- - 📐 Output resolution: 512x768 pixels
268
- - 🎨 Realistic blending with natural lighting
269
- """)
270
-
271
- # API Documentation
272
- with gr.Accordion("📱 iOS App Integration Guide", open=False):
273
- gr.Markdown("""
274
- ## REST API Endpoint
275
- ```
276
- POST https://gk2291-mirro-app-server.hf.space/api/predict
277
- ```
278
-
279
- ### Request Format (JSON)
280
  ```json
281
- {
282
- "data": [
283
- "data:image/jpeg;base64,<person_image_base64>",
284
- "data:image/jpeg;base64,<garment_image_base64>"
285
- ]
286
- }
287
  ```
288
-
289
- ### Response Format (JSON)
290
  ```json
291
- {
292
- "data": ["data:image/png;base64,<result_image_base64>"],
293
- "duration": 15.3
294
- }
295
  ```
296
-
297
- ### Swift Implementation
298
- ```swift
299
- import Foundation
300
- import UIKit
301
-
302
- class MirroAPI {
303
- static let shared = MirroAPI()
304
- private let baseURL = "https://gk2291-mirro-app-server.hf.space"
305
-
306
- func virtualTryOn(personImage: UIImage,
307
- garmentImage: UIImage,
308
- completion: @escaping (Result<UIImage, Error>) -> Void) {
309
-
310
- guard let url = URL(string: "\\(baseURL)/api/predict") else {
311
- completion(.failure(NSError(domain: "Invalid URL", code: -1)))
312
- return
313
- }
314
-
315
- var request = URLRequest(url: url)
316
- request.httpMethod = "POST"
317
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
318
- request.timeoutInterval = 120 // 2 minutes timeout
319
-
320
- // Convert images to base64
321
- guard let personData = personImage.jpegData(compressionQuality: 0.85),
322
- let garmentData = garmentImage.jpegData(compressionQuality: 0.85) else {
323
- completion(.failure(NSError(domain: "Image conversion failed", code: -1)))
324
- return
325
- }
326
-
327
- let personB64 = "data:image/jpeg;base64,\\(personData.base64EncodedString())"
328
- let garmentB64 = "data:image/jpeg;base64,\\(garmentData.base64EncodedString())"
329
-
330
- let payload: [String: Any] = [
331
- "data": [personB64, garmentB64]
332
- ]
333
-
334
- request.httpBody = try? JSONSerialization.data(withJSONObject: payload)
335
-
336
- URLSession.shared.dataTask(with: request) { data, response, error in
337
- if let error = error {
338
- completion(.failure(error))
339
- return
340
- }
341
-
342
- guard let data = data,
343
- let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
344
- let dataArray = json["data"] as? [String],
345
- let imageBase64 = dataArray.first else {
346
- completion(.failure(NSError(domain: "Parse error", code: -1)))
347
- return
348
- }
349
-
350
- // Extract base64 string
351
- let base64String = imageBase64.components(separatedBy: ",").last ?? ""
352
-
353
- guard let imageData = Data(base64Encoded: base64String),
354
- let image = UIImage(data: imageData) else {
355
- completion(.failure(NSError(domain: "Image decode error", code: -1)))
356
- return
357
- }
358
-
359
- completion(.success(image))
360
- }.resume()
361
- }
362
- }
363
-
364
- // Usage Example
365
- MirroAPI.shared.virtualTryOn(
366
- personImage: selectedPersonImage,
367
- garmentImage: selectedGarmentImage
368
- ) { result in
369
- DispatchQueue.main.async {
370
- switch result {
371
- case .success(let tryOnImage):
372
- self.resultImageView.image = tryOnImage
373
- print("✅ Try-on successful!")
374
- case .failure(let error):
375
- self.showError(error.localizedDescription)
376
- print("❌ Error: \\(error)")
377
- }
378
- }
379
- }
380
- ```
381
-
382
- ### SwiftUI Example
383
- ```swift
384
- import SwiftUI
385
-
386
- struct TryOnView: View {
387
- @State private var personImage: UIImage?
388
- @State private var garmentImage: UIImage?
389
- @State private var resultImage: UIImage?
390
- @State private var isLoading = false
391
-
392
- var body: some View {
393
- VStack {
394
- HStack {
395
- ImagePicker(image: $personImage, title: "Person")
396
- ImagePicker(image: $garmentImage, title: "Garment")
397
- }
398
-
399
- Button("Try On") {
400
- guard let person = personImage,
401
- let garment = garmentImage else { return }
402
-
403
- isLoading = true
404
- MirroAPI.shared.virtualTryOn(
405
- personImage: person,
406
- garmentImage: garment
407
- ) { result in
408
- isLoading = false
409
- if case .success(let image) = result {
410
- resultImage = image
411
- }
412
- }
413
- }
414
- .disabled(isLoading || personImage == nil || garmentImage == nil)
415
-
416
- if isLoading {
417
- ProgressView("Generating try-on...")
418
- } else if let result = resultImage {
419
- Image(uiImage: result)
420
- .resizable()
421
- .scaledToFit()
422
- }
423
- }
424
- }
425
- }
426
- ```
427
- """)
428
-
429
- # Event Handler
430
- submit_btn.click(
431
- fn=virtual_tryon,
432
- inputs=[person_input, garment_input],
433
- outputs=output_image,
434
- api_name="predict"
435
- )
436
-
437
- # Footer
438
- gr.Markdown("""
439
- ---
440
- <div style="text-align: center; color: #666; padding: 20px;">
441
- <p>⚡ Powered by HuggingFace Zero GPU | 🎨 AI Image Processing | 🚀 Optimized for Mobile Apps</p>
442
- <p style="font-size: 0.9em;">Built with ❤️ for Mirro iOS App</p>
443
- </div>
444
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
 
446
- # Launch
447
  if __name__ == "__main__":
448
- demo.queue(max_size=20, default_concurrency_limit=1)
449
- demo.launch(
450
- server_name="0.0.0.0",
451
- server_port=7860,
452
- share=False,
453
- show_error=True
454
- )
 
1
  import gradio as gr
2
  import spaces
3
+ from PIL import Image, ImageEnhance
4
  import numpy as np
5
  import logging
6
  import cv2
7
  from rembg import remove
 
8
 
9
  # Setup logging
10
  logging.basicConfig(level=logging.INFO)
11
  logger = logging.getLogger(__name__)
12
 
13
+ logger.info("🚀 Mirro Virtual Try-On Server Starting...")
14
 
15
  def create_clothing_mask(image):
16
  """Create mask for upper body clothing area"""
17
  width, height = image.size
18
  mask = np.zeros((height, width), dtype=np.uint8)
19
 
20
+ # Upper body region
21
  y_start = int(height * 0.15)
22
  y_end = int(height * 0.70)
23
  x_start = int(width * 0.20)
24
  x_end = int(width * 0.80)
25
 
26
+ # Elliptical mask
27
  center_x = (x_start + x_end) // 2
28
  center_y = (y_start + y_end) // 2
29
  radius_x = (x_end - x_start) // 2
 
40
 
41
  def enhance_image(image):
42
  """Enhance image quality"""
 
43
  enhancer = ImageEnhance.Sharpness(image)
44
  image = enhancer.enhance(1.3)
45
 
 
46
  enhancer = ImageEnhance.Contrast(image)
47
  image = enhancer.enhance(1.1)
48
 
 
49
  enhancer = ImageEnhance.Color(image)
50
  image = enhancer.enhance(1.1)
51
 
52
  return image
53
 
54
  def remove_garment_background(garment_img):
55
+ """Remove background from garment"""
56
  try:
57
  logger.info("Removing garment background...")
58
  output = remove(garment_img)
 
62
  return garment_img
63
 
64
  def blend_images(person_img, garment_img, mask):
65
+ """Blend garment onto person"""
 
66
  person_array = np.array(person_img).astype(float)
67
  garment_array = np.array(garment_img).astype(float)
68
  mask_array = np.array(mask).astype(float) / 255.0
69
 
 
70
  mask_3ch = np.stack([mask_array] * 3, axis=2)
71
 
 
72
  blended = (garment_array * mask_3ch + person_array * (1 - mask_3ch))
73
 
74
  return Image.fromarray(blended.astype(np.uint8))
 
79
  garment_array = np.array(garment_img)
80
  mask_array = np.array(mask) / 255.0
81
 
 
82
  person_sample = person_array * (1 - mask_array[:, :, np.newaxis])
83
  person_mean = np.mean(person_sample[person_sample > 0])
84
 
 
85
  garment_mean = np.mean(garment_array)
86
 
 
87
  if garment_mean > 0:
88
  adjustment = person_mean / garment_mean
89
  garment_adjusted = np.clip(garment_array * adjustment, 0, 255).astype(np.uint8)
 
93
 
94
  @spaces.GPU(duration=60)
95
  def virtual_tryon(person_image, garment_image, progress=gr.Progress()):
96
+ """Virtual try-on function"""
 
 
97
  try:
 
98
  if person_image is None:
99
  raise gr.Error("❌ Please upload a person's photo!")
100
 
101
  if garment_image is None:
102
  raise gr.Error("❌ Please upload a garment photo!")
103
 
104
+ logger.info("🎯 Starting Virtual Try-On")
 
 
105
 
106
+ progress(0.1, desc="📸 Preprocessing...")
 
107
  person_img = person_image.convert("RGB")
108
  garment_img = garment_image.convert("RGB")
109
 
 
110
  target_size = (512, 768)
111
  person_img = person_img.resize(target_size, Image.Resampling.LANCZOS)
112
  garment_img = garment_img.resize(target_size, Image.Resampling.LANCZOS)
113
 
 
114
  progress(0.3, desc="🎨 Processing garment...")
115
  garment_nobg = remove_garment_background(garment_img)
116
 
117
+ progress(0.4, desc="✨ Enhancing...")
 
118
  garment_enhanced = enhance_image(garment_nobg)
119
 
120
+ progress(0.5, desc="🎯 Creating mask...")
 
121
  mask = create_clothing_mask(person_img)
122
 
 
123
  progress(0.6, desc="🎨 Matching colors...")
124
  garment_matched = match_colors(person_img, garment_enhanced, mask)
125
 
126
+ progress(0.8, desc="✨ Blending...")
 
127
  result = blend_images(person_img, garment_matched, mask)
128
 
 
129
  progress(0.9, desc="🎨 Final touches...")
130
  result = enhance_image(result)
131
 
132
  progress(1.0, desc="✅ Complete!")
133
+ logger.info("✅ Try-on completed!")
 
134
 
135
  return result
136
 
 
138
  logger.error(f"❌ Error: {str(e)}")
139
  import traceback
140
  traceback.print_exc()
141
+ raise gr.Error(f"❌ Failed: {str(e)}")
142
 
143
+ # UI
144
+ css = """
145
+ #container {max-width: 1400px; margin: auto;}
146
+ .primary-btn {
 
 
 
 
 
 
147
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
148
  border: none !important;
 
 
 
 
 
 
149
  }
150
  footer {display: none !important;}
151
  """
152
 
153
+ with gr.Blocks(css=css, title="Mirro Virtual Try-On") as demo:
154
+
155
+ gr.Markdown("""
156
+ # 👗 Mirro Virtual Try-On API
157
+ ### AI-Powered Virtual Clothing Try-On
158
+
159
+ Upload person and garment photos for realistic try-on results.
160
+ """)
161
 
162
+ gr.Markdown("🟢 **Status:** Ready!")
163
+
164
+ with gr.Row():
165
+ with gr.Column():
166
+ person_input = gr.Image(
167
+ label="👤 Person Photo",
168
+ type="pil",
169
+ sources=["upload", "clipboard"]
170
+ )
171
+
172
+ garment_input = gr.Image(
173
+ label="👔 Garment Photo",
174
+ type="pil",
175
+ sources=["upload", "clipboard"]
176
+ )
177
+
178
+ with gr.Row():
179
+ clear_btn = gr.ClearButton([person_input, garment_input], value="Clear")
180
+ submit_btn = gr.Button("✨ Generate Try-On", variant="primary")
181
 
182
+ with gr.Column():
183
+ output_image = gr.Image(label="🎯 Result", type="pil")
184
+
185
+ with gr.Accordion("💡 Tips", open=False):
186
  gr.Markdown("""
187
+ - Use clear, well-lit photos
188
+ - Person facing forward
189
+ - Plain backgrounds work best
190
+ - Processing: 10-20 seconds
 
191
  """)
192
+
193
+ with gr.Accordion("📱 API Documentation", open=False):
194
+ gr.Markdown("""
195
+ **Endpoint:** `POST https://gk2291-mirro-app-server.hf.space/api/predict`
196
 
197
+ **Request:**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  ```json
199
+ {
200
+ "data": [
201
+ "data:image/jpeg;base64,<person_base64>",
202
+ "data:image/jpeg;base64,<garment_base64>"
203
+ ]
204
+ }
205
  ```
206
+
207
+ **Response:**
208
  ```json
209
+ {
210
+ "data": ["data:image/png;base64,<result_base64>"]
211
+ }
 
212
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  """)
214
+
215
+ submit_btn.click(
216
+ fn=virtual_tryon,
217
+ inputs=[person_input, garment_input],
218
+ outputs=output_image,
219
+ api_name="predict"
220
+ )
221
+
222
+ gr.Markdown("""
223
+ ---
224
+ <div style="text-align: center; color: #666;">
225
+ <p>⚡ Powered by HuggingFace Zero GPU</p>
226
+ </div>
227
+ """)
228
 
 
229
  if __name__ == "__main__":
230
+ demo.queue(max_size=20)
231
+ demo.launch(server_name="0.0.0.0", server_port=7860)