# -*- coding: utf-8 -*- """fall_detection_system.ipynb Automatically generated by Colab. Original file is located at https://colab.research.google.com/drive/15iogmvi7LKpzek-Zt_uNfiQoYdvVO-nY """ #@title ⬇️ Install Ultralytics + deps !pip -q install ultralytics opencv-python numpy requests import torch print("Torch:", torch.__version__, "| CUDA available:", torch.cuda.is_available()) from google.colab import drive drive.mount('/content/drive') # ==== BIG, BOLD FALL MESSAGE VERSION (with extra red alert banner) ==== import cv2, math, os, time import numpy as np from ultralytics import YOLO # --- EDIT PATHS --- INPUT_VIDEO = "/content/drive/MyDrive/AI ML Projects/Fall Detection System/Fall Tiktok.mp4" OUT_VIDEO = "/content/drive/MyDrive/AI ML Projects/Fall Detection System/out_fall_annotated.mp4" MODEL_WEIGHTS = "yolov8n-pose.pt" # or "yolov8s-pose.pt" # --- RULE THRESHOLDS (make easier if needed) --- CONFIDENCE = 0.25 IMG_SIZE = 640 ANGLE_MAX_FALL = 35 # <= ~horizontal torso HW_MAX_FALL = 0.80 # <= flat-ish bbox (height/width) PERSIST_FRAMES = 10 # consecutive frames to confirm FALL (try 8–12) STAND_MIN_ANG = 60 # standing if >= # COCO-17 skeleton edges COCO_EDGES = [ (5,6),(5,7),(7,9),(6,8),(8,10),(5,11),(6,12),(11,12), (11,13),(13,15),(12,14),(14,16),(0,5),(0,6) ] def torso_angle_deg(kps): try: sx = (kps[5][0] + kps[6][0]) / 2.0 sy = (kps[5][1] + kps[6][1]) / 2.0 hx = (kps[11][0] + kps[12][0]) / 2.0 hy = (kps[11][1] + kps[12][1]) / 2.0 dx, dy = abs(sx - hx), abs(sy - hy) return math.degrees(math.atan2(dy, dx)) # 0≈horizontal, 90≈vertical except: return 90.0 def draw_skeleton(img, kps, color, t=2): for a,b in COCO_EDGES: x1,y1 = int(kps[a][0]), int(kps[a][1]) x2,y2 = int(kps[b][0]), int(kps[b][1]) cv2.line(img, (x1,y1), (x2,y2), color, t) for (x,y) in kps[:, :2]: cv2.circle(img, (int(x),int(y)), 3, color, -1) def center_banner(img, text, color=(0,0,255)): """Huge centered banner + top strip + red border.""" H, W = img.shape[:2] # Center banner font_scale = max(1.0, min(W, H) / 600.0) * 1.8 thickness = 5 (tw, th), _ = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, thickness) bx1 = max(10, (W - (tw + 80)) // 2) by1 = max(10, (H - (th + 80)) // 2) bx2 = min(W-10, bx1 + tw + 80) by2 = min(H-10, by1 + th + 80) overlay = img.copy() cv2.rectangle(overlay, (bx1, by1), (bx2, by2), color, -1) cv2.addWeighted(overlay, 0.45, img, 0.55, 0, img) tx = bx1 + (bx2 - bx1 - tw) // 2 ty = by1 + (by2 - by1 + th) // 2 # white text with black outline cv2.putText(img, text, (tx+2, ty+2), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (0,0,0), thickness+2, cv2.LINE_AA) cv2.putText(img, text, (tx, ty), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255,255,255), thickness, cv2.LINE_AA) # Top strip banner strip_h = max(50, th + 30) cv2.rectangle(img, (0,0), (W, strip_h), color, -1) cv2.putText(img, "FALL DOWN DETECTED", (20, strip_h - 15), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255,255,255), 3, cv2.LINE_AA) # Red border cv2.rectangle(img, (0,0), (W-1,H-1), color, 12) def box_label(img, x1, y1, txt, color): """Black bg + bold text always visible even near frame top.""" font_scale = 0.95 thickness = 2 (tw, th), _ = cv2.getTextSize(txt, cv2.FONT_HERSHEY_SIMPLEX, font_scale, thickness) # Put label inside the box if the top is too near the frame edge y_text = y1 + th + 12 if y1 < (th + 14) else y1 - 8 # Compute background box bg_x1 = x1 bg_y1 = y_text - th - 10 if y1 >= (th + 14) else y1 + 2 bg_x2 = x1 + tw + 14 bg_y2 = y_text + 2 if y1 >= (th + 14) else y1 + th + 14 cv2.rectangle(img, (bg_x1, bg_y1), (bg_x2, bg_y2), (0,0,0), -1) cv2.putText(img, txt, (x1 + 6, y_text), cv2.FONT_HERSHEY_SIMPLEX, font_scale, color, thickness, cv2.LINE_AA) # --- Load model & video IO --- model = YOLO(MODEL_WEIGHTS) cap = cv2.VideoCapture(INPUT_VIDEO) if not cap.isOpened(): raise RuntimeError(f"Could not open: {INPUT_VIDEO}") fps = cap.get(cv2.CAP_PROP_FPS) or 25.0 W = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) or 1280) H = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) or 720) os.makedirs(os.path.dirname(OUT_VIDEO), exist_ok=True) fourcc = cv2.VideoWriter_fourcc(*"mp4v") writer = cv2.VideoWriter(OUT_VIDEO, fourcc, fps, (W, H)) persist = {} # pid -> frames satisfying fall-like condition frame_idx = 0 while True: ok, frame = cap.read() if not ok: break any_fall = False r = model(frame, conf=CONFIDENCE, imgsz=IMG_SIZE, verbose=False)[0] if r.boxes is not None and r.keypoints is not None: boxes = r.boxes.xyxy.cpu().numpy() kpss = r.keypoints.xy.cpu().numpy() for i, box in enumerate(boxes): x1,y1,x2,y2 = map(int, box) h = max(1, y2-y1); w = max(1, x2-x1) hw = h/float(w) kps = kpss[i] ang = torso_angle_deg(kps) # fall-like this frame; then persistence to confirm fall_like = (ang <= ANGLE_MAX_FALL) and (hw <= HW_MAX_FALL) pid = i persist[pid] = persist.get(pid, 0) + 1 if fall_like else max(0, persist.get(pid, 0) - 1) fallen = persist[pid] >= PERSIST_FRAMES if fallen: any_fall = True color, thick = (0,0,255), 4 cv2.rectangle(frame, (x1,y1), (x2,y2), color, thick) draw_skeleton(frame, kps, color, t=2) # Super clear per-person message (inside or near the box) box_label(frame, x1, y1, "PERSON FALLEN", (0,0,255)) else: # show status for context (standing vs other) if ang >= STAND_MIN_ANG: color, label = (0,255,0), "STANDING" else: color, label = (0,165,255), "MOVING/OTHER" cv2.rectangle(frame, (x1,y1), (x2,y2), color, 2) draw_skeleton(frame, kps, color, t=2) box_label(frame, x1, y1, f"{label} θ={ang:.0f} h/w={hw:.2f}", color) # 🔴 EXTRA-LOUD global banner if any person is fallen if any_fall: center_banner(frame, "FALL DOWN DETECTED", color=(0,0,255)) writer.write(frame) frame_idx += 1 cap.release() writer.release() print(f"[OK] Saved annotated video to:\n{OUT_VIDEO}")