Fall Detection - XGBoost Classifier

Model description

  • Model name: XGBoost Priority 1 Fall Detection Classifier
  • Version: v1 (Experiment 01)
  • Status: experimantal
  • Repository visibility: internal
  • Model type: binary gradient-boosted classifier (xgboost.XGBClassifier)
  • Input contract: 51 keypoint features only (17 keypoints Γ— x, y, confidence) + bbox (x1,y1,x2,y2) β€” the extra 5 features (aspect_ratio, nose_relative_y, torso_angle, norm_com_y, head_hip_v_dist) are extracted in scripts/run.py, not received from upstream; no RGB image input
  • Output contract: Fall / Normal (P(Fall) >= 0.70 β†’ Fall)
  • Model artifact: models/xgboost_priority1_fall_model.pkl
  • Reference date: 2026-09-21

This repository documents only the classifier stage of the fall detection system. Input contract: 51 keypoint features + bbox only. The model itself is fed a 56-column pandas DataFrame that scripts/run.py builds by extracting the extra 5 features from that input β€” upstream never provides the 5, and the model never sees pixels, frames, or CSV files directly.

CCTV IMAGE β†’ YOLO26x-Pose β†’ 51 features + bbox (DataFrame) β†’ run.py: +5 features β†’ 56-col DataFrame β†’ XGBoost β†’ Fall / Normal
              upstream (out of scope)                         this repo                              ↑ direct model input

The classifier achieves 91.00% accuracy on the standard test set, 92.67% accuracy / 90.91% F1 on real-world CCTV evaluation, and up to 98.05% precision for fall events at a strict probability threshold.

Problem statement

Classifying falls directly from raw keypoint coordinates suffers from a severe domain shift: a raw-keypoint baseline reached 92.22% accuracy on a standard train/test split but collapsed to 66.83% on real-world CCTV footage.

Analysis of real-world failures revealed three critical bottlenecks:

  1. Camera Angle & Height Sensitivity: Raw coordinates (X, Y) shift significantly when camera pitch, installation height, or lens focal length changes
  2. Crowd & Limb Occlusion: Lower-body keypoints (knees, ankles) are frequently obscured by furniture or bystanders, causing standard feature vectors to drop key geometry
  3. Loss of Relative Posture Ratios: Single coordinates lack explicit representation of body aspect ratios, torso inclination, and center-of-mass vertical drops

This model resolves the problem with an engineered 56-feature representation incorporating 5 Priority 1 CCTV-invariant features, improving real-world accuracy to 88.36%+ while keeping 91% on standard test sets.

Input contract

This model/pipeline does not take an RGB image. The input contract is only the 51 keypoint features (+ bounding box needed to derive the engineered features). The extra 5 features are extracted inside scripts/run.py β€” they are never part of the upstream input.

What goes IN (upstream β†’ run.py): 51 features + bbox

  1. Upstream (out of scope): YOLO-Pose β†’ 17 keypoints β†’ 51 features only (x_i, y_i, conf_i for i = 0…16, x/y normalized to bbox) + bounding box (x1, y1, x2, y2), delivered as a pandas DataFrame
  2. scripts/run.py (this repo): extracts the 5 engineered features from that input β†’ builds the 56-column DataFrame β†’ passes it to XGBoost
CCTV IMAGE
    β”‚
    β–Ό
YOLO26x-Pose                          (upstream)
    β”‚
    β”œβ”€β”€ Person bounding box (x1, y1, x2, y2)
    β”‚
    └── 17 pose keypoints (x, y, confidence)
              β”‚
              β–Ό
     INPUT CONTRACT: 51 features + bbox   (pandas DataFrame)
              β”‚
              β”‚   scripts/run.py extracts:
              β”œβ”€β”€ aspect_ratio
              β”œβ”€β”€ nose_relative_y
              β”œβ”€β”€ torso_angle
              β”œβ”€β”€ norm_com_y
              └── head_hip_v_dist
              β”‚
              β–Ό
     56-column DataFrame  β†’  XGBoost.predict_proba

Input columns accepted by run.py

Columns Count Source
x_i, y_i, conf_i for i = 0…16 51 Upstream (input contract)
x1, y1, x2, y2 4 Upstream bbox (needed to compute the 5)
55 Total input to run.py
  • Dtype: float; NaN allowed for occluded keypoints (conf < 0.50 β†’ x/y = NaN)
  • Not accepted: RGB/BGR images, video frames, raw pixels β€” only the 51 features + bbox DataFrame
  • Validation: each row should have β‰₯ 5 valid keypoints; otherwise prediction is skipped
  • Cardinality: one row = one person = one prediction. No identity tracking across frames

What run.py extracts (the extra 5 β€” computed here, not received)

The 5 engineered features are derived inside run.py from the 51 + bbox input and appended as columns 52–56:

Pos Feature Formula Derived from
52 aspect_ratio w / h bbox
53 nose_relative_y (Y_nose - y1) / h keypoint 0 (nose)
54 torso_angle Angle HipMid β†’ ShoulderMid vs. vertical Y-axis (degrees) keypoints 5,6,11,12 + bbox
55 norm_com_y Confidence-weighted CoM Y: (Ξ£(Y_iΒ·conf_i)/Ξ£(conf_i) - y1)/h all visible keypoints
56 head_hip_v_dist (Y_hip_mid - Y_nose) / h keypoints 0,11,12

What XGBoost receives (after run.py)

  • Type: pandas.DataFrame (in-memory; not a CSV file)
  • Shape: (n, 56) β€” 51 input features + 5 extracted in run.py
  • Column order: FEATURE_COLS in scripts/run.py (positions 1–51 upstream, 52–56 computed here)
  • CSV is never fed to the model β€” it is only a transport format into a DataFrame

Feature engineering rationale

The 5 Priority 1 CCTV-invariant features give the classifier a representation that raw coordinates cannot provide:

Feature Name Formula CCTV Robustness Physical Significance
aspect_ratio W / H Very High Bounding box horizontal spread. Upright: < 1.0, Lying: > 1.0
nose_relative_y (Y_nose - Y1) / H Very High Head position in box. Standing: 0.0–0.25, Fallen: 0.7–1.0
torso_angle Angle(Shoulder-Mid β†’ Hip-Mid) vs Y-axis High Resists perspective. Standing: ~0Β°, Fallen: 80–90Β°
norm_com_y Weighted CoM using visible keypoints High Vertical center-of-mass. Robust to leg occlusion
head_hip_v_dist (Y_hip - Y_nose) / H High Upper-body extension. Collapses near 0.0 when lying flat

These features provide:

  • Scale invariance: Ratios and normalized coordinates remain stable across camera distances
  • Angle invariance: Geometric relationships resist perspective distortion
  • Occlusion robustness: Upper-body features work even when the lower body is blocked

Pipeline architecture

CCTV IMAGE
    β”‚
    β–Ό
YOLO26x-Pose
    β”‚
    β”œβ”€β”€ Person bounding box
    β”‚
    └── 17 pose keypoints
          β”‚
          β”œβ”€β”€ x
          β”œβ”€β”€ y
          └── confidence
                 β”‚
                 β–Ό
            51 features (17 keypoints Γ— 3) + bbox     ← upstream pandas DataFrame
                 β”‚
                 β”‚   scripts/run.py:
                 β”‚   build_feature_frame(upstream_df)
                 β”œβ”€β”€ aspect_ratio
                 β”œβ”€β”€ nose_relative_y
                 β”œβ”€β”€ torso_angle
                 β”œβ”€β”€ norm_com_y
                 └── head_hip_v_dist
                 β”‚
                 β–Ό
          56-column pandas DataFrame  ──►  XGBoost.predict_proba(df)  ──►  Fall / Normal

Upstream (out of scope) produces the 51 keypoint features + bounding box as a DataFrame. scripts/run.py computes the 5 engineered features, builds the 56-column DataFrame, and passes that DataFrame to XGBoost β€” that stage is in scope for this repository. The model never receives a CSV at inference.

Output contract

For each row of the 56-column DataFrame the XGBoost classifier returns a binary decision:

56-column DataFrame  β†’  XGBoost  β†’  Fall / Normal
  • model.predict(X) β†’ class id: 0 = Normal, 1 = Fall
  • model.predict_proba(X) β†’ [P(Normal), P(Fall)]
  • Decision rule: P(Fall) >= FALL_PROB_THRESH (0.70) β†’ Fall, otherwise Normal

Example output for one person:

{
  "label_id": 1,
  "prediction": "Fall",
  "fall_probability": 0.97,
  "normal_probability": 0.03,
  "threshold": 0.70
}

One input vector yields exactly one Fall / Normal label. Class mapping is defined in config.json (0: Normal, 1: Fall). fall_probability can be threshold-tuned (see Configuration and thresholds).

Configuration and thresholds

Classifier configuration lives in config.json:

CONF_THRESH = 0.50         # Input-side: keypoint confidence below this -> NaN coordinates
FALL_PROB_THRESH = 0.70    # Decision threshold on P(Fall) (optimized via grid search)

Optimized performance at these thresholds:

  • Accuracy: 92.67%
  • F1-Score: 90.91%
  • Precision: 90.11%
  • Recall: 91.73%

Tunable threshold guidance β€” adjust FALL_PROB_THRESH based on deployment priorities:

  • High precision (fewer false alarms): 0.95–0.98 (up to 98.05% precision)
  • High recall (catch more falls): 0.80–0.90
  • Balanced (default): 0.70

CONF_THRESH controls how aggressively occluded keypoints become NaN. Higher values tolerate less occlusion; lower values keep noisier coordinates.

Model architecture and training

Algorithm: XGBoost gradient-boosted decision trees, trained from scratch on the 56-feature dataset.

Hyperparameter Value
n_estimators 150
max_depth 5
learning_rate 0.03
scale_pos_weight 1.0 (train split pre-balanced 1:1)
missing np.nan (native occlusion handling)
eval_metric logloss
random_state 42

Training procedure (scripts/train.py):

  1. Load pose_benchmark_priority1_dataset.csv (56 features + split + label)
  2. Undersample the train split to an exact 1:1 Normal:Fall ratio (random_state=42); test split is left intact
  3. Train XGBoost on raw features with native NaN support (no imputer/scaler)
  4. Benchmark against Random Forest, SVM (RBF) and MLP; export all artifacts to models/

Why XGBoost: it outperformed Random Forest, SVM and MLP on both standard and real-world test sets, and its native missing=np.nan handling removes the need for an imputation pipeline when keypoints are occluded.

Runtime requirements

  • Python: 3.10+
  • Key dependencies (classifier only):
    • XGBoost β‰₯ 2.0.0 (native NaN handling for occluded keypoints)
    • NumPy β‰₯ 1.26.0
    • joblib β‰₯ 1.3.0 (model serialization)
    • pandas β‰₯ 2.0.0, scikit-learn β‰₯ 1.3.0 (training and evaluation only)

Install dependencies:

pip install -r requirements.txt

The classifier is lightweight and runs on CPU; no GPU is required for training or inference.

Performance

Training performance (test set, after 1:1 balancing)

Dataset: 6,607 samples (3,207 Normal, 3,400 Fall) Model: XGBoost with 56 engineered features

              precision    recall  f1-score   support

  Normal (0)       0.90      0.91      0.91      3207
    Fall (1)       0.92      0.91      0.91      3400

    accuracy                           0.91      6607
   macro avg       0.91      0.91      0.91      6607
weighted avg       0.91      0.91      0.91      6607

Overall accuracy: 91.00%

Threshold grid search (real-world CCTV evaluation)

Grid search over the input keypoint confidence and the decision threshold:

  • Keypoint confidence threshold: 0.25, 0.50
  • Fall probability threshold: 0.20 – 0.95 (9 steps)

Top parameter combinations (by F1-score):

Keypoint_Conf Fall_Prob_Thresh Accuracy F1_Score Precision Recall TN FP FN TP
0.50 0.70 92.67% 90.91% 90.11% 91.73% 390 28 23 255
0.50 0.80 92.67% 90.68% 92.19% 89.21% 397 21 30 248
0.50 0.60 92.10% 90.30% 88.58% 92.09% 385 33 22 256
0.50 0.90 92.53% 90.04% 96.31% 84.53% 409 9 43 235
0.50 0.50 91.24% 89.43% 86.29% 92.81% 377 41 20 258
0.50 0.40 90.80% 89.19% 84.08% 94.96% 368 50 14 264
0.50 0.30 89.51% 88.09% 80.60% 97.12% 353 65 8 270
0.50 0.20 88.07% 86.80% 77.78% 98.20% 340 78 5 273
0.50 0.95 88.36% 83.23% 98.05% 72.30% 414 4 77 201

Confusion matrix (selected configuration, threshold 0.70):

                Predicted
              Normal  Fall
Actual Normal   390    28     (TN, FP)
       Fall      23   255    (FN, TP)

Key insights:

  • A keypoint confidence of 0.50 gives the best balance between occlusion tolerance and noisy coordinates
  • A fall probability threshold of 0.70 achieves the best F1-score; stricter 0.95 maximizes precision (98.05%) but misses ~28% of falls
  • This configuration (CONF_THRESH=0.50, FALL_PROB_THRESH=0.70) is the deployment default

Intended use

This classifier is intended for feature-vector-based fall detection in:

  • Live concert environment
  • On-the-road and touring conditions
  • IN indoor condition

This is a safety assistance tool, not a replacement for human supervision.

Limitations

  • Degrades on heavily degraded inputs: vectors where most keypoints are NaN (severe occlusion, >70% of body blocked) or extreme camera angles (>60Β° from vertical) fall outside the training distribution
  • No temporal smoothing: single-vector classification without motion history. May produce momentary false positives during normal sitting/bending motions
  • No identity tracking: each vector is classified independently; deployments need their own deduplication for repeated alerts on the same person
  • Threshold-sensitive: precision/recall trade-offs depend on FALL_PROB_THRESH and must be tuned per environment
  • Training-distribution bound: trained on a specific dataset; performance on new camera placements, demographics, or fall types should be validated before experimental use
  • Oclusion: if a fallen person is ocluded by a perosn or a object it might don't detect the person as fallen

Dataset location

Training and evaluation datasets are stored at:

/home/ctspl/model_training/fall/version3/data/

Ownership

  • Model trainer/developer: Nishant Prasad
  • Dataset source: Internal CCTV footage collection
  • Model architecture: XGBoost (XGBClassifier, 56 input features)
  • Training date: 2026-09-21
  • Status: experimental

Repository layout

fall-detection/
β”œβ”€β”€ README.md
β”œβ”€β”€ MODEL_CARD.md
β”œβ”€β”€ CHANGELOG.md
β”œβ”€β”€ config.json                          # Feature schema, thresholds and paths
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ models/
β”‚   └── xgboost_priority1_fall_model.pkl # Trained XGBoost classifier
β”œβ”€β”€ scripts/
β”‚   β”œβ”€β”€ train.py                         # Training + benchmark pipeline
β”‚   β”œβ”€β”€ evaluate.ipynb                   # Evaluation notebook (DataFrame path)
β”‚   └── run.py                           # Inference: upstream DF (51+bbox) β†’ +5 β†’ 56-col DF β†’ XGBoost
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ spec.md                          # Development specification
β”‚   β”œβ”€β”€ data.md                          # Dataset schema
β”‚   └── train.log.md                     # Training provenance
└── data/                                # Datasets (not committed)
Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support