Download scripts/demo_video.py from minhy112/FallKLTN: direct link, hf CLI and curl.
- Browser
- Download file 6.34 kB
-
https://huggingface.co/minhy112/FallKLTN/resolve/main/scripts/demo_video.py
- Command line
-
hf download hf://minhy112/FallKLTN/scripts/demo_video.py
-
curl -L -o demo_video.py https://huggingface.co/minhy112/FallKLTN/resolve/main/scripts/demo_video.py
6.34 kB
| #!/usr/bin/env python3 | |
| from __future__ import annotations | |
| import argparse | |
| from collections import deque | |
| from pathlib import Path | |
| import cv2 | |
| import numpy as np | |
| import torch | |
| from torch import nn | |
| from fall_detection.features import normalize_pose_sequence | |
| from fall_detection.models import PoseGRU, PoseTCN | |
| from fall_detection.pose_extractor import MediaPipePoseExtractor | |
| CONNECTIONS = [ | |
| (11, 12), (11, 13), (13, 15), (12, 14), (14, 16), | |
| (11, 23), (12, 24), (23, 24), (23, 25), (25, 27), | |
| (24, 26), (26, 28), (27, 31), (28, 32), | |
| ] | |
| def draw_pose(frame: np.ndarray, pose: np.ndarray | None) -> None: | |
| if pose is None: | |
| return | |
| height, width = frame.shape[:2] | |
| for first, second in CONNECTIONS: | |
| if min(pose[first, 3], pose[second, 3]) < 0.35: | |
| continue | |
| p1 = (int(pose[first, 0] * width), int(pose[first, 1] * height)) | |
| p2 = (int(pose[second, 0] * width), int(pose[second, 1] * height)) | |
| cv2.line(frame, p1, p2, (70, 230, 70), 2) | |
| for point in pose: | |
| if point[3] >= 0.35: | |
| cv2.circle(frame, (int(point[0] * width), int(point[1] * height)), 2, (0, 255, 255), -1) | |
| def load_model(path: Path, device: torch.device) -> tuple[nn.Module, dict]: | |
| checkpoint = torch.load(path, map_location=device, weights_only=False) | |
| if checkpoint.get("architecture", "gru") == "tcn": | |
| model = PoseTCN( | |
| input_size=checkpoint["input_size"], | |
| channels=checkpoint["tcn_channels"], | |
| dropout=checkpoint["dropout"], | |
| ).to(device) | |
| else: | |
| model = PoseGRU( | |
| input_size=checkpoint["input_size"], | |
| hidden_size=checkpoint["hidden_size"], | |
| num_layers=checkpoint["num_layers"], | |
| dropout=checkpoint["dropout"], | |
| ).to(device) | |
| model.load_state_dict(checkpoint["state_dict"]) | |
| model.eval() | |
| return model, checkpoint | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description="Run fall detection on a video") | |
| parser.add_argument("--input", type=Path, required=True) | |
| parser.add_argument("--model", type=Path, default=Path("artifacts/experiments/urfd/pose_tcn/model.pt")) | |
| parser.add_argument("--output", type=Path) | |
| parser.add_argument("--target-fps", type=float, default=10.0) | |
| parser.add_argument("--smoothing", type=int, default=5) | |
| parser.add_argument("--alert-consecutive", type=int, default=3) | |
| args = parser.parse_args() | |
| output = args.output or args.input.with_name(args.input.stem + "_detected.mp4") | |
| output.parent.mkdir(parents=True, exist_ok=True) | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| model, checkpoint = load_model(args.model, device) | |
| sequence_length = int(checkpoint["sequence_length"]) | |
| feature_mean = np.asarray(checkpoint["feature_mean"], dtype=np.float32) | |
| feature_std = np.asarray(checkpoint["feature_std"], dtype=np.float32) | |
| threshold = float(checkpoint["threshold"]) | |
| capture = cv2.VideoCapture(str(args.input)) | |
| if not capture.isOpened(): | |
| raise SystemExit(f"Cannot open {args.input}") | |
| source_fps = capture.get(cv2.CAP_PROP_FPS) or 25.0 | |
| width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
| height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
| writer = cv2.VideoWriter( | |
| str(output), cv2.VideoWriter_fourcc(*"mp4v"), source_fps, (width, height) | |
| ) | |
| sample_every = max(1, int(round(source_fps / args.target_fps))) | |
| pose_window: deque[np.ndarray] = deque(maxlen=sequence_length) | |
| probability_window: deque[float] = deque(maxlen=args.smoothing) | |
| probability = 0.0 | |
| consecutive = 0 | |
| frame_index = 0 | |
| with MediaPipePoseExtractor() as extractor: | |
| while True: | |
| ok, frame = capture.read() | |
| if not ok: | |
| break | |
| current_pose = None | |
| display_pose = None | |
| if frame_index % sample_every == 0: | |
| is_wide_urfd = frame.shape[1] / frame.shape[0] > 2.2 | |
| pose_frame = frame[:, frame.shape[1] // 2 :] if is_wide_urfd else frame | |
| current_pose = extractor.process_rgb(cv2.cvtColor(pose_frame, cv2.COLOR_BGR2RGB)) | |
| if current_pose is None: | |
| current_pose = pose_window[-1].copy() if pose_window else np.zeros((33, 4), dtype=np.float32) | |
| pose_window.append(current_pose) | |
| display_pose = current_pose.copy() | |
| if is_wide_urfd: | |
| display_pose[:, 0] = 0.5 + 0.5 * display_pose[:, 0] | |
| if len(pose_window) >= sequence_length: | |
| features = normalize_pose_sequence( | |
| np.stack(pose_window), checkpoint["visibility_threshold"] | |
| ) | |
| feature_indices = np.asarray( | |
| checkpoint.get("feature_indices", np.arange(features.shape[1])), | |
| dtype=np.int64, | |
| ) | |
| features = features[:, feature_indices] | |
| features = (features - feature_mean) / feature_std | |
| tensor = torch.from_numpy(features[None]).float().to(device) | |
| with torch.no_grad(): | |
| probability = float(torch.sigmoid(model(tensor)).item()) | |
| probability_window.append(probability) | |
| probability = float(np.mean(probability_window)) | |
| consecutive = consecutive + 1 if probability >= threshold else 0 | |
| draw_pose(frame, display_pose) | |
| alert = consecutive >= args.alert_consecutive | |
| color = (0, 0, 255) if alert else (0, 180, 0) | |
| status = "CANH BAO: TE NGA" if alert else "BINH THUONG" | |
| cv2.rectangle(frame, (10, 10), (385, 78), (0, 0, 0), -1) | |
| cv2.putText(frame, status, (20, 38), cv2.FONT_HERSHEY_SIMPLEX, 0.72, color, 2) | |
| cv2.putText( | |
| frame, | |
| f"P(fall)={probability:.2f} threshold={threshold:.2f}", | |
| (20, 66), | |
| cv2.FONT_HERSHEY_SIMPLEX, | |
| 0.50, | |
| (255, 255, 255), | |
| 1, | |
| ) | |
| writer.write(frame) | |
| frame_index += 1 | |
| capture.release() | |
| writer.release() | |
| print(f"Saved annotated video to {output}") | |
| if __name__ == "__main__": | |
| main() | |