SamDNX's picture
Initial commit: LSF (French Sign Language) interpreter
5029d29
Raw
History Blame Contribute Delete
4.26 kB
"""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())