Update app.py
Browse files
app.py
CHANGED
|
@@ -414,9 +414,12 @@ class AutoResponse(BaseModel):
|
|
| 414 |
async def predict_auto(files: List[UploadFile] = File(...)):
|
| 415 |
"""
|
| 416 |
Accepts 60 JPEG frames.
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
|
|
|
|
|
|
|
|
|
| 420 |
"""
|
| 421 |
if not files:
|
| 422 |
raise HTTPException(status_code=400, detail="No frames provided")
|
|
@@ -427,27 +430,55 @@ async def predict_auto(files: List[UploadFile] = File(...)):
|
|
| 427 |
data = await file.read()
|
| 428 |
frame_data.append(data)
|
| 429 |
|
| 430 |
-
# ββ Step 2:
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
for data in frame_data:
|
| 434 |
-
mp_img = bytes_to_mp_image(data)
|
| 435 |
-
result = hand_detector_alpha.detect(mp_img)
|
| 436 |
-
hand_lms = result.hand_landmarks[0] if result.hand_landmarks else None
|
| 437 |
-
|
| 438 |
-
if hand_lms is None:
|
| 439 |
-
continue
|
| 440 |
-
|
| 441 |
-
skel = render_hand_skeleton(hand_lms)
|
| 442 |
-
tensor = infer_transform(skel).unsqueeze(0).to(device)
|
| 443 |
|
| 444 |
-
|
| 445 |
-
probs = torch.softmax(alphabet_model(tensor), dim=1)[0]
|
| 446 |
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 450 |
|
|
|
|
| 451 |
vote_ratio = 0.0
|
| 452 |
best_letter = ""
|
| 453 |
|
|
@@ -455,7 +486,6 @@ async def predict_auto(files: List[UploadFile] = File(...)):
|
|
| 455 |
best_letter = max(votes, key=votes.get)
|
| 456 |
vote_ratio = votes[best_letter] / sum(votes.values())
|
| 457 |
|
| 458 |
-
# ββ Step 3: Letter decision βββββββββββββββββββββββββββββββ
|
| 459 |
if vote_ratio >= 0.75 and best_letter:
|
| 460 |
return AutoResponse(
|
| 461 |
result=best_letter,
|
|
@@ -465,29 +495,7 @@ async def predict_auto(files: List[UploadFile] = File(...)):
|
|
| 465 |
detected=True
|
| 466 |
)
|
| 467 |
|
| 468 |
-
# ββ Step 4: LSTM on
|
| 469 |
-
coord_buffer = []
|
| 470 |
-
last_pose_vec = np.zeros(POSE_SIZE, dtype=np.float32)
|
| 471 |
-
last_left_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
|
| 472 |
-
last_right_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
|
| 473 |
-
|
| 474 |
-
for data in frame_data:
|
| 475 |
-
mp_img = bytes_to_mp_image(data)
|
| 476 |
-
|
| 477 |
-
pose_result = pose_detector.detect(mp_img)
|
| 478 |
-
pose_lms = pose_result.pose_landmarks[0] \
|
| 479 |
-
if pose_result.pose_landmarks else None
|
| 480 |
-
if pose_lms:
|
| 481 |
-
last_pose_vec = normalize_pose(pose_lms)
|
| 482 |
-
|
| 483 |
-
hand_result = hand_detector_phrase.detect(mp_img)
|
| 484 |
-
left_lms, right_lms = get_hands_by_side(hand_result)
|
| 485 |
-
if left_lms is not None: last_left_hand_vec = normalize_hand(left_lms)
|
| 486 |
-
if right_lms is not None: last_right_hand_vec = normalize_hand(right_lms)
|
| 487 |
-
|
| 488 |
-
vec = np.concatenate([last_pose_vec, last_left_hand_vec, last_right_hand_vec])
|
| 489 |
-
coord_buffer.append(vec)
|
| 490 |
-
|
| 491 |
if len(coord_buffer) < MIN_COORD_FRAMES:
|
| 492 |
return AutoResponse(
|
| 493 |
result="", result_type="phrase",
|
|
@@ -512,6 +520,10 @@ async def predict_auto(files: List[UploadFile] = File(...)):
|
|
| 512 |
detected=True
|
| 513 |
)
|
| 514 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 515 |
|
| 516 |
async def predict_phrase(files: List[UploadFile] = File(...)):
|
| 517 |
"""
|
|
|
|
| 414 |
async def predict_auto(files: List[UploadFile] = File(...)):
|
| 415 |
"""
|
| 416 |
Accepts 60 JPEG frames.
|
| 417 |
+
Optimization: runs MediaPipe on every other frame (30 calls instead of 60).
|
| 418 |
+
- CNN: votes on the 30 processed frames
|
| 419 |
+
- LSTM: 60 coord vectors β processed frames get real coords,
|
| 420 |
+
in-between frames reuse the previous frame's coords
|
| 421 |
+
1. If CNN vote_ratio >= 0.75 β return letter
|
| 422 |
+
2. Else β run LSTM on 60 coords β return phrase
|
| 423 |
"""
|
| 424 |
if not files:
|
| 425 |
raise HTTPException(status_code=400, detail="No frames provided")
|
|
|
|
| 430 |
data = await file.read()
|
| 431 |
frame_data.append(data)
|
| 432 |
|
| 433 |
+
# ββ Step 2: Run MediaPipe on every other frame ββββββββββββ
|
| 434 |
+
# For each frame, store: hand_lms, pose_vec, left_vec, right_vec
|
| 435 |
+
# Odd-indexed frames reuse the previous even frame's results
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
|
| 437 |
+
votes = {}
|
|
|
|
| 438 |
|
| 439 |
+
# LSTM coord buffer β 60 entries total
|
| 440 |
+
coord_buffer = []
|
| 441 |
+
last_pose_vec = np.zeros(POSE_SIZE, dtype=np.float32)
|
| 442 |
+
last_left_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
|
| 443 |
+
last_right_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
|
| 444 |
+
|
| 445 |
+
for i, data in enumerate(frame_data):
|
| 446 |
+
if i % 2 == 0:
|
| 447 |
+
# ββ Process this frame through MediaPipe ββββββββββ
|
| 448 |
+
mp_img = bytes_to_mp_image(data)
|
| 449 |
+
|
| 450 |
+
# CNN β hand landmarks
|
| 451 |
+
cnn_result = hand_detector_alpha.detect(mp_img)
|
| 452 |
+
hand_lms = cnn_result.hand_landmarks[0] \
|
| 453 |
+
if cnn_result.hand_landmarks else None
|
| 454 |
+
|
| 455 |
+
if hand_lms is not None:
|
| 456 |
+
skel = render_hand_skeleton(hand_lms)
|
| 457 |
+
tensor = infer_transform(skel).unsqueeze(0).to(device)
|
| 458 |
+
with torch.no_grad():
|
| 459 |
+
probs = torch.softmax(alphabet_model(tensor), dim=1)[0]
|
| 460 |
+
conf, idx = probs.max(dim=0)
|
| 461 |
+
letter = ALPHA_CLASSES[idx.item()]
|
| 462 |
+
votes[letter] = votes.get(letter, 0) + 1
|
| 463 |
+
|
| 464 |
+
# LSTM β pose + hand coords
|
| 465 |
+
pose_result = pose_detector.detect(mp_img)
|
| 466 |
+
pose_lms = pose_result.pose_landmarks[0] \
|
| 467 |
+
if pose_result.pose_landmarks else None
|
| 468 |
+
if pose_lms:
|
| 469 |
+
last_pose_vec = normalize_pose(pose_lms)
|
| 470 |
+
|
| 471 |
+
lstm_result = hand_detector_phrase.detect(mp_img)
|
| 472 |
+
left_lms, right_lms = get_hands_by_side(lstm_result)
|
| 473 |
+
if left_lms is not None: last_left_hand_vec = normalize_hand(left_lms)
|
| 474 |
+
if right_lms is not None: last_right_hand_vec = normalize_hand(right_lms)
|
| 475 |
+
|
| 476 |
+
# Both even and odd frames get a coord entry
|
| 477 |
+
# Odd frames reuse the last updated coords (from the previous even frame)
|
| 478 |
+
vec = np.concatenate([last_pose_vec, last_left_hand_vec, last_right_hand_vec])
|
| 479 |
+
coord_buffer.append(vec)
|
| 480 |
|
| 481 |
+
# ββ Step 3: CNN vote decision βββββββββββββββββββββββββββββ
|
| 482 |
vote_ratio = 0.0
|
| 483 |
best_letter = ""
|
| 484 |
|
|
|
|
| 486 |
best_letter = max(votes, key=votes.get)
|
| 487 |
vote_ratio = votes[best_letter] / sum(votes.values())
|
| 488 |
|
|
|
|
| 489 |
if vote_ratio >= 0.75 and best_letter:
|
| 490 |
return AutoResponse(
|
| 491 |
result=best_letter,
|
|
|
|
| 495 |
detected=True
|
| 496 |
)
|
| 497 |
|
| 498 |
+
# ββ Step 4: LSTM on 60 coords βββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
if len(coord_buffer) < MIN_COORD_FRAMES:
|
| 500 |
return AutoResponse(
|
| 501 |
result="", result_type="phrase",
|
|
|
|
| 520 |
detected=True
|
| 521 |
)
|
| 522 |
|
| 523 |
+
vote_ratio=vote_ratio,
|
| 524 |
+
detected=True
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
|
| 528 |
async def predict_phrase(files: List[UploadFile] = File(...)):
|
| 529 |
"""
|