FaizanShaikh1 commited on
Commit
997c1fe
·
verified ·
1 Parent(s): c3cb01f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1212 -149
app.py CHANGED
@@ -1,162 +1,1225 @@
1
- import faicons as fa
2
- import plotly.express as px
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
- # Load data and compute static values
5
- from shared import app_dir, tips
6
- from shinywidgets import render_plotly
7
 
8
- from shiny import reactive, render
9
- from shiny.express import input, ui
 
 
 
 
 
 
10
 
11
- bill_rng = (min(tips.total_bill), max(tips.total_bill))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- # Add page title and sidebar
14
- ui.page_opts(title="Restaurant tipping", fillable=True)
15
-
16
- with ui.sidebar(open="desktop"):
17
- ui.input_slider(
18
- "total_bill",
19
- "Bill amount",
20
- min=bill_rng[0],
21
- max=bill_rng[1],
22
- value=bill_rng,
23
- pre="$",
24
- )
25
- ui.input_checkbox_group(
26
- "time",
27
- "Food service",
28
- ["Lunch", "Dinner"],
29
- selected=["Lunch", "Dinner"],
30
- inline=True,
31
- )
32
- ui.input_action_button("reset", "Reset filter")
33
-
34
- # Add main content
35
- ICONS = {
36
- "user": fa.icon_svg("user", "regular"),
37
- "wallet": fa.icon_svg("wallet"),
38
- "currency-dollar": fa.icon_svg("dollar-sign"),
39
- "ellipsis": fa.icon_svg("ellipsis"),
40
- }
41
-
42
- with ui.layout_columns(fill=False):
43
- with ui.value_box(showcase=ICONS["user"]):
44
- "Total tippers"
45
-
46
- @render.express
47
- def total_tippers():
48
- tips_data().shape[0]
49
-
50
- with ui.value_box(showcase=ICONS["wallet"]):
51
- "Average tip"
52
-
53
- @render.express
54
- def average_tip():
55
- d = tips_data()
56
- if d.shape[0] > 0:
57
- perc = d.tip / d.total_bill
58
- f"{perc.mean():.1%}"
59
-
60
- with ui.value_box(showcase=ICONS["currency-dollar"]):
61
- "Average bill"
62
-
63
- @render.express
64
- def average_bill():
65
- d = tips_data()
66
- if d.shape[0] > 0:
67
- bill = d.total_bill.mean()
68
- f"${bill:.2f}"
69
-
70
-
71
- with ui.layout_columns(col_widths=[6, 6, 12]):
72
- with ui.card(full_screen=True):
73
- ui.card_header("Tips data")
74
-
75
- @render.data_frame
76
- def table():
77
- return render.DataGrid(tips_data())
78
-
79
- with ui.card(full_screen=True):
80
- with ui.card_header(class_="d-flex justify-content-between align-items-center"):
81
- "Total bill vs tip"
82
- with ui.popover(title="Add a color variable", placement="top"):
83
- ICONS["ellipsis"]
84
- ui.input_radio_buttons(
85
- "scatter_color",
86
- None,
87
- ["none", "sex", "smoker", "day", "time"],
88
- inline=True,
89
- )
90
-
91
- @render_plotly
92
- def scatterplot():
93
- color = input.scatter_color()
94
- return px.scatter(
95
- tips_data(),
96
- x="total_bill",
97
- y="tip",
98
- color=None if color == "none" else color,
99
- trendline="lowess",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  )
101
-
102
- with ui.card(full_screen=True):
103
- with ui.card_header(class_="d-flex justify-content-between align-items-center"):
104
- "Tip percentages"
105
- with ui.popover(title="Add a color variable"):
106
- ICONS["ellipsis"]
107
- ui.input_radio_buttons(
108
- "tip_perc_y",
109
- "Split by:",
110
- ["sex", "smoker", "day", "time"],
111
- selected="day",
112
- inline=True,
113
- )
114
-
115
- @render_plotly
116
- def tip_perc():
117
- from ridgeplot import ridgeplot
118
-
119
- dat = tips_data()
120
- dat["percent"] = dat.tip / dat.total_bill
121
- yvar = input.tip_perc_y()
122
- uvals = dat[yvar].unique()
123
-
124
- samples = [[dat.percent[dat[yvar] == val]] for val in uvals]
125
-
126
- plt = ridgeplot(
127
- samples=samples,
128
- labels=uvals,
129
- bandwidth=0.01,
130
- colorscale="viridis",
131
- colormode="row-index",
132
- )
133
-
134
- plt.update_layout(
135
- legend=dict(
136
- orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5
137
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
- return plt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- ui.include_css(app_dir / "styles.css")
144
-
145
- # --------------------------------------------------------
146
- # Reactive calculations and effects
147
- # --------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
 
 
 
 
149
 
150
- @reactive.calc
151
- def tips_data():
152
- bill = input.total_bill()
153
- idx1 = tips.total_bill.between(bill[0], bill[1])
154
- idx2 = tips.time.isin(input.time())
155
- return tips[idx1 & idx2]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
- @reactive.effect
159
- @reactive.event(input.reset)
160
- def _():
161
- ui.update_slider("total_bill", value=bill_rng)
162
- ui.update_checkbox_group("time", selected=["Lunch", "Dinner"])
 
1
+ import cv2
2
+ import numpy as np
3
+ import argparse
4
+ import time
5
+ import sys
6
+ import os
7
+ from pathlib import Path
8
+ import streamlit as st
9
+ from PIL import Image, ImageTk
10
+ import tempfile
11
+ import io
12
+ import threading
13
+ import tkinter as tk
14
+ from tkinter import filedialog, Label, Button, Frame
15
 
16
+ # Constants
17
+ MODEL_DIR = "models"
18
+ TEMP_DIR = "temp"
19
 
20
+ def parse_args():
21
+ parser = argparse.ArgumentParser(description='Advanced Virtual Try-On')
22
+ parser.add_argument('--garment', type=str, help='Path to garment image')
23
+ parser.add_argument('--webcam', type=int, default=0, help='Webcam index to use')
24
+ parser.add_argument('--resolution', type=str, default='640x480', help='Camera resolution')
25
+ parser.add_argument('--streamlit', action='store_true', help='Run in Streamlit mode')
26
+ parser.add_argument('--tkinter', action='store_true', help='Run with Tkinter UI')
27
+ return parser.parse_args()
28
 
29
+ class HumanPoseEstimator:
30
+ """Human pose estimation using OpenPose or similar model"""
31
+
32
+ def __init__(self):
33
+ # Create model directory if it doesn't exist
34
+ if not os.path.exists(MODEL_DIR):
35
+ os.makedirs(MODEL_DIR)
36
+
37
+ # Download pose model if not present (simplified here)
38
+ self.download_models_if_needed()
39
+
40
+ # Load COCO body model for OpenPose
41
+ self.BODY_PARTS = {
42
+ "Nose": 0, "Neck": 1, "RShoulder": 2, "RElbow": 3, "RWrist": 4,
43
+ "LShoulder": 5, "LElbow": 6, "LWrist": 7, "RHip": 8, "RKnee": 9,
44
+ "RAnkle": 10, "LHip": 11, "LKnee": 12, "LAnkle": 13, "REye": 14,
45
+ "LEye": 15, "REar": 16, "LEar": 17, "Background": 18
46
+ }
47
+
48
+ self.POSE_PAIRS = [
49
+ ["Neck", "RShoulder"], ["Neck", "LShoulder"], ["RShoulder", "RElbow"],
50
+ ["RElbow", "RWrist"], ["LShoulder", "LElbow"], ["LElbow", "LWrist"],
51
+ ["Neck", "RHip"], ["RHip", "RKnee"], ["RKnee", "RAnkle"], ["Neck", "LHip"],
52
+ ["LHip", "LKnee"], ["LKnee", "LAnkle"], ["Neck", "Nose"], ["Nose", "REye"],
53
+ ["REye", "REar"], ["Nose", "LEye"], ["LEye", "LEar"]
54
+ ]
55
+
56
+ # Load OpenPose network
57
+ self.net = self.load_pose_model()
58
+
59
+ print("Pose estimation model loaded successfully")
60
+
61
+ def download_models_if_needed(self):
62
+ """Download models if not present"""
63
+ # Model paths
64
+ pose_model_path = os.path.join(MODEL_DIR, "pose_iter_440000.caffemodel")
65
+ pose_proto_path = os.path.join(MODEL_DIR, "pose_deploy_linevec.prototxt")
66
+
67
+ # Check if models exist
68
+ if not os.path.exists(pose_model_path) or not os.path.exists(pose_proto_path):
69
+ print("Models not found. Downloading pose estimation models...")
70
+ # Normally we'd download the models here using requests or urllib
71
+ # For this example, we'll direct the user to download them manually
72
+ print("Please download the OpenPose model:")
73
+ print(f"1. Download pose_iter_440000.caffemodel to {MODEL_DIR}")
74
+ print(f"2. Download pose_deploy_linevec.prototxt to {MODEL_DIR}")
75
+ print("Models can be found at: https://github.com/CMU-Perceptual-Computing-Lab/openpose/tree/master/models")
76
+
77
+ # Create directory for models
78
+ Path(MODEL_DIR).mkdir(parents=True, exist_ok=True)
79
+
80
+ # For demonstration, we'll create dummy files with instructions
81
+ with open(pose_proto_path, 'w') as f:
82
+ f.write("# Download the actual model file from OpenPose repository")
83
+ with open(pose_model_path, 'w') as f:
84
+ f.write("# Download the actual model file from OpenPose repository")
85
+
86
+ print("Created placeholder files. Replace with actual model files before running.")
87
+
88
+ def load_pose_model(self):
89
+ """Load the pose detection model"""
90
+ try:
91
+ # Try to load the OpenPose model
92
+ model_path = os.path.join(MODEL_DIR, "pose_iter_440000.caffemodel")
93
+ config_path = os.path.join(MODEL_DIR, "pose_deploy_linevec.prototxt")
94
+
95
+ if os.path.getsize(model_path) < 1000: # Placeholder file
96
+ print("Warning: Using placeholder model file. Results will be simulated.")
97
+ # Fall back to a basic pose estimation
98
+ return None
99
+
100
+ net = cv2.dnn.readNetFromCaffe(config_path, model_path)
101
+
102
+ # Try to use GPU if available - safely check for CUDA availability
103
+ try:
104
+ # Check if CUDA is available by testing if the cv2.cuda module exists
105
+ cuda_available = False
106
+ if hasattr(cv2, 'cuda'):
107
+ try:
108
+ cuda_available = cv2.cuda.getCudaEnabledDeviceCount() > 0
109
+ except:
110
+ cuda_available = False
111
+
112
+ if cuda_available:
113
+ print("CUDA is available. Using GPU acceleration.")
114
+ net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
115
+ net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
116
+ else:
117
+ print("CUDA is not available. Using CPU.")
118
+ net.setPreferableBackend(cv2.dnn.DNN_BACKEND_DEFAULT)
119
+ net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
120
+ except Exception as cuda_err:
121
+ print(f"Error checking CUDA availability: {cuda_err}. Using CPU instead.")
122
+ net.setPreferableBackend(cv2.dnn.DNN_BACKEND_DEFAULT)
123
+ net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
124
+
125
+ return net
126
+ except Exception as e:
127
+ print(f"Error loading pose model: {e}")
128
+ print("Falling back to simulation mode")
129
+ return None
130
+
131
+ def estimate_pose(self, frame):
132
+ """Estimate human pose in the frame"""
133
+ frame_height, frame_width = frame.shape[:2]
134
+
135
+ # If we don't have the actual model, simulate pose detection
136
+ if self.net is None:
137
+ return self.simulate_pose(frame)
138
+
139
+ # Prepare input for the network
140
+ input_blob = cv2.dnn.blobFromImage(frame, 1.0 / 255, (368, 368), (0, 0, 0), swapRB=False, crop=False)
141
+ self.net.setInput(input_blob)
142
+
143
+ # Forward pass through the network
144
+ output = self.net.forward()
145
+
146
+ # Parse keypoints
147
+ keypoints = []
148
+ threshold = 0.1
149
+
150
+ for i in range(len(self.BODY_PARTS) - 1): # Exclude background
151
+ # Get confidence map
152
+ prob_map = output[0, i, :, :]
153
+ prob_map = cv2.resize(prob_map, (frame_width, frame_height))
154
+
155
+ # Find global maximum
156
+ _, confidence, _, point = cv2.minMaxLoc(prob_map)
157
+
158
+ if confidence > threshold:
159
+ keypoints.append((point[0], point[1], confidence))
160
+ else:
161
+ keypoints.append(None)
162
+
163
+ return keypoints
164
+
165
+ def simulate_pose(self, frame):
166
+ """Simulate pose detection when model isn't available"""
167
+ # Use face detection to estimate body position
168
+ face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
169
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
170
+ faces = face_cascade.detectMultiScale(gray, 1.3, 5)
171
+
172
+ # Initialize keypoints
173
+ keypoints = [None] * (len(self.BODY_PARTS) - 1)
174
+
175
+ if len(faces) > 0:
176
+ # Get the largest face
177
+ x, y, w, h = max(faces, key=lambda rect: rect[2] * rect[3])
178
+
179
+ # Center of face
180
+ face_center_x = x + w // 2
181
+ face_center_y = y + h // 2
182
+
183
+ # Estimate keypoints based on face position
184
+ frame_height, frame_width = frame.shape[:2]
185
+
186
+ # Nose (center of face)
187
+ keypoints[self.BODY_PARTS["Nose"]] = (face_center_x, face_center_y, 0.9)
188
+
189
+ # Neck (below face)
190
+ neck_y = y + h + h // 4
191
+ keypoints[self.BODY_PARTS["Neck"]] = (face_center_x, neck_y, 0.8)
192
+
193
+ # Shoulders (on either side of neck)
194
+ shoulder_y = neck_y + h // 8
195
+ keypoints[self.BODY_PARTS["RShoulder"]] = (face_center_x - w, shoulder_y, 0.7)
196
+ keypoints[self.BODY_PARTS["LShoulder"]] = (face_center_x + w, shoulder_y, 0.7)
197
+
198
+ # Approximate other body parts
199
+ keypoints[self.BODY_PARTS["RHip"]] = (face_center_x - w//2, frame_height - h*2, 0.5)
200
+ keypoints[self.BODY_PARTS["LHip"]] = (face_center_x + w//2, frame_height - h*2, 0.5)
201
+
202
+ return keypoints
203
+
204
+ def draw_skeleton(self, frame, keypoints):
205
+ """Draw skeleton on the frame for visualization"""
206
+ # Draw keypoints
207
+ for i, keypoint in enumerate(keypoints):
208
+ if keypoint:
209
+ cv2.circle(frame, (int(keypoint[0]), int(keypoint[1])), 8, (0, 255, 255), thickness=-1, lineType=cv2.FILLED)
210
+
211
+ # Draw connections
212
+ for pair in self.POSE_PAIRS:
213
+ part_from = self.BODY_PARTS[pair[0]]
214
+ part_to = self.BODY_PARTS[pair[1]]
215
+
216
+ if keypoints[part_from] and keypoints[part_to]:
217
+ cv2.line(frame,
218
+ (int(keypoints[part_from][0]), int(keypoints[part_from][1])),
219
+ (int(keypoints[part_to][0]), int(keypoints[part_to][1])),
220
+ (0, 255, 0), 3)
221
+
222
+ return frame
223
 
224
+ class GarmentProcessor:
225
+ """Process garment images for virtual try-on"""
226
+
227
+ def __init__(self):
228
+ # Create temp directory for processed images
229
+ if not os.path.exists(TEMP_DIR):
230
+ os.makedirs(TEMP_DIR)
231
+
232
+ def load_garment(self, path):
233
+ """Load and preprocess a garment image"""
234
+ # Load the image
235
+ garment = cv2.imread(path, cv2.IMREAD_UNCHANGED)
236
+
237
+ if garment is None:
238
+ raise FileNotFoundError(f"Could not load garment image from {path}")
239
+
240
+ # If garment doesn't have alpha channel, add one
241
+ if garment.shape[2] == 3:
242
+ garment = self.remove_background(garment)
243
+
244
+ return garment
245
+
246
+ def remove_background(self, img):
247
+ """Remove background from garment image including black backgrounds"""
248
+ # Convert to RGBA
249
+ rgba = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
250
+
251
+ # Get image dimensions
252
+ h, w = img.shape[:2]
253
+
254
+ # Create an initial mask
255
+ # Instead of simple thresholding which fails for black clothes,
256
+ # we'll use a combination of techniques
257
+
258
+ # 1. Start with an approximate mask using color detection
259
+ # Convert to different color spaces
260
+ hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
261
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
262
+
263
+ # Create masks for different color spaces
264
+ # Detect very dark regions (potential black backgrounds)
265
+ _, dark_mask = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY)
266
+
267
+ # Detect edges - useful for finding garment boundaries
268
+ edges = cv2.Canny(img, 50, 150)
269
+ kernel = np.ones((5,5), np.uint8)
270
+ dilated_edges = cv2.dilate(edges, kernel, iterations=2)
271
+
272
+ # Create initial GrabCut mask
273
+ # 0 = background, 1 = foreground, 2 = probable background, 3 = probable foreground
274
+ gc_mask = np.zeros(img.shape[:2], np.uint8)
275
+
276
+ # Mark the borders as likely background
277
+ border_width = w // 10 # 10% of width
278
+ gc_mask[:border_width, :] = 2
279
+ gc_mask[-border_width:, :] = 2
280
+ gc_mask[:, :border_width] = 2
281
+ gc_mask[:, -border_width:] = 2
282
+
283
+ # Mark the center as likely foreground
284
+ center_rect = (border_width, border_width, w - 2*border_width, h - 2*border_width)
285
+ cv2.rectangle(gc_mask, (center_rect[0], center_rect[1]),
286
+ (center_rect[0] + center_rect[2], center_rect[1] + center_rect[3]), 3, -1)
287
+
288
+ # Use edges to refine foreground
289
+ gc_mask[dilated_edges > 0] = 1
290
+
291
+ # Initialize GrabCut background and foreground models
292
+ bgd_model = np.zeros((1, 65), np.float64)
293
+ fgd_model = np.zeros((1, 65), np.float64)
294
+
295
+ # Run GrabCut algorithm
296
+ try:
297
+ cv2.grabCut(img, gc_mask, center_rect, bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_MASK)
298
+ except Exception as e:
299
+ print(f"GrabCut failed: {e}. Using fallback method.")
300
+ # Fallback to simpler method if GrabCut fails
301
+ # Create a simple mask based on color threshold
302
+ _, mask = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
303
+ kernel = np.ones((5, 5), np.uint8)
304
+ mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
305
+ mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
306
+ rgba[:, :, 3] = mask
307
+ return rgba
308
+
309
+ # Create final mask where 1 and 3 are foreground
310
+ final_mask = np.where((gc_mask == 1) | (gc_mask == 3), 255, 0).astype('uint8')
311
+
312
+ # Clean up mask with morphological operations
313
+ kernel = np.ones((5, 5), np.uint8)
314
+ final_mask = cv2.morphologyEx(final_mask, cv2.MORPH_OPEN, kernel)
315
+ final_mask = cv2.morphologyEx(final_mask, cv2.MORPH_CLOSE, kernel)
316
+
317
+ # Dilate the mask slightly to include edge details
318
+ final_mask = cv2.dilate(final_mask, kernel, iterations=1)
319
+
320
+ # Apply mask to alpha channel
321
+ rgba[:, :, 3] = final_mask
322
+
323
+ print("Added transparency to garment image with advanced background removal")
324
+ return rgba
325
+
326
+ def warp_garment(self, garment, keypoints, frame_size, sizing_params=None):
327
+ """Warp the garment to fit the detected pose"""
328
+ frame_height, frame_width = frame_size
329
+
330
+ # Set default sizing parameters if not provided
331
+ if sizing_params is None:
332
+ sizing_params = {
333
+ 'width_scale': 1.2, # Default width scale - reduced for better fit
334
+ 'height_scale': 1.1 # Default height scale
335
+ }
336
+
337
+ # If no valid keypoints, return original garment
338
+ if not keypoints or not keypoints[1]: # Check if neck keypoint exists
339
+ return garment
340
+
341
+ # Get relevant keypoints for garment warping
342
+ neck = keypoints[1]
343
+ right_shoulder = keypoints[2]
344
+ left_shoulder = keypoints[5]
345
+ right_hip = keypoints[8]
346
+ left_hip = keypoints[11]
347
+
348
+ if not all([neck, right_shoulder, left_shoulder]):
349
+ return garment # Not enough keypoints
350
+
351
+ # Calculate garment dimensions based on body
352
+ if right_shoulder and left_shoulder:
353
+ # Calculate Euclidean distance between shoulders
354
+ shoulder_width = np.linalg.norm(
355
+ [left_shoulder[0] - right_shoulder[0], left_shoulder[1] - right_shoulder[1]]
356
  )
357
+ # Get angle between shoulders for rotation
358
+ shoulder_angle = np.arctan2(
359
+ left_shoulder[1] - right_shoulder[1],
360
+ left_shoulder[0] - right_shoulder[0]
361
+ ) * 180 / np.pi
362
+ else:
363
+ shoulder_width = frame_width * 0.2 # Fallback
364
+ shoulder_angle = 0
365
+
366
+ # Calculate torso measurements for better proportions
367
+ torso_height = 0
368
+ if right_hip and left_hip and neck:
369
+ # Distance from neck to hips
370
+ hip_center_x = (left_hip[0] + right_hip[0]) / 2
371
+ hip_center_y = (left_hip[1] + right_hip[1]) / 2
372
+ torso_height = np.linalg.norm([hip_center_x - neck[0], hip_center_y - neck[1]])
373
+ else:
374
+ # Estimate torso height based on shoulder width and typical human proportions
375
+ # Use a more conservative estimate for better fit
376
+ torso_height = shoulder_width * 1.4 # Adjusted from 1.6 for better fit
377
+
378
+ # Calculate body size estimate
379
+ body_width = shoulder_width * 1.1 # Slightly wider than shoulders (reduced from 1.2)
380
+
381
+ # Get garment original dimensions
382
+ garment_height, garment_width = garment.shape[:2]
383
+
384
+ # Calculate aspect ratio of the garment
385
+ garment_aspect = garment_width / float(garment_height) if garment_height > 0 else 1.0
386
+
387
+ # Calculate ideal dimensions for the garment based on body
388
+ # For t-shirts: width should cover shoulders plus some extra, height should cover torso
389
+ ideal_width = shoulder_width * sizing_params['width_scale']
390
+ ideal_height = torso_height * sizing_params['height_scale']
391
+
392
+ # Maintain aspect ratio while fitting to body
393
+ if (ideal_width / ideal_height) > garment_aspect:
394
+ # Width-constrained: use ideal width, calculate height to maintain aspect
395
+ target_width = int(ideal_width)
396
+ target_height = int(target_width / garment_aspect)
397
+ else:
398
+ # Height-constrained: use ideal height, calculate width to maintain aspect
399
+ target_height = int(ideal_height)
400
+ target_width = int(target_height * garment_aspect)
401
+
402
+ # Resize garment to target dimensions
403
+ garment_resized = self.resize_garment(garment, target_width, target_height)
404
+
405
+ # Apply rotation if shoulders aren't level (beyond a small threshold)
406
+ if abs(shoulder_angle) > 5:
407
+ center = (garment_resized.shape[1] // 2, garment_resized.shape[0] // 2)
408
+ rotation_matrix = cv2.getRotationMatrix2D(center, shoulder_angle, 1.0)
409
+ garment_resized = cv2.warpAffine(
410
+ garment_resized, rotation_matrix,
411
+ (garment_resized.shape[1], garment_resized.shape[0]),
412
+ flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_TRANSPARENT
413
  )
414
+
415
+ # Apply perspective transform to better fit the body shape
416
+ try:
417
+ # Only apply perspective transform if we have all four corners (shoulders and hips)
418
+ if all([right_shoulder, left_shoulder, right_hip, left_hip]):
419
+ # Define source points (corners of the garment)
420
+ garment_h, garment_w = garment_resized.shape[:2]
421
+ src_pts = np.array([
422
+ [0, 0], # Top-left
423
+ [garment_w, 0], # Top-right
424
+ [garment_w, garment_h], # Bottom-right
425
+ [0, garment_h] # Bottom-left
426
+ ], dtype=np.float32)
427
+
428
+ # Define destination points based on body keypoints
429
+ # Scale factors to find garment edges from body keypoints
430
+ top_width_factor = 1.1 # How much wider than shoulders at top
431
+ bottom_width_factor = 0.9 # How much wider than hips at bottom
432
+
433
+ # Calculate destination points
434
+ top_left_x = left_shoulder[0] - (shoulder_width * (top_width_factor - 1) / 2)
435
+ top_right_x = right_shoulder[0] + (shoulder_width * (top_width_factor - 1) / 2)
436
+ bottom_left_x = left_hip[0] - (shoulder_width * (bottom_width_factor - 1) / 2)
437
+ bottom_right_x = right_hip[0] + (shoulder_width * (bottom_width_factor - 1) / 2)
438
+
439
+ # Get y-coordinates (adjust top to be at collar position)
440
+ top_y = (left_shoulder[1] + right_shoulder[1]) / 2 - garment_h * 0.2
441
+ bottom_y = top_y + garment_h * 0.95 # Slightly higher than full height for better look
442
+
443
+ dst_pts = np.array([
444
+ [top_left_x, top_y], # Top-left
445
+ [top_right_x, top_y], # Top-right
446
+ [bottom_right_x, bottom_y], # Bottom-right
447
+ [bottom_left_x, bottom_y] # Bottom-left
448
+ ], dtype=np.float32)
449
+
450
+ # Get perspective transform matrix
451
+ M = cv2.getPerspectiveTransform(src_pts, dst_pts)
452
+
453
+ # Apply perspective transform
454
+ # Make output size large enough to contain the warped garment
455
+ output_size = (frame_width, frame_height)
456
+ warped = cv2.warpPerspective(garment_resized, M, output_size,
457
+ flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_TRANSPARENT)
458
+
459
+ # Crop to the actual garment size to avoid large transparent areas
460
+ # Find non-zero alpha channel pixels
461
+ alpha = warped[:, :, 3]
462
+ coords = cv2.findNonZero(alpha)
463
+
464
+ if coords is not None and len(coords) > 0:
465
+ x, y, w, h = cv2.boundingRect(coords)
466
+ warped = warped[y:y+h, x:x+w]
467
+ return warped
468
+
469
+ except Exception as e:
470
+ print(f"Perspective transform failed: {e}")
471
+ # Continue with the regular garment if perspective transform fails
472
+ pass
473
+
474
+ print(f"Resized garment to fit body: {target_width}x{target_height} px")
475
+ return garment_resized
476
+
477
+ def resize_garment(self, garment, target_width=None, target_height=None):
478
+ """Resize garment maintaining aspect ratio"""
479
+ if garment is None:
480
+ return None
481
+
482
+ garment_height, garment_width = garment.shape[:2]
483
+ aspect = garment_width / float(garment_height)
484
+
485
+ if target_width is not None:
486
+ new_width = target_width
487
+ new_height = int(new_width / aspect)
488
+ elif target_height is not None:
489
+ new_height = target_height
490
+ new_width = int(new_height * aspect)
491
+ else:
492
+ return garment # No resize if no dimensions provided
493
+
494
+ # High-quality resize
495
+ resized = cv2.resize(garment, (new_width, new_height), interpolation=cv2.INTER_LANCZOS4)
496
+ return resized
497
 
498
+ class AdvancedVirtualTryOn:
499
+ """Main class for the virtual try-on system"""
500
+
501
+ def __init__(self, garment_path, camera_index=0, resolution="640x480", streamlit_mode=False):
502
+ # Parse resolution
503
+ width, height = map(int, resolution.split('x'))
504
+
505
+ # Set Streamlit mode
506
+ self.streamlit_mode = streamlit_mode
507
+
508
+ # Initialize components
509
+ self.pose_estimator = HumanPoseEstimator()
510
+ self.garment_processor = GarmentProcessor()
511
+
512
+ # Load garment
513
+ self.garment = self.garment_processor.load_garment(garment_path)
514
+
515
+ # Initialize camera if not in Streamlit mode
516
+ if not streamlit_mode:
517
+ self.camera = cv2.VideoCapture(camera_index)
518
+ self.camera.set(cv2.CAP_PROP_FRAME_WIDTH, width)
519
+ self.camera.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
520
+
521
+ if not self.camera.isOpened():
522
+ raise RuntimeError("Could not open camera")
523
+
524
+ # Performance tracking
525
+ self.prev_frame_time = 0
526
+ self.new_frame_time = 0
527
+ self.fps = 0
528
+
529
+ # Garment positioning and sizing parameters - adjusted for better default fit
530
+ self.vertical_offset = 0.05
531
+ self.width_scale = 1.2 # Reduced from 1.5 for a more realistic fit
532
+ self.height_scale = 1.1 # Scale factor for garment height relative to torso
533
+ self.collar_position = 0.20 # Increased to position collar higher on neck
534
+
535
+ # UI modes
536
+ self.debug_mode = False
537
+ self.show_controls = True
538
+ self.fullscreen_mode = False
539
+
540
+ # For smoother processing and better performance
541
+ self.skip_frames = 0 # Process every frame by default
542
+ self.frame_counter = 0
543
+ self.last_warped_garment = None
544
+ self.last_keypoints = None
545
+
546
+ print("Advanced Virtual Try-On initialized.")
547
+ if not streamlit_mode:
548
+ print("Starting camera feed...")
549
+
550
+ def update_fps(self):
551
+ """Calculate and update FPS"""
552
+ self.new_frame_time = time.time()
553
+ self.fps = 1 / (self.new_frame_time - self.prev_frame_time) if (self.new_frame_time - self.prev_frame_time) > 0 else 0
554
+ self.prev_frame_time = self.new_frame_time
555
+ return int(self.fps)
556
+
557
+ def overlay_garment(self, frame, keypoints):
558
+ """Overlay the garment on the frame"""
559
+ frame_height, frame_width = frame.shape[:2]
560
+
561
+ # Check if we have valid keypoints
562
+ if not keypoints or not keypoints[1]: # Neck keypoint
563
+ # Use last valid keypoints if available for smoothness
564
+ if self.last_keypoints and self.last_warped_garment is not None:
565
+ keypoints = self.last_keypoints
566
+ else:
567
+ return frame
568
+ else:
569
+ # Store last valid keypoints for smooth transitions
570
+ self.last_keypoints = keypoints
571
+
572
+ try:
573
+ # Get key body keypoints
574
+ neck = keypoints[1]
575
+ right_shoulder = keypoints[2]
576
+ left_shoulder = keypoints[5]
577
+
578
+ if not all([neck, right_shoulder, left_shoulder]):
579
+ if self.last_warped_garment is not None:
580
+ # Use last valid garment if available
581
+ warped_garment = self.last_warped_garment
582
+ else:
583
+ return frame
584
+ else:
585
+ # Calculate shoulder midpoint for better centering
586
+ shoulder_center_x = (right_shoulder[0] + left_shoulder[0]) / 2
587
+ shoulder_center_y = (right_shoulder[1] + left_shoulder[1]) / 2
588
+
589
+ # Check if we should skip processing this frame (for performance)
590
+ self.frame_counter += 1
591
+ if self.skip_frames > 0 and self.frame_counter % (self.skip_frames + 1) != 0 and self.last_warped_garment is not None:
592
+ warped_garment = self.last_warped_garment
593
+ else:
594
+ # Pass current sizing parameters to warp_garment
595
+ sizing_params = {
596
+ 'width_scale': self.width_scale,
597
+ 'height_scale': self.height_scale
598
+ }
599
+
600
+ # Warp garment to fit the body
601
+ warped_garment = self.garment_processor.warp_garment(
602
+ self.garment, keypoints, (frame_height, frame_width), sizing_params
603
+ )
604
+
605
+ # Save for potential reuse
606
+ self.last_warped_garment = warped_garment
607
+
608
+ if warped_garment is None:
609
+ return frame
610
+
611
+ # Calculate position
612
+ garment_height, garment_width = warped_garment.shape[:2]
613
+
614
+ if all([neck, right_shoulder, left_shoulder]):
615
+ # Center horizontally on shoulders rather than neck for better alignment
616
+ center_x = int(shoulder_center_x)
617
+
618
+ # Calculate vertical position based on neck and shoulders
619
+ # Position garment higher for more natural look
620
+ shoulder_y = (right_shoulder[1] + left_shoulder[1]) / 2
621
+ center_y = int(neck[1] + (shoulder_y - neck[1]) * 0.5 - (garment_height * self.collar_position))
622
+ else:
623
+ # Fallback to last known position
624
+ center_x = frame_width // 2
625
+ center_y = frame_height // 3
626
+
627
+ # Calculate top-left corner
628
+ x1 = center_x - garment_width // 2
629
+ y1 = center_y
630
+
631
+ # Ensure coordinates are within frame
632
+ x1 = max(0, x1)
633
+ y1 = max(0, y1)
634
+ x2 = min(frame_width, x1 + garment_width)
635
+ y2 = min(frame_height, y1 + garment_height)
636
+
637
+ # Calculate source region in garment
638
+ g_x1 = 0
639
+ g_y1 = 0
640
+ g_x2 = x2 - x1
641
+ g_y2 = y2 - y1
642
+
643
+ if g_x2 <= 0 or g_y2 <= 0 or g_x1 >= garment_width or g_y1 >= garment_height:
644
+ return frame
645
+
646
+ # Adjust if needed
647
+ if g_x2 > garment_width:
648
+ g_x2 = garment_width
649
+ x2 = x1 + g_x2
650
+
651
+ if g_y2 > garment_height:
652
+ g_y2 = garment_height
653
+ y2 = y1 + g_y2
654
+
655
+ # Extract regions
656
+ roi = frame[y1:y2, x1:x2].copy()
657
+ garment_roi = warped_garment[g_y1:g_y2, g_x1:g_x2].copy()
658
+
659
+ if roi.shape[:2] != garment_roi.shape[:2]:
660
+ return frame
661
+
662
+ # Improved alpha blending with edge feathering
663
+ alpha = garment_roi[:, :, 3] / 255.0
664
+
665
+ # Apply Gaussian blur to alpha channel for softer edges
666
+ alpha_blur = cv2.GaussianBlur(alpha, (5, 5), 0)
667
+ alpha_blur = np.repeat(alpha_blur[:, :, np.newaxis], 3, axis=2)
668
+
669
+ # Blend images with the smoothed alpha
670
+ blended = roi * (1 - alpha_blur) + garment_roi[:, :, :3] * alpha_blur
671
+
672
+ # Apply color correction to match lighting
673
+ # This helps the garment look more natural in the scene
674
+ mean_roi = np.mean(roi, axis=(0, 1))
675
+ mean_garment = np.mean(garment_roi[:, :, :3], axis=(0, 1))
676
+
677
+ # Apply subtle lighting adjustment (limit the effect for realism)
678
+ lighting_factor = 0.3
679
+ lighting_adjustment = (mean_roi - mean_garment) * lighting_factor
680
+ adjusted_garment = np.clip(garment_roi[:, :, :3] + lighting_adjustment, 0, 255)
681
+
682
+ # Final blending with lighting adjustment
683
+ final_blend = roi * (1 - alpha_blur) + adjusted_garment * alpha_blur
684
+ frame[y1:y2, x1:x2] = final_blend
685
+
686
+ except Exception as e:
687
+ print(f"Error overlaying garment: {e}")
688
+
689
+ return frame
690
+
691
+ def process_frame(self, frame):
692
+ """Process a single frame, returning the processed frame"""
693
+ # Flip for mirror effect
694
+ frame = cv2.flip(frame, 1)
695
+
696
+ # Estimate pose
697
+ keypoints = self.pose_estimator.estimate_pose(frame)
698
+
699
+ # Overlay garment
700
+ frame = self.overlay_garment(frame, keypoints)
701
+
702
+ # Draw skeleton in debug mode
703
+ if self.debug_mode:
704
+ frame = self.pose_estimator.draw_skeleton(frame, keypoints)
705
+
706
+ # Calculate and display FPS
707
+ fps = self.update_fps()
708
+ cv2.putText(frame, f"FPS: {fps}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX,
709
+ 1, (0, 255, 0), 2, cv2.LINE_AA)
710
+
711
+ # Display current garment fit parameters
712
+ if self.show_controls and not self.streamlit_mode:
713
+ # Display fitting instructions
714
+ instructions = [
715
+ "Controls:",
716
+ f"Width Scale: {self.width_scale:.1f} (+/- to adjust)",
717
+ f"Height Scale: {self.height_scale:.1f} (up/down arrows)",
718
+ f"Collar Position: {self.collar_position:.2f} (</> to adjust)",
719
+ f"Performance: {'High' if self.skip_frames>0 else 'Normal'} (f key)",
720
+ f"Display: {'Fullscreen' if self.fullscreen_mode else 'Window'} (s key)",
721
+ "'d' - Toggle debug | 'q' - Quit | 'c' - Hide controls"
722
+ ]
723
+
724
+ y_pos = 70
725
+ for line in instructions:
726
+ cv2.putText(frame, line, (10, y_pos), cv2.FONT_HERSHEY_SIMPLEX,
727
+ 0.5, (0, 255, 0), 1, cv2.LINE_AA)
728
+ y_pos += 25
729
+ elif not self.streamlit_mode:
730
+ cv2.putText(frame, "Press 'c' for controls", (10, 70),
731
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 1, cv2.LINE_AA)
732
+
733
+ return frame
734
 
735
+ def run(self):
736
+ """Main application loop"""
737
+ if self.streamlit_mode:
738
+ print("Streamlit mode is active. The main loop will be controlled by the Streamlit app.")
739
+ return
740
+
741
+ print("Advanced Virtual Try-On started. Press 'q' to quit, 'd' to toggle debug mode.")
742
+ print("Use '+'/'-' to adjust garment width, 'up'/'down' arrows to adjust height.")
743
+ print("Use 'c' to toggle control instructions, 'f' to toggle frame skipping for better performance.")
744
+ print("Press 's' to toggle fullscreen mode.")
745
+
746
+ # Create a resizable window
747
+ cv2.namedWindow('Advanced Virtual Try-On', cv2.WINDOW_NORMAL)
748
+
749
+ while self.camera.isOpened():
750
+ success, frame = self.camera.read()
751
+ if not success:
752
+ print("Failed to capture frame")
753
+ break
754
+
755
+ # Process the frame
756
+ frame = self.process_frame(frame)
757
+
758
+ # Display the result
759
+ cv2.imshow('Advanced Virtual Try-On', frame)
760
+
761
+ # Handle key presses
762
+ key = cv2.waitKey(1) & 0xFF
763
+
764
+ # Quit
765
+ if key == ord('q'):
766
+ break
767
+
768
+ # Toggle debug mode
769
+ elif key == ord('d'):
770
+ self.debug_mode = not self.debug_mode
771
+ print(f"Debug mode: {'ON' if self.debug_mode else 'OFF'}")
772
+
773
+ # Toggle control display
774
+ elif key == ord('c'):
775
+ self.show_controls = not self.show_controls
776
+
777
+ # Toggle fullscreen mode
778
+ elif key == ord('s'):
779
+ self.fullscreen_mode = not self.fullscreen_mode
780
+ if self.fullscreen_mode:
781
+ cv2.setWindowProperty('Advanced Virtual Try-On', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
782
+ print("Fullscreen mode enabled")
783
+ else:
784
+ cv2.setWindowProperty('Advanced Virtual Try-On', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_NORMAL)
785
+ print("Window mode enabled")
786
+
787
+ # Adjust width scale
788
+ elif key == ord('+') or key == ord('='): # = is on the same key as + without shift
789
+ self.width_scale = min(3.0, self.width_scale + 0.1)
790
+ print(f"Width scale: {self.width_scale:.1f}")
791
+
792
+ elif key == ord('-'):
793
+ self.width_scale = max(0.8, self.width_scale - 0.1)
794
+ print(f"Width scale: {self.width_scale:.1f}")
795
+
796
+ # Adjust height scale
797
+ elif key == 82: # Up arrow
798
+ self.height_scale = min(2.0, self.height_scale + 0.1)
799
+ print(f"Height scale: {self.height_scale:.1f}")
800
+
801
+ elif key == 84: # Down arrow
802
+ self.height_scale = max(0.6, self.height_scale - 0.1)
803
+ print(f"Height scale: {self.height_scale:.1f}")
804
+
805
+ # Adjust collar position
806
+ elif key == ord(',') or key == ord('<'):
807
+ self.collar_position = max(0.05, self.collar_position - 0.01)
808
+ print(f"Collar position: {self.collar_position:.2f}")
809
+
810
+ elif key == ord('.') or key == ord('>'):
811
+ self.collar_position = min(0.3, self.collar_position + 0.01)
812
+ print(f"Collar position: {self.collar_position:.2f}")
813
+
814
+ # Toggle performance mode
815
+ elif key == ord('f'):
816
+ # Toggle between 0, 1, and 2 frame skips
817
+ self.skip_frames = (self.skip_frames + 1) % 3
818
+ print(f"Performance mode: {'High (skip {self.skip_frames} frames)' if self.skip_frames>0 else 'Normal'}")
819
+
820
+ # Clean up
821
+ self.clean_up()
822
+
823
+ def clean_up(self):
824
+ """Clean up resources"""
825
+ if hasattr(self, 'camera') and not self.streamlit_mode and self.camera.isOpened():
826
+ self.camera.release()
827
+ cv2.destroyAllWindows()
828
+ print("Application closed.")
829
 
830
+ class TkinterUI:
831
+ """Tkinter UI for the virtual try-on application"""
832
+
833
+ def __init__(self, webcam_index=0, resolution="640x480"):
834
+ self.webcam_index = webcam_index
835
+ self.width, self.height = map(int, resolution.split('x'))
836
+ self.app = None
837
+ self.running = False
838
+ self.garment_path = None
839
+ self.root = None
840
+ self.webcam_label = None
841
+ self.update_interval = 10 # Update every 10ms
842
+
843
+ def start(self):
844
+ """Start the Tkinter UI"""
845
+ self.root = tk.Tk()
846
+ self.root.title("Virtual Try-On")
847
+ self.root.geometry(f"{self.width + 300}x{self.height + 100}")
848
+ self.root.resizable(True, True)
849
+ self.root.protocol("WM_DELETE_WINDOW", self.on_close)
850
+
851
+ # Main frame
852
+ main_frame = Frame(self.root)
853
+ main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
854
+
855
+ # Left side - webcam and controls
856
+ left_frame = Frame(main_frame)
857
+ left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
858
+
859
+ # Webcam frame
860
+ webcam_frame = Frame(left_frame, width=self.width, height=self.height)
861
+ webcam_frame.pack(pady=10)
862
+ webcam_frame.pack_propagate(0) # Don't shrink
863
+
864
+ self.webcam_label = Label(webcam_frame)
865
+ self.webcam_label.pack(fill=tk.BOTH, expand=True)
866
+
867
+ # Control buttons
868
+ control_frame = Frame(left_frame)
869
+ control_frame.pack(fill=tk.X, pady=10)
870
+
871
+ upload_btn = Button(control_frame, text="Upload Garment", command=self.upload_garment)
872
+ upload_btn.pack(side=tk.LEFT, padx=5)
873
+
874
+ start_btn = Button(control_frame, text="Start Try-On", command=self.start_tryon)
875
+ start_btn.pack(side=tk.LEFT, padx=5)
876
+
877
+ stop_btn = Button(control_frame, text="Stop", command=self.stop_tryon)
878
+ stop_btn.pack(side=tk.LEFT, padx=5)
879
+
880
+ # Right side - adjustments and garment preview
881
+ right_frame = Frame(main_frame, width=280)
882
+ right_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10)
883
+ right_frame.pack_propagate(0) # Don't shrink
884
+
885
+ # Garment preview
886
+ preview_label = Label(right_frame, text="Garment Preview")
887
+ preview_label.pack(pady=(0, 5))
888
+
889
+ self.garment_preview = Label(right_frame, text="No garment selected")
890
+ self.garment_preview.pack(pady=5)
891
+
892
+ # Adjustments
893
+ adjustments_label = Label(right_frame, text="Adjustments")
894
+ adjustments_label.pack(pady=(15, 5))
895
+
896
+ # Width scale slider
897
+ width_frame = Frame(right_frame)
898
+ width_frame.pack(fill=tk.X, pady=5)
899
+
900
+ Label(width_frame, text="Width:").pack(side=tk.LEFT)
901
+ self.width_scale_var = tk.DoubleVar(value=1.2)
902
+ self.width_scale = tk.Scale(width_frame, from_=0.8, to=3.0, resolution=0.1, orient=tk.HORIZONTAL,
903
+ variable=self.width_scale_var, command=self.update_params)
904
+ self.width_scale.pack(side=tk.RIGHT, fill=tk.X, expand=True)
905
+
906
+ # Height scale slider
907
+ height_frame = Frame(right_frame)
908
+ height_frame.pack(fill=tk.X, pady=5)
909
+
910
+ Label(height_frame, text="Height:").pack(side=tk.LEFT)
911
+ self.height_scale_var = tk.DoubleVar(value=1.1)
912
+ self.height_scale = tk.Scale(height_frame, from_=0.6, to=2.0, resolution=0.1, orient=tk.HORIZONTAL,
913
+ variable=self.height_scale_var, command=self.update_params)
914
+ self.height_scale.pack(side=tk.RIGHT, fill=tk.X, expand=True)
915
+
916
+ # Collar position slider
917
+ collar_frame = Frame(right_frame)
918
+ collar_frame.pack(fill=tk.X, pady=5)
919
+
920
+ Label(collar_frame, text="Collar:").pack(side=tk.LEFT)
921
+ self.collar_var = tk.DoubleVar(value=0.2)
922
+ self.collar_scale = tk.Scale(collar_frame, from_=0.05, to=0.3, resolution=0.01, orient=tk.HORIZONTAL,
923
+ variable=self.collar_var, command=self.update_params)
924
+ self.collar_scale.pack(side=tk.RIGHT, fill=tk.X, expand=True)
925
+
926
+ # Debug mode checkbox
927
+ debug_frame = Frame(right_frame)
928
+ debug_frame.pack(fill=tk.X, pady=5)
929
+
930
+ self.debug_var = tk.BooleanVar(value=False)
931
+ debug_check = tk.Checkbutton(debug_frame, text="Show Skeleton", variable=self.debug_var,
932
+ command=self.update_params)
933
+ debug_check.pack(side=tk.LEFT)
934
+
935
+ # Status label
936
+ self.status_label = Label(right_frame, text="Ready")
937
+ self.status_label.pack(pady=10)
938
+
939
+ # Start main loop
940
+ self.root.mainloop()
941
+
942
+ def upload_garment(self):
943
+ """Open file dialog to select a garment image"""
944
+ filetypes = [
945
+ ("Image files", "*.png *.jpg *.jpeg"),
946
+ ("PNG files", "*.png"),
947
+ ("JPEG files", "*.jpg *.jpeg"),
948
+ ("All files", "*.*")
949
+ ]
950
+
951
+ self.garment_path = filedialog.askopenfilename(
952
+ title="Select Garment Image",
953
+ filetypes=filetypes
954
+ )
955
+
956
+ if self.garment_path:
957
+ self.status_label.config(text=f"Garment selected: {os.path.basename(self.garment_path)}")
958
+ self.load_preview()
959
+
960
+ def load_preview(self):
961
+ """Load and display the garment preview"""
962
+ if not self.garment_path:
963
+ return
964
+
965
+ try:
966
+ # Load image with PIL for preview
967
+ pil_img = Image.open(self.garment_path)
968
+
969
+ # Resize for preview (keep aspect ratio)
970
+ preview_width = 250
971
+ aspect_ratio = pil_img.width / pil_img.height
972
+ preview_height = int(preview_width / aspect_ratio)
973
+
974
+ # Limit height
975
+ if preview_height > 300:
976
+ preview_height = 300
977
+ preview_width = int(preview_height * aspect_ratio)
978
+
979
+ pil_img = pil_img.resize((preview_width, preview_height), Image.LANCZOS)
980
+
981
+ # Convert to Tkinter format
982
+ tk_img = ImageTk.PhotoImage(pil_img)
983
+
984
+ # Update preview
985
+ self.garment_preview.config(image=tk_img)
986
+ self.garment_preview.image = tk_img # Keep a reference
987
+
988
+ except Exception as e:
989
+ self.status_label.config(text=f"Error loading preview: {e}")
990
+
991
+ def start_tryon(self):
992
+ """Start the virtual try-on"""
993
+ if not self.garment_path:
994
+ self.status_label.config(text="Please select a garment first")
995
+ return
996
+
997
+ if self.running:
998
+ self.status_label.config(text="Already running")
999
+ return
1000
+
1001
+ try:
1002
+ # Initialize the virtual try-on application
1003
+ self.app = AdvancedVirtualTryOn(
1004
+ self.garment_path,
1005
+ self.webcam_index,
1006
+ f"{self.width}x{self.height}"
1007
+ )
1008
+
1009
+ # Set initial parameters
1010
+ self.update_params()
1011
+
1012
+ # Start the webcam
1013
+ self.running = True
1014
+ self.status_label.config(text="Try-on started")
1015
+
1016
+ # Start updating frames
1017
+ self.update_frame()
1018
+
1019
+ except Exception as e:
1020
+ self.status_label.config(text=f"Error starting try-on: {e}")
1021
+
1022
+ def update_params(self, *args):
1023
+ """Update the application parameters from UI controls"""
1024
+ if self.app:
1025
+ self.app.width_scale = self.width_scale_var.get()
1026
+ self.app.height_scale = self.height_scale_var.get()
1027
+ self.app.collar_position = self.collar_var.get()
1028
+ self.app.debug_mode = self.debug_var.get()
1029
+
1030
+ def update_frame(self):
1031
+ """Update the webcam frame"""
1032
+ if not self.running or not self.app:
1033
+ return
1034
+
1035
+ try:
1036
+ # Get frame from camera
1037
+ ret, frame = self.app.camera.read()
1038
+
1039
+ if ret:
1040
+ # Process the frame
1041
+ processed = self.app.process_frame(frame)
1042
+
1043
+ # Convert to PIL format
1044
+ pil_img = Image.fromarray(cv2.cvtColor(processed, cv2.COLOR_BGR2RGB))
1045
+
1046
+ # Convert to Tkinter format
1047
+ tk_img = ImageTk.PhotoImage(image=pil_img)
1048
+
1049
+ # Update image
1050
+ self.webcam_label.config(image=tk_img)
1051
+ self.webcam_label.image = tk_img # Keep a reference
1052
+
1053
+ # Schedule next update
1054
+ self.root.after(self.update_interval, self.update_frame)
1055
+
1056
+ except Exception as e:
1057
+ self.status_label.config(text=f"Error updating frame: {e}")
1058
+ self.stop_tryon()
1059
+
1060
+ def stop_tryon(self):
1061
+ """Stop the virtual try-on"""
1062
+ self.running = False
1063
+
1064
+ if self.app:
1065
+ self.app.clean_up()
1066
+ self.app = None
1067
+
1068
+ self.status_label.config(text="Try-on stopped")
1069
+
1070
+ def on_close(self):
1071
+ """Handle window close event"""
1072
+ self.stop_tryon()
1073
+ if self.root:
1074
+ self.root.destroy()
1075
 
1076
+ def run_tkinter_app(webcam_index=0, resolution="640x480"):
1077
+ """Run the application with Tkinter UI"""
1078
+ ui = TkinterUI(webcam_index, resolution)
1079
+ ui.start()
1080
 
1081
+ def run_streamlit_app():
1082
+ """Run the application in Streamlit mode"""
1083
+ st.set_page_config(page_title="Virtual Try-On", page_icon="👕", layout="wide")
1084
+
1085
+ st.title("Advanced Virtual Try-On")
1086
+ st.subheader("Try on clothing virtually using your webcam")
1087
+
1088
+ # Sidebar for uploading garment and adjustments
1089
+ with st.sidebar:
1090
+ st.header("Settings")
1091
+ uploaded_garment = st.file_uploader("Upload a garment image (with transparent background)", type=["png", "jpg", "jpeg"])
1092
+
1093
+ st.subheader("Fitting Adjustments")
1094
+ width_scale = st.slider("Width Scale", 0.8, 3.0, 1.2, 0.1)
1095
+ height_scale = st.slider("Height Scale", 0.6, 2.0, 1.1, 0.1)
1096
+ collar_position = st.slider("Collar Position", 0.05, 0.3, 0.2, 0.01)
1097
+
1098
+ debug_mode = st.checkbox("Show Skeleton", value=False)
1099
+
1100
+ st.info("For best results, use a garment image with a transparent background.")
1101
+
1102
+ # Main content
1103
+ col1, col2 = st.columns(2)
1104
+
1105
+ # If no garment uploaded, show sample garments
1106
+ if uploaded_garment is None:
1107
+ with col1:
1108
+ st.warning("Please upload a garment image to begin")
1109
+ st.write("No garment image uploaded. Here's how it works:")
1110
+ st.write("1. Upload a garment image with transparent background")
1111
+ st.write("2. Position yourself in front of the camera")
1112
+ st.write("3. Adjust the fit using the controls in the sidebar")
1113
+
1114
+ # Example garment placeholder
1115
+ st.image("https://i.imgur.com/JFP0rja.png", caption="Example garment (upload your own)")
1116
+
1117
+ with col2:
1118
+ # Camera placeholder
1119
+ st.write("Camera feed will appear here")
1120
+ placeholder = st.empty()
1121
+ placeholder.image(np.zeros((480, 640, 3), dtype=np.uint8), channels="BGR", caption="Webcam feed will appear here")
1122
+
1123
+ return
1124
+
1125
+ # Save uploaded garment to temp file
1126
+ temp_garment_file = None
1127
+ if uploaded_garment is not None:
1128
+ temp_garment_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
1129
+ temp_garment_file.write(uploaded_garment.getvalue())
1130
+ temp_garment_file.close()
1131
+
1132
+ # Initialize the application with the uploaded garment
1133
+ try:
1134
+ app = AdvancedVirtualTryOn(temp_garment_file.name, 0, "640x480", streamlit_mode=True)
1135
+
1136
+ # Set parameters from sliders
1137
+ app.width_scale = width_scale
1138
+ app.height_scale = height_scale
1139
+ app.collar_position = collar_position
1140
+ app.debug_mode = debug_mode
1141
+
1142
+ # Show the garment
1143
+ with col1:
1144
+ st.subheader("Garment:")
1145
+ garment_display = cv2.cvtColor(app.garment, cv2.COLOR_BGRA2RGBA)
1146
+ st.image(garment_display, caption="Your uploaded garment")
1147
+
1148
+ # Start webcam
1149
+ with col2:
1150
+ st.subheader("Virtual Try-On:")
1151
+ webcam_placeholder = st.empty()
1152
+
1153
+ cap = cv2.VideoCapture(0)
1154
+ cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
1155
+ cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
1156
+
1157
+ # Check if webcam opened successfully
1158
+ if not cap.isOpened():
1159
+ st.error("Could not open webcam. Please check your camera connection.")
1160
+ return
1161
+
1162
+ stop_button = st.button("Stop")
1163
+
1164
+ while not stop_button:
1165
+ success, frame = cap.read()
1166
+ if not success:
1167
+ st.error("Failed to capture frame from webcam")
1168
+ break
1169
+
1170
+ # Process the frame
1171
+ processed_frame = app.process_frame(frame)
1172
+
1173
+ # Convert BGR to RGB for Streamlit
1174
+ processed_frame_rgb = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB)
1175
+
1176
+ # Display the processed frame
1177
+ webcam_placeholder.image(processed_frame_rgb, channels="RGB", caption="Live Try-On")
1178
+
1179
+ # Check if stop button was pressed
1180
+ if stop_button:
1181
+ break
1182
+
1183
+ # Small sleep to reduce CPU usage
1184
+ time.sleep(0.01)
1185
+
1186
+ # Recheck the stop button status
1187
+ stop_button = st.button("Stop")
1188
+
1189
+ # Clean up
1190
+ cap.release()
1191
+
1192
+ except Exception as e:
1193
+ st.error(f"Error: {e}")
1194
+ if 'app' in locals():
1195
+ app.clean_up()
1196
+
1197
+ finally:
1198
+ # Remove temporary file
1199
+ if temp_garment_file:
1200
+ try:
1201
+ os.unlink(temp_garment_file.name)
1202
+ except:
1203
+ pass
1204
 
1205
+ def main():
1206
+ args = parse_args()
1207
+ try:
1208
+ # Check which mode to run in
1209
+ if args.streamlit:
1210
+ run_streamlit_app()
1211
+ elif args.tkinter or (not args.garment and not args.streamlit):
1212
+ # Use tkinter by default if no garment is specified
1213
+ run_tkinter_app(args.webcam, args.resolution)
1214
+ else:
1215
+ # Traditional command-line mode if garment is specified
1216
+ width, height = map(int, args.resolution.split('x'))
1217
+ app = AdvancedVirtualTryOn(args.garment, args.webcam, args.resolution)
1218
+ app.run()
1219
+ except Exception as e:
1220
+ print(f"Error: {e}")
1221
+ import traceback
1222
+ traceback.print_exc()
1223
 
1224
+ if __name__ == "__main__":
1225
+ main()