rafiaashraf commited on
Commit
c4b072f
·
verified ·
1 Parent(s): 0b4afc7

Update app.py

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