kennn14 commited on
Commit
d526b08
Β·
verified Β·
1 Parent(s): b422d0f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -55
app.py CHANGED
@@ -57,6 +57,9 @@ MIN_HAND_PRESENCE_RATIO = 1 / 3
57
  # Fraction of processed frames with 2 hands to trigger automatic LSTM
58
  TWO_HAND_RATIO = 1 / 3
59
 
 
 
 
60
  # ============================================================
61
  # Model Architectures
62
  # ============================================================
@@ -196,10 +199,10 @@ download_if_missing(
196
  'pose_landmarker.task'
197
  )
198
 
199
- # IMAGE mode detectors
200
  hand_options_alphabet = mp_vision.HandLandmarkerOptions(
201
  base_options=mp_python.BaseOptions(model_asset_path='hand_landmarker.task'),
202
- running_mode=mp_vision.RunningMode.IMAGE,
203
  num_hands=1,
204
  min_hand_detection_confidence=0.7,
205
  min_hand_presence_confidence=0.6,
@@ -207,7 +210,7 @@ hand_options_alphabet = mp_vision.HandLandmarkerOptions(
207
  )
208
  hand_options_phrase = mp_vision.HandLandmarkerOptions(
209
  base_options=mp_python.BaseOptions(model_asset_path='hand_landmarker.task'),
210
- running_mode=mp_vision.RunningMode.IMAGE,
211
  num_hands=2,
212
  min_hand_detection_confidence=0.5,
213
  min_hand_presence_confidence=0.5,
@@ -215,7 +218,7 @@ hand_options_phrase = mp_vision.HandLandmarkerOptions(
215
  )
216
  pose_options = mp_vision.PoseLandmarkerOptions(
217
  base_options=mp_python.BaseOptions(model_asset_path='pose_landmarker.task'),
218
- running_mode=mp_vision.RunningMode.IMAGE,
219
  num_poses=1,
220
  min_pose_detection_confidence=0.3,
221
  min_pose_presence_confidence=0.3,
@@ -244,7 +247,7 @@ def bytes_to_mp_image(data: bytes):
244
  arr = np.frombuffer(data, np.uint8)
245
  frame = cv2.imdecode(arr, cv2.IMREAD_COLOR)
246
  frame = cv2.flip(frame, 1)
247
- #frame = cv2.convertScaleAbs(frame, alpha=1.3, beta=20)
248
  rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
249
  return mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)
250
 
@@ -382,7 +385,7 @@ def health():
382
  async def predict_alphabet(file: UploadFile = File(...)):
383
  data = await file.read()
384
  mp_img = bytes_to_mp_image(data)
385
- result = hand_detector_alpha.detect(mp_img)
386
  hand_lms = result.hand_landmarks[0] if result.hand_landmarks else None
387
 
388
  if hand_lms is None:
@@ -408,10 +411,10 @@ async def predict_alphabet_batch(files: List[UploadFile] = File(...)):
408
  raise HTTPException(status_code=400, detail="No frames provided")
409
 
410
  votes = {}
411
- for file in files:
412
  data = await file.read()
413
  mp_img = bytes_to_mp_image(data)
414
- result = hand_detector_alpha.detect(mp_img)
415
  hand_lms = result.hand_landmarks[0] if result.hand_landmarks else None
416
  if hand_lms is None:
417
  continue
@@ -435,13 +438,14 @@ async def predict_alphabet_batch(files: List[UploadFile] = File(...)):
435
  async def predict_auto(files: List[UploadFile] = File(...)):
436
  """
437
  Accepts 60 JPEG frames.
438
- Runs MediaPipe on every other frame (30 calls).
439
 
440
  Decision logic:
441
  1. If hand presence < 1/3 of processed frames β†’ not detected
442
  2. If 2 hands detected in >= 1/3 of processed frames β†’ skip CNN, go straight to LSTM
443
- 3. If CNN vote_ratio >= 0.50 β†’ return letter
444
- 4. Else β†’ run LSTM β†’ return phrase
 
445
  """
446
  if not files:
447
  raise HTTPException(status_code=400, detail="No frames provided")
@@ -452,7 +456,7 @@ async def predict_auto(files: List[UploadFile] = File(...)):
452
  frame_data.append(data)
453
 
454
  votes = {}
455
- total_processed_frames = 0
456
  total_hand_frames = 0
457
  two_hand_frames = 0
458
 
@@ -465,43 +469,42 @@ async def predict_auto(files: List[UploadFile] = File(...)):
465
  last_right_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
466
 
467
  for i, data in enumerate(frame_data):
468
- if i % 2 == 0:
469
- total_processed_frames += 1
470
- mp_img = bytes_to_mp_image(data)
471
-
472
- # CNN β€” single hand for alphabet
473
- cnn_result = hand_detector_alpha.detect(mp_img)
474
- hand_lms = cnn_result.hand_landmarks[0] \
475
- if cnn_result.hand_landmarks else None
476
-
477
- if hand_lms is not None:
478
- total_hand_frames += 1
479
- # Track wrist position for movement detection
480
- wrist_positions.append((hand_lms[WRIST].x, hand_lms[WRIST].y))
481
-
482
- skel = render_hand_skeleton(hand_lms)
483
- tensor = infer_transform(skel).unsqueeze(0).to(device)
484
- with torch.no_grad():
485
- probs = torch.softmax(alphabet_model(tensor), dim=1)[0]
486
- conf, idx = probs.max(dim=0)
487
- if conf.item() >= 0.6: # only count confident predictions
488
- letter = ALPHA_CLASSES[idx.item()]
489
- votes[letter] = votes.get(letter, 0) + 1
490
-
491
- # LSTM β€” pose + both hands
492
- pose_result = pose_detector.detect(mp_img)
493
- pose_lms = pose_result.pose_landmarks[0] \
494
- if pose_result.pose_landmarks else None
495
- if pose_lms:
496
- last_pose_vec = normalize_pose(pose_lms)
497
-
498
- lstm_result = hand_detector_phrase.detect(mp_img)
499
- left_lms, right_lms = get_hands_by_side(lstm_result)
500
- if left_lms is not None: last_left_hand_vec = normalize_hand(left_lms)
501
- if right_lms is not None: last_right_hand_vec = normalize_hand(right_lms)
502
-
503
- if left_lms is not None and right_lms is not None:
504
- two_hand_frames += 1
505
 
506
  vec = np.concatenate([last_pose_vec, last_left_hand_vec, last_right_hand_vec])
507
  coord_buffer.append(vec)
@@ -535,7 +538,6 @@ async def predict_auto(files: List[UploadFile] = File(...)):
535
 
536
  print(f"[auto] Wrist movement (midβ†’end): {movement:.4f}")
537
 
538
- MOVEMENT_THRESHOLD = 0.70 # ~20% of frame width, tunable
539
  if movement >= MOVEMENT_THRESHOLD:
540
  print(f"[auto] Significant movement ({movement:.4f}) β†’ LSTM")
541
  return run_lstm(coord_buffer, vote_ratio=0.0)
@@ -562,7 +564,7 @@ async def predict_auto(files: List[UploadFile] = File(...)):
562
  detected=True
563
  )
564
 
565
- # ── Step 4: LSTM ─────────────────────────────────────────
566
  return run_lstm(coord_buffer, vote_ratio)
567
 
568
 
@@ -576,17 +578,19 @@ async def predict_phrase(files: List[UploadFile] = File(...)):
576
  last_left_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
577
  last_right_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
578
 
579
- for file in files:
580
  data = await file.read()
581
  mp_img = bytes_to_mp_image(data)
582
-
583
- pose_result = pose_detector.detect(mp_img)
 
 
584
  pose_lms = pose_result.pose_landmarks[0] \
585
  if pose_result.pose_landmarks else None
586
  if pose_lms:
587
  last_pose_vec = normalize_pose(pose_lms)
588
 
589
- hand_result = hand_detector_phrase.detect(mp_img)
590
  left_lms, right_lms = get_hands_by_side(hand_result)
591
  if left_lms is not None: last_left_hand_vec = normalize_hand(left_lms)
592
  if right_lms is not None: last_right_hand_vec = normalize_hand(right_lms)
 
57
  # Fraction of processed frames with 2 hands to trigger automatic LSTM
58
  TWO_HAND_RATIO = 1 / 3
59
 
60
+ # Movement threshold for wrist position change (normalized coordinates)
61
+ MOVEMENT_THRESHOLD = 0.03 # ~3% of frame width
62
+
63
  # ============================================================
64
  # Model Architectures
65
  # ============================================================
 
199
  'pose_landmarker.task'
200
  )
201
 
202
+ # VIDEO mode detectors
203
  hand_options_alphabet = mp_vision.HandLandmarkerOptions(
204
  base_options=mp_python.BaseOptions(model_asset_path='hand_landmarker.task'),
205
+ running_mode=mp_vision.RunningMode.VIDEO,
206
  num_hands=1,
207
  min_hand_detection_confidence=0.7,
208
  min_hand_presence_confidence=0.6,
 
210
  )
211
  hand_options_phrase = mp_vision.HandLandmarkerOptions(
212
  base_options=mp_python.BaseOptions(model_asset_path='hand_landmarker.task'),
213
+ running_mode=mp_vision.RunningMode.VIDEO,
214
  num_hands=2,
215
  min_hand_detection_confidence=0.5,
216
  min_hand_presence_confidence=0.5,
 
218
  )
219
  pose_options = mp_vision.PoseLandmarkerOptions(
220
  base_options=mp_python.BaseOptions(model_asset_path='pose_landmarker.task'),
221
+ running_mode=mp_vision.RunningMode.VIDEO,
222
  num_poses=1,
223
  min_pose_detection_confidence=0.3,
224
  min_pose_presence_confidence=0.3,
 
247
  arr = np.frombuffer(data, np.uint8)
248
  frame = cv2.imdecode(arr, cv2.IMREAD_COLOR)
249
  frame = cv2.flip(frame, 1)
250
+ frame = cv2.convertScaleAbs(frame, alpha=1.3, beta=20)
251
  rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
252
  return mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)
253
 
 
385
  async def predict_alphabet(file: UploadFile = File(...)):
386
  data = await file.read()
387
  mp_img = bytes_to_mp_image(data)
388
+ result = hand_detector_alpha.detect_for_video(mp_img, 0)
389
  hand_lms = result.hand_landmarks[0] if result.hand_landmarks else None
390
 
391
  if hand_lms is None:
 
411
  raise HTTPException(status_code=400, detail="No frames provided")
412
 
413
  votes = {}
414
+ for i, file in enumerate(files):
415
  data = await file.read()
416
  mp_img = bytes_to_mp_image(data)
417
+ result = hand_detector_alpha.detect_for_video(mp_img, i * 33)
418
  hand_lms = result.hand_landmarks[0] if result.hand_landmarks else None
419
  if hand_lms is None:
420
  continue
 
438
  async def predict_auto(files: List[UploadFile] = File(...)):
439
  """
440
  Accepts 60 JPEG frames.
441
+ Processes all frames (no skipping).
442
 
443
  Decision logic:
444
  1. If hand presence < 1/3 of processed frames β†’ not detected
445
  2. If 2 hands detected in >= 1/3 of processed frames β†’ skip CNN, go straight to LSTM
446
+ 3. If wrist movement >= threshold β†’ skip CNN, go straight to LSTM
447
+ 4. If CNN vote_ratio >= 0.65 β†’ return letter
448
+ 5. Else β†’ run LSTM β†’ return phrase
449
  """
450
  if not files:
451
  raise HTTPException(status_code=400, detail="No frames provided")
 
456
  frame_data.append(data)
457
 
458
  votes = {}
459
+ total_processed_frames = len(frame_data)
460
  total_hand_frames = 0
461
  two_hand_frames = 0
462
 
 
469
  last_right_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
470
 
471
  for i, data in enumerate(frame_data):
472
+ timestamp_ms = int(i * (1000 / 30))
473
+ mp_img = bytes_to_mp_image(data)
474
+
475
+ # CNN β€” single hand for alphabet
476
+ cnn_result = hand_detector_alpha.detect_for_video(mp_img, timestamp_ms)
477
+ hand_lms = cnn_result.hand_landmarks[0] \
478
+ if cnn_result.hand_landmarks else None
479
+
480
+ if hand_lms is not None:
481
+ total_hand_frames += 1
482
+ # Track wrist position for movement detection
483
+ wrist_positions.append((hand_lms[WRIST].x, hand_lms[WRIST].y))
484
+
485
+ skel = render_hand_skeleton(hand_lms)
486
+ tensor = infer_transform(skel).unsqueeze(0).to(device)
487
+ with torch.no_grad():
488
+ probs = torch.softmax(alphabet_model(tensor), dim=1)[0]
489
+ conf, idx = probs.max(dim=0)
490
+ if conf.item() >= 0.6: # only count confident predictions
491
+ letter = ALPHA_CLASSES[idx.item()]
492
+ votes[letter] = votes.get(letter, 0) + 1
493
+
494
+ # LSTM β€” pose + both hands
495
+ pose_result = pose_detector.detect_for_video(mp_img, timestamp_ms)
496
+ pose_lms = pose_result.pose_landmarks[0] \
497
+ if pose_result.pose_landmarks else None
498
+ if pose_lms:
499
+ last_pose_vec = normalize_pose(pose_lms)
500
+
501
+ lstm_result = hand_detector_phrase.detect_for_video(mp_img, timestamp_ms)
502
+ left_lms, right_lms = get_hands_by_side(lstm_result)
503
+ if left_lms is not None: last_left_hand_vec = normalize_hand(left_lms)
504
+ if right_lms is not None: last_right_hand_vec = normalize_hand(right_lms)
505
+
506
+ if left_lms is not None and right_lms is not None:
507
+ two_hand_frames += 1
 
508
 
509
  vec = np.concatenate([last_pose_vec, last_left_hand_vec, last_right_hand_vec])
510
  coord_buffer.append(vec)
 
538
 
539
  print(f"[auto] Wrist movement (midβ†’end): {movement:.4f}")
540
 
 
541
  if movement >= MOVEMENT_THRESHOLD:
542
  print(f"[auto] Significant movement ({movement:.4f}) β†’ LSTM")
543
  return run_lstm(coord_buffer, vote_ratio=0.0)
 
564
  detected=True
565
  )
566
 
567
+ # ── Step 5: LSTM ─────────────────────────────────────────
568
  return run_lstm(coord_buffer, vote_ratio)
569
 
570
 
 
578
  last_left_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
579
  last_right_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
580
 
581
+ for i, file in enumerate(files):
582
  data = await file.read()
583
  mp_img = bytes_to_mp_image(data)
584
+
585
+ timestamp_ms = int(i * (1000 / 30))
586
+
587
+ pose_result = pose_detector.detect_for_video(mp_img, timestamp_ms)
588
  pose_lms = pose_result.pose_landmarks[0] \
589
  if pose_result.pose_landmarks else None
590
  if pose_lms:
591
  last_pose_vec = normalize_pose(pose_lms)
592
 
593
+ hand_result = hand_detector_phrase.detect_for_video(mp_img, timestamp_ms)
594
  left_lms, right_lms = get_hands_by_side(hand_result)
595
  if left_lms is not None: last_left_hand_vec = normalize_hand(left_lms)
596
  if right_lms is not None: last_right_hand_vec = normalize_hand(right_lms)