Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -2,76 +2,68 @@ from fastapi import FastAPI, UploadFile, File
|
|
| 2 |
import cv2
|
| 3 |
import numpy as np
|
| 4 |
import mediapipe as mp
|
| 5 |
-
import torch
|
| 6 |
-
from ultralytics import YOLO
|
| 7 |
|
| 8 |
-
app = FastAPI()
|
| 9 |
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
mp_pose = mp.solutions.pose.Pose(static_image_mode=True)
|
| 13 |
|
| 14 |
-
# ----
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
def
|
| 17 |
-
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
return 170 / bbox_height_px
|
| 22 |
|
| 23 |
-
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
image_bytes = await file.read()
|
| 28 |
-
img_np = np.frombuffer(image_bytes, np.uint8)
|
| 29 |
-
image = cv2.imdecode(img_np, cv2.IMREAD_COLOR)
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
|
| 40 |
-
#
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
if not pose_result.pose_landmarks:
|
| 44 |
-
return {"error": "Pose not detected"}
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
left_hip = (lm[23].x * w, lm[23].y * h)
|
| 52 |
-
right_hip = (lm[24].x * w, lm[24].y * h)
|
| 53 |
|
| 54 |
-
|
| 55 |
-
hip_px = distance(left_hip, right_hip)
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
| 64 |
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
1.2 * shoulder_cm +
|
| 69 |
-
0.8 * hip_px * scale
|
| 70 |
-
) / 10
|
| 71 |
|
| 72 |
-
return
|
| 73 |
-
"height_cm": round(height_cm, 2),
|
| 74 |
-
"shoulder_cm": round(shoulder_cm, 2),
|
| 75 |
-
"weight_kg": round(weight_kg, 2),
|
| 76 |
-
"confidence": 0.9
|
| 77 |
-
}
|
|
|
|
| 2 |
import cv2
|
| 3 |
import numpy as np
|
| 4 |
import mediapipe as mp
|
|
|
|
|
|
|
| 5 |
|
| 6 |
+
app = FastAPI(title="Human Anthropometry API")
|
| 7 |
|
| 8 |
+
mp_pose = mp.solutions.pose
|
| 9 |
+
pose = mp_pose.Pose(static_image_mode=False)
|
|
|
|
| 10 |
|
| 11 |
+
# ---- Utility ----
|
| 12 |
+
def dist(a, b):
|
| 13 |
+
return np.linalg.norm(a - b)
|
| 14 |
|
| 15 |
+
def analyze_frame(image):
|
| 16 |
+
h, w, _ = image.shape
|
| 17 |
+
rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
| 18 |
+
res = pose.process(rgb)
|
| 19 |
|
| 20 |
+
if not res.pose_landmarks:
|
| 21 |
+
return None
|
|
|
|
| 22 |
|
| 23 |
+
lm = res.pose_landmarks.landmark
|
| 24 |
|
| 25 |
+
def p(i):
|
| 26 |
+
return np.array([lm[i].x * w, lm[i].y * h])
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
head = p(0)
|
| 29 |
+
l_sh = p(11)
|
| 30 |
+
r_sh = p(12)
|
| 31 |
+
l_hip = p(23)
|
| 32 |
+
r_hip = p(24)
|
| 33 |
+
l_heel = p(29)
|
| 34 |
+
r_heel = p(30)
|
| 35 |
|
| 36 |
+
shoulder_px = dist(l_sh, r_sh)
|
| 37 |
+
torso_px = dist((l_sh + r_sh)/2, (l_hip + r_hip)/2)
|
| 38 |
+
height_px = dist(head, (l_heel + r_heel)/2)
|
| 39 |
|
| 40 |
+
# ---- INDUSTRIAL NORMALIZATION ----
|
| 41 |
+
# Average adult shoulder width ≈ 0.23 × height
|
| 42 |
+
height_cm = (shoulder_px / height_px) * (1 / 0.23) * 100
|
|
|
|
|
|
|
| 43 |
|
| 44 |
+
shoulder_cm = height_cm * 0.23
|
| 45 |
+
torso_ratio = torso_px / height_px
|
| 46 |
|
| 47 |
+
# Weight estimation via body volume proxy (not BMI)
|
| 48 |
+
weight_kg = round((height_cm * shoulder_cm * torso_ratio) / 1000, 1)
|
|
|
|
|
|
|
| 49 |
|
| 50 |
+
confidence = round(min(0.95, 0.6 + torso_ratio), 2)
|
|
|
|
| 51 |
|
| 52 |
+
return {
|
| 53 |
+
"height_cm": round(height_cm, 1),
|
| 54 |
+
"shoulder_cm": round(shoulder_cm, 1),
|
| 55 |
+
"weight_kg": weight_kg,
|
| 56 |
+
"confidence": confidence
|
| 57 |
+
}
|
| 58 |
|
| 59 |
+
# ---- API ----
|
| 60 |
+
@app.post("/analyze")
|
| 61 |
+
async def analyze(file: UploadFile = File(...)):
|
| 62 |
+
data = await file.read()
|
| 63 |
+
img = cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR)
|
| 64 |
|
| 65 |
+
result = analyze_frame(img)
|
| 66 |
+
if not result:
|
| 67 |
+
return {"error": "Human not detected"}
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
+
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|