Spaces:
Runtime error
Runtime error
Upload 4 files
Browse filesSize Recommendation Model
- Dockerfile +25 -0
- app.py +374 -0
- requirements.txt +0 -0
- 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
|