rafiaashraf commited on
Commit
510c00e
·
verified ·
1 Parent(s): 1b6f199

Upload 4 files

Browse files

Size Recommendation Model

Files changed (4) hide show
  1. Dockerfile +25 -0
  2. app.py +374 -0
  3. requirements.txt +0 -0
  4. space.yaml +1 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Install system dependencies
4
+ RUN apt-get update && apt-get install -y \
5
+ ffmpeg \
6
+ libsm6 \
7
+ libxext6 \
8
+ libgl1-mesa-glx \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Set working directory
12
+ WORKDIR /app
13
+
14
+ # Copy files
15
+ COPY . /app
16
+
17
+ # Install dependencies
18
+ RUN pip install --upgrade pip
19
+ RUN pip install -r requirements.txt
20
+
21
+ # Expose port
22
+ EXPOSE 7860
23
+
24
+ # Run Flask app
25
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,374 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import cv2
4
+ import numpy as np
5
+ import mediapipe as mp
6
+ import torch
7
+ from flask import Flask, request, jsonify
8
+ import torch.nn.functional as F
9
+
10
+
11
+ app = Flask(__name__)
12
+
13
+ mp_pose = mp.solutions.pose
14
+ mp_holistic = mp.solutions.holistic
15
+ pose = mp_pose.Pose(model_complexity=2) # Improved accuracy
16
+ holistic = mp_holistic.Holistic() # For refining pose
17
+
18
+ KNOWN_OBJECT_WIDTH_CM = 21.0 # A4 paper width in cm
19
+ FOCAL_LENGTH = 600 # Default focal length
20
+ DEFAULT_HEIGHT_CM = 152.0 # Default height if not provided
21
+
22
+ # Load depth estimation model
23
+ def load_depth_model():
24
+ model = torch.hub.load("intel-isl/MiDaS", "MiDaS_small")
25
+ model.eval()
26
+ return model
27
+
28
+ depth_model = load_depth_model()
29
+
30
+ def calibrate_focal_length(image, real_width_cm, detected_width_px):
31
+ """Dynamically calibrates focal length using a known object."""
32
+ return (detected_width_px * FOCAL_LENGTH) / real_width_cm if detected_width_px else FOCAL_LENGTH
33
+
34
+
35
+
36
+ def detect_reference_object(image):
37
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
38
+ edges = cv2.Canny(gray, 50, 150)
39
+ contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
40
+ if contours:
41
+ largest_contour = max(contours, key=cv2.contourArea)
42
+ x, y, w, h = cv2.boundingRect(largest_contour)
43
+ focal_length = calibrate_focal_length(image, KNOWN_OBJECT_WIDTH_CM, w)
44
+ scale_factor = KNOWN_OBJECT_WIDTH_CM / w
45
+ return scale_factor, focal_length
46
+ return 0.05, FOCAL_LENGTH
47
+
48
+ def estimate_depth(image):
49
+ """Uses AI-based depth estimation to improve circumference calculations."""
50
+ input_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) / 255.0
51
+ input_tensor = torch.tensor(input_image, dtype=torch.float32).permute(2, 0, 1).unsqueeze(0)
52
+
53
+ # Resize input to match MiDaS model input size
54
+ input_tensor = F.interpolate(input_tensor, size=(384, 384), mode="bilinear", align_corners=False)
55
+
56
+ with torch.no_grad():
57
+ depth_map = depth_model(input_tensor)
58
+
59
+ return depth_map.squeeze().numpy()
60
+
61
+ def calculate_distance_using_height(landmarks, image_height, user_height_cm):
62
+ """Calculate distance using the user's known height."""
63
+ top_head = landmarks[mp_pose.PoseLandmark.NOSE.value].y * image_height
64
+ bottom_foot = max(
65
+ landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].y,
66
+ landmarks[mp_pose.PoseLandmark.RIGHT_ANKLE.value].y
67
+ ) * image_height
68
+
69
+ person_height_px = abs(bottom_foot - top_head)
70
+
71
+ # Using the formula: distance = (actual_height_cm * focal_length) / height_in_pixels
72
+ distance = (user_height_cm * FOCAL_LENGTH) / person_height_px
73
+
74
+ # Calculate more accurate scale_factor based on known height
75
+ scale_factor = user_height_cm / person_height_px
76
+
77
+ return distance, scale_factor
78
+
79
+ def get_body_width_at_height(frame, height_px, center_x):
80
+ """Scan horizontally at a specific height to find body edges."""
81
+ # Convert to grayscale and apply threshold
82
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
83
+ blur = cv2.GaussianBlur(gray, (5, 5), 0)
84
+ _, thresh = cv2.threshold(blur, 50, 255, cv2.THRESH_BINARY)
85
+
86
+ # Ensure height_px is within image bounds
87
+ if height_px >= frame.shape[0]:
88
+ height_px = frame.shape[0] - 1
89
+
90
+ # Get horizontal line at the specified height
91
+ horizontal_line = thresh[height_px, :]
92
+
93
+ # Find left and right edges starting from center
94
+ center_x = int(center_x * frame.shape[1])
95
+ left_edge, right_edge = center_x, center_x
96
+
97
+ # Scan from center to left
98
+ for i in range(center_x, 0, -1):
99
+ if horizontal_line[i] == 0: # Found edge (black pixel)
100
+ left_edge = i
101
+ break
102
+
103
+ # Scan from center to right
104
+ for i in range(center_x, len(horizontal_line)):
105
+ if horizontal_line[i] == 0: # Found edge (black pixel)
106
+ right_edge = i
107
+ break
108
+
109
+ width_px = right_edge - left_edge
110
+
111
+ # If width is unreasonably small, apply a minimum width
112
+ min_width = 0.1 * frame.shape[1] # Minimum width as 10% of image width
113
+ if width_px < min_width:
114
+ width_px = min_width
115
+
116
+ return width_px
117
+
118
+ def calculate_measurements(results, scale_factor, image_width, image_height, depth_map, frame=None, user_height_cm=None):
119
+ landmarks = results.pose_landmarks.landmark
120
+
121
+ # If user's height is provided, use it to get a more accurate scale factor
122
+ if user_height_cm:
123
+ _, scale_factor = calculate_distance_using_height(landmarks, image_height, user_height_cm)
124
+
125
+ def pixel_to_cm(value):
126
+ return round(value * scale_factor, 2)
127
+
128
+ def calculate_circumference(width_px, depth_ratio=1.0):
129
+ """Estimate circumference using width and depth adjustment."""
130
+ # Using a simplified elliptical approximation: C ≈ 2π * sqrt((a² + b²)/2)
131
+ # where a is half the width and b is estimated depth
132
+ width_cm = width_px * scale_factor
133
+ estimated_depth_cm = width_cm * depth_ratio * 0.7 # Depth is typically ~70% of width for torso
134
+ half_width = width_cm / 2
135
+ half_depth = estimated_depth_cm / 2
136
+ return round(2 * np.pi * np.sqrt((half_width**2 + half_depth**2) / 2), 2)
137
+
138
+ measurements = {}
139
+
140
+ # Shoulder Width
141
+ left_shoulder = landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value]
142
+ right_shoulder = landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value]
143
+ shoulder_width_px = abs(left_shoulder.x * image_width - right_shoulder.x * image_width)
144
+
145
+ # Apply a slight correction factor for shoulders (they're usually detected well)
146
+ shoulder_correction = 1.1 # 10% wider
147
+ shoulder_width_px *= shoulder_correction
148
+
149
+ measurements["shoulder_width"] = pixel_to_cm(shoulder_width_px)
150
+
151
+ # Chest/Bust Measurement
152
+ chest_y_ratio = 0.15 # Approximately 15% down from shoulder to hip
153
+ chest_y = left_shoulder.y + (landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].y - left_shoulder.y) * chest_y_ratio
154
+
155
+ chest_correction = 1.15 # 15% wider than detected width
156
+ chest_width_px = abs((right_shoulder.x - left_shoulder.x) * image_width) * chest_correction
157
+
158
+ if frame is not None:
159
+ chest_y_px = int(chest_y * image_height)
160
+ center_x = (left_shoulder.x + right_shoulder.x) / 2
161
+ detected_width = get_body_width_at_height(frame, chest_y_px, center_x)
162
+ if detected_width > 0:
163
+ chest_width_px = max(chest_width_px, detected_width)
164
+
165
+ chest_depth_ratio = 1.0
166
+ if depth_map is not None:
167
+ chest_x = int(((left_shoulder.x + right_shoulder.x) / 2) * image_width)
168
+ chest_y_px = int(chest_y * image_height)
169
+ scale_y = 384 / image_height
170
+ scale_x = 384 / image_width
171
+ chest_y_scaled = int(chest_y_px * scale_y)
172
+ chest_x_scaled = int(chest_x * scale_x)
173
+ if 0 <= chest_y_scaled < 384 and 0 <= chest_x_scaled < 384:
174
+ chest_depth = depth_map[chest_y_scaled, chest_x_scaled]
175
+ max_depth = np.max(depth_map)
176
+ chest_depth_ratio = 1.0 + 0.5 * (1.0 - chest_depth / max_depth)
177
+
178
+ measurements["chest_width"] = pixel_to_cm(chest_width_px)
179
+ measurements["chest_circumference"] = calculate_circumference(chest_width_px, chest_depth_ratio)
180
+
181
+
182
+ # Waist Measurement
183
+ left_hip = landmarks[mp_pose.PoseLandmark.LEFT_HIP.value]
184
+ right_hip = landmarks[mp_pose.PoseLandmark.RIGHT_HIP.value]
185
+
186
+ # Adjust waist_y_ratio to better reflect the natural waistline
187
+ waist_y_ratio = 0.35 # 35% down from shoulder to hip (higher than before)
188
+ waist_y = left_shoulder.y + (left_hip.y - left_shoulder.y) * waist_y_ratio
189
+
190
+ # Use contour detection to dynamically estimate waist width
191
+ if frame is not None:
192
+ waist_y_px = int(waist_y * image_height)
193
+ center_x = (left_hip.x + right_hip.x) / 2
194
+ detected_width = get_body_width_at_height(frame, waist_y_px, center_x)
195
+ if detected_width > 0:
196
+ waist_width_px = detected_width
197
+ else:
198
+ # Fallback to hip width if contour detection fails
199
+ waist_width_px = abs(right_hip.x - left_hip.x) * image_width * 0.9 # 90% of hip width
200
+ else:
201
+ # Fallback to hip width if no frame is provided
202
+ waist_width_px = abs(right_hip.x - left_hip.x) * image_width * 0.9 # 90% of hip width
203
+
204
+ # Apply 30% correction factor to waist width
205
+ waist_correction = 1.16 # 30% wider
206
+ waist_width_px *= waist_correction
207
+
208
+ # Get depth adjustment for waist if available
209
+ waist_depth_ratio = 1.0
210
+ if depth_map is not None:
211
+ waist_x = int(((left_hip.x + right_hip.x) / 2) * image_width)
212
+ waist_y_px = int(waist_y * image_height)
213
+ scale_y = 384 / image_height
214
+ scale_x = 384 / image_width
215
+ waist_y_scaled = int(waist_y_px * scale_y)
216
+ waist_x_scaled = int(waist_x * scale_x)
217
+ if 0 <= waist_y_scaled < 384 and 0 <= waist_x_scaled < 384:
218
+ waist_depth = depth_map[waist_y_scaled, waist_x_scaled]
219
+ max_depth = np.max(depth_map)
220
+ waist_depth_ratio = 1.0 + 0.5 * (1.0 - waist_depth / max_depth)
221
+
222
+ measurements["waist_width"] = pixel_to_cm(waist_width_px)
223
+ measurements["waist"] = calculate_circumference(waist_width_px, waist_depth_ratio)
224
+ # Hip Measurement
225
+ hip_correction = 1.35 # Hips are typically 35% wider than detected landmarks
226
+ hip_width_px = abs(left_hip.x * image_width - right_hip.x * image_width) * hip_correction
227
+
228
+ if frame is not None:
229
+ hip_y_offset = 0.1 # 10% down from hip landmarks
230
+ hip_y = left_hip.y + (landmarks[mp_pose.PoseLandmark.LEFT_KNEE.value].y - left_hip.y) * hip_y_offset
231
+ hip_y_px = int(hip_y * image_height)
232
+ center_x = (left_hip.x + right_hip.x) / 2
233
+ detected_width = get_body_width_at_height(frame, hip_y_px, center_x)
234
+ if detected_width > 0:
235
+ hip_width_px = max(hip_width_px, detected_width)
236
+
237
+ hip_depth_ratio = 1.0
238
+ if depth_map is not None:
239
+ hip_x = int(((left_hip.x + right_hip.x) / 2) * image_width)
240
+ hip_y_px = int(left_hip.y * image_height)
241
+ hip_y_scaled = int(hip_y_px * scale_y)
242
+ hip_x_scaled = int(hip_x * scale_x)
243
+ if 0 <= hip_y_scaled < 384 and 0 <= hip_x_scaled < 384:
244
+ hip_depth = depth_map[hip_y_scaled, hip_x_scaled]
245
+ max_depth = np.max(depth_map)
246
+ hip_depth_ratio = 1.0 + 0.5 * (1.0 - hip_depth / max_depth)
247
+
248
+ measurements["hip_width"] = pixel_to_cm(hip_width_px)
249
+ measurements["hip"] = calculate_circumference(hip_width_px, hip_depth_ratio)
250
+
251
+ # Other measurements (unchanged)
252
+ neck = landmarks[mp_pose.PoseLandmark.NOSE.value]
253
+ left_ear = landmarks[mp_pose.PoseLandmark.LEFT_EAR.value]
254
+ neck_width_px = abs(neck.x * image_width - left_ear.x * image_width) * 2.0
255
+ measurements["neck"] = calculate_circumference(neck_width_px, 1.0)
256
+ measurements["neck_width"] = pixel_to_cm(neck_width_px)
257
+
258
+ left_wrist = landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value]
259
+ sleeve_length_px = abs(left_shoulder.y * image_height - left_wrist.y * image_height)
260
+ measurements["arm_length"] = pixel_to_cm(sleeve_length_px)
261
+
262
+ shirt_length_px = abs(left_shoulder.y * image_height - left_hip.y * image_height) * 1.2
263
+ measurements["shirt_length"] = pixel_to_cm(shirt_length_px)
264
+
265
+ # Thigh Circumference (improved with depth information)
266
+ thigh_y_ratio = 0.2 # 20% down from hip to knee
267
+ left_knee = landmarks[mp_pose.PoseLandmark.LEFT_KNEE.value]
268
+ thigh_y = left_hip.y + (left_knee.y - left_hip.y) * thigh_y_ratio
269
+
270
+ # Apply correction factor for thigh width
271
+ thigh_correction = 1.2 # Thighs are typically wider than what can be estimated from front view
272
+ thigh_width_px = hip_width_px * 0.5 * thigh_correction # Base thigh width on hip width
273
+
274
+ # Use contour detection if frame is available
275
+ if frame is not None:
276
+ thigh_y_px = int(thigh_y * image_height)
277
+ thigh_x = left_hip.x * 0.9 # Move slightly inward from hip
278
+ detected_width = get_body_width_at_height(frame, thigh_y_px, thigh_x)
279
+ if detected_width > 0 and detected_width < hip_width_px: # Sanity check
280
+ thigh_width_px = detected_width # Use detected width
281
+
282
+ # If depth map is available, use it for thigh measurement
283
+ thigh_depth_ratio = 1.0
284
+ if depth_map is not None:
285
+ thigh_x = int(left_hip.x * image_width)
286
+ thigh_y_px = int(thigh_y * image_height)
287
+
288
+ # Scale coordinates to match depth map size
289
+ thigh_y_scaled = int(thigh_y_px * scale_y)
290
+ thigh_x_scaled = int(thigh_x * scale_x)
291
+
292
+ if 0 <= thigh_y_scaled < 384 and 0 <= thigh_x_scaled < 384:
293
+ thigh_depth = depth_map[thigh_y_scaled, thigh_x_scaled]
294
+ max_depth = np.max(depth_map)
295
+ thigh_depth_ratio = 1.0 + 0.5 * (1.0 - thigh_depth / max_depth)
296
+
297
+ measurements["thigh"] = pixel_to_cm(thigh_width_px)
298
+ measurements["thigh_circumference"] = calculate_circumference(thigh_width_px, thigh_depth_ratio)
299
+
300
+
301
+ left_ankle = landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value]
302
+ trouser_length_px = abs(left_hip.y * image_height - left_ankle.y * image_height)
303
+ measurements["trouser_length"] = pixel_to_cm(trouser_length_px)
304
+
305
+ return measurements
306
+
307
+ @app.route("/upload_images", methods=["POST"])
308
+ def upload_images():
309
+ if "front" not in request.files:
310
+ return jsonify({"error": "Missing front image for reference."}), 400
311
+
312
+ # Get user height if provided, otherwise use default
313
+ user_height_cm = request.form.get('height_cm')
314
+ print(user_height_cm)
315
+ if user_height_cm:
316
+ try:
317
+ user_height_cm = float(user_height_cm)
318
+ except ValueError:
319
+ user_height_cm = DEFAULT_HEIGHT_CM
320
+ else:
321
+ user_height_cm = DEFAULT_HEIGHT_CM
322
+
323
+ received_images = {pose_name: request.files[pose_name] for pose_name in ["front", "left_side", "right_side", "back"] if pose_name in request.files}
324
+ measurements, scale_factor, focal_length, results = {}, None, FOCAL_LENGTH, {}
325
+ frames = {}
326
+
327
+ for pose_name, image_file in received_images.items():
328
+ image_np = np.frombuffer(image_file.read(), np.uint8)
329
+ frame = cv2.imdecode(image_np, cv2.IMREAD_COLOR)
330
+ frames[pose_name] = frame # Store the frame for contour detection
331
+ rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
332
+ results[pose_name] = holistic.process(rgb_frame)
333
+ image_height, image_width, _ = frame.shape
334
+
335
+ if pose_name == "front":
336
+ # Always use height for calibration (default or provided)
337
+ if results[pose_name].pose_landmarks:
338
+ _, scale_factor = calculate_distance_using_height(
339
+ results[pose_name].pose_landmarks.landmark,
340
+ image_height,
341
+ user_height_cm
342
+ )
343
+ else:
344
+ # Fallback to object detection only if pose landmarks aren't detected
345
+ scale_factor, focal_length = detect_reference_object(frame)
346
+
347
+ depth_map = estimate_depth(frame) if pose_name in ["front", "left_side"] else None
348
+
349
+ if results[pose_name].pose_landmarks:
350
+ if pose_name == "front":
351
+ measurements.update(calculate_measurements(
352
+ results[pose_name],
353
+ scale_factor,
354
+ image_width,
355
+ image_height,
356
+ depth_map,
357
+ frames[pose_name], # Pass the frame for contour detection
358
+ user_height_cm
359
+ ))
360
+
361
+ # Debug information to help troubleshoot measurements
362
+ debug_info = {
363
+ "scale_factor": float(scale_factor) if scale_factor else None,
364
+ "focal_length": float(focal_length),
365
+ "user_height_cm": float(user_height_cm)
366
+ }
367
+
368
+ return jsonify({
369
+ "measurements": measurements,
370
+ "debug_info": debug_info
371
+ })
372
+
373
+ if __name__ == '__main__':
374
+ app.run(host='0.0.0.0', port=7860)
requirements.txt ADDED
Binary file (1.97 kB). View file
 
space.yaml ADDED
@@ -0,0 +1 @@
 
 
1
+ sdk: docker