File size: 4,262 Bytes
5029d29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
"""Standalone CLI runner for the LSF interpreter (no web server).

Opens a window showing the live camera feed with the holistic skeleton drawn on
top and the running transcription printed in the console and on-screen.

Keys
----
  q / Esc : quit
  r       : start recording a sign sample; you'll be asked for its gloss in the
            console, then perform the sign. Press  s  to save it as a template.
  s       : stop + save the current recording
  c       : clear the on-screen transcript

For the Django web UI instead, run:  python web/manage.py runserver
"""

from __future__ import annotations

import argparse
import sys
import time
from collections import deque

import cv2

from lsf.camera import InterpreterPipeline


def parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(description="French Sign Language interpreter (CLI).")
    p.add_argument("--camera", type=int, default=0, help="Camera index (default 0).")
    p.add_argument(
        "--templates",
        default="signs/lsf_signs.json",
        help="Path to the learned-sign templates file.",
    )
    p.add_argument("--model", default=None, help="Optional Keras model.h5 path.")
    p.add_argument(
        "--monocular-depth",
        action="store_true",
        help="Enable MiDaS monocular depth fallback (needs torch installed).",
    )
    return p.parse_args()


def main() -> int:
    args = parse_args()
    cap = cv2.VideoCapture(args.camera)
    if not cap.isOpened():
        print(f"Error: could not open camera index {args.camera}.", file=sys.stderr)
        return 1

    pipeline = InterpreterPipeline(
        templates_path=args.templates,
        model_path=args.model,
        enable_monocular_depth=args.monocular_depth,
    )
    pipeline.probe_depth(cap)
    print(f"Depth source: {pipeline.depth.status} ({pipeline.depth.reason})")
    print("Press 'r' to record a sign, 's' to save, 'c' to clear, 'q' to quit.")

    transcript: deque[str] = deque(maxlen=8)
    recording = False
    fps = 0.0
    last = time.time()

    try:
        while True:
            ok, frame = cap.read()
            if not ok:
                break
            frame = cv2.flip(frame, 1)
            result = pipeline.process(frame)

            if result.recognition is not None:
                line = f"{result.recognition.gloss} ({result.recognition.confidence:.2f})"
                transcript.append(line)
                print("→", line)

            now = time.time()
            fps = 0.9 * fps + 0.1 * (1.0 / max(now - last, 1e-3))
            last = now

            _draw_overlay(result.annotated, transcript, result.depth_source, fps, recording)
            cv2.imshow("LSF interpreter", result.annotated)

            key = cv2.waitKey(1) & 0xFF
            if key in (ord("q"), 27):
                break
            elif key == ord("r") and not recording:
                label = input("Gloss for this sign (then perform it): ").strip()
                if label:
                    pipeline.recognizer.start_recording(label)
                    recording = True
                    print(f"Recording '{label}'… press 's' to save.")
            elif key == ord("s") and recording:
                saved = pipeline.recognizer.stop_recording()
                recording = False
                print(f"Saved template: {saved}" if saved else "Recording too short, discarded.")
            elif key == ord("c"):
                transcript.clear()
    finally:
        cap.release()
        cv2.destroyAllWindows()
        pipeline.close()
    return 0


def _draw_overlay(frame, transcript, depth_source, fps, recording) -> None:
    h = frame.shape[0]
    status = "REC" if recording else ""
    hud = f"depth:{depth_source}  fps:{fps:0.0f}  {status}"
    cv2.putText(frame, hud, (10, 24), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 4)
    cv2.putText(frame, hud, (10, 24), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 128), 1)
    for i, line in enumerate(list(transcript)[-6:]):
        y = h - 20 - (5 - i) * 26
        cv2.putText(frame, line, (10, y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 4)
        cv2.putText(frame, line, (10, y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 1)


if __name__ == "__main__":
    raise SystemExit(main())