|
1 | | -# 🚗💤 Drowsiness Detection with OpenCV |
| 1 | +# 🚗💤 Drowsiness Detection |
2 | 2 |
|
3 | | -> **Real-time driver drowsiness detection** using computer vision and the Eye Aspect Ratio (EAR) algorithm. |
| 3 | +[](https://github.com/Sanjays2402/Drowsiness-Detection-with-OpenCV/actions/workflows/tests.yml) |
| 4 | +[](https://ieeexplore.ieee.org/document/9532758) |
| 5 | + |
| 6 | + |
| 7 | + |
| 8 | + |
4 | 9 |
|
5 | | -<p align="center"> |
6 | | - <a href="https://ieeexplore.ieee.org/document/9532758"><img src="https://img.shields.io/badge/IEEE-Published%20Paper-00629B?style=for-the-badge&logo=ieee&logoColor=white" alt="IEEE Paper"></a> |
7 | | -</p> |
| 10 | +Real-time driver drowsiness detection using the **Eye Aspect Ratio (EAR)**. Originally an IEEE conference paper; this repo packages it as a production-shaped Python library with a CLI, two interchangeable landmark backends, a stateful detector with event hooks, and a headless batch-analysis mode. |
8 | 11 |
|
9 | | -<p align="center"> |
10 | | - <img src="https://img.shields.io/badge/Python-3.7+-3776AB?style=flat-square&logo=python&logoColor=white"> |
11 | | - <img src="https://img.shields.io/badge/OpenCV-4.x-5C3EE8?style=flat-square&logo=opencv&logoColor=white"> |
12 | | - <img src="https://img.shields.io/badge/dlib-face__recognition-green?style=flat-square"> |
13 | | - <img src="https://img.shields.io/badge/SciPy-EAR%20Computation-8CAAE6?style=flat-square&logo=scipy&logoColor=white"> |
14 | | - <img src="https://img.shields.io/badge/License-MIT-yellow?style=flat-square"> |
15 | | -</p> |
| 12 | +> 📄 S. Santhanam et al., *"Real-Time Drowsiness Detection using Computer Vision"*, **2021 5th ICCMC (IEEE)**. [Read the paper →](https://ieeexplore.ieee.org/document/9532758) |
16 | 13 |
|
17 | 14 | --- |
18 | 15 |
|
19 | | -## 📄 Publication |
| 16 | +## What's new vs. the original script |
20 | 17 |
|
21 | | -This project is published in **IEEE Xplore**: |
| 18 | +| | Old script | This package | |
| 19 | +|---|---|---| |
| 20 | +| Architecture | Single file, globals, blocking alarm | Stateful `DrowsinessDetector` with FSM (`AWAKE`/`DROWSY`), event callbacks | |
| 21 | +| Alarm | Blocking — stalled the detection loop on every trigger | Background thread + cooldown (`Alarm`); never blocks frames | |
| 22 | +| Alarm spam | Fired every frame while eyes closed | Fires once per AWAKE→DROWSY transition | |
| 23 | +| Landmarks | dlib + `face_recognition` (CMake hell) | **MediaPipe FaceMesh by default** (pure-pip, faster, GPU-friendly) — `face_recognition` still available | |
| 24 | +| API | None | `DrowsinessDetector`, `DrowsinessConfig`, `DrowsyEvent`, `DetectionResult` | |
| 25 | +| CLI | Single mode | `drowsy run` (live) and `drowsy analyze` (headless batch → CSV/JSONL/JSON) | |
| 26 | +| Tests | None | 14 pytest cases, FSM and alarm fully covered, runs in CI on Python 3.9/3.11/3.12 | |
| 27 | +| Robustness | Crashed on missing alarm file; reset on lost detection | Soft-fails on missing audio; preserves state across brief detection dropouts | |
| 28 | +| Visualization | EAR text only | EAR vs threshold, FPS counter, color-coded eye polylines, drowsy banner | |
22 | 29 |
|
23 | | -> **S. Santhanam et al.**, "Real-Time Drowsiness Detection using Computer Vision," *2021 5th International Conference on Computing Methodologies and Communication (ICCMC)*, 2021. |
24 | | -> |
25 | | -> 🔗 **[Read the paper on IEEE Xplore →](https://ieeexplore.ieee.org/document/9532758)** |
| 30 | +--- |
| 31 | + |
| 32 | +## Install |
| 33 | + |
| 34 | +```bash |
| 35 | +# Default install: package + OpenCV + NumPy |
| 36 | +pip install -e . |
| 37 | + |
| 38 | +# Recommended: add MediaPipe (no native build, very fast) |
| 39 | +pip install -e ".[mediapipe,audio]" |
| 40 | + |
| 41 | +# Or, original dlib-based backend (requires CMake) |
| 42 | +pip install -e ".[legacy,audio]" |
| 43 | +``` |
26 | 44 |
|
27 | 45 | --- |
28 | 46 |
|
29 | | -## 🧠 How It Works |
| 47 | +## Quick start |
| 48 | + |
| 49 | +### CLI |
| 50 | + |
| 51 | +```bash |
| 52 | +# Live webcam detection with overlay |
| 53 | +drowsy run 0 |
| 54 | + |
| 55 | +# Headless batch analysis of a video — emits CSV/JSONL/JSON report |
| 56 | +drowsy analyze drive.mp4 --output-dir report/ |
30 | 57 |
|
31 | | -The system uses the **Eye Aspect Ratio (EAR)** to detect drowsiness in real-time: |
| 58 | +# Tune sensitivity |
| 59 | +drowsy run 0 --ear-threshold 0.22 --frame-count 24 |
32 | 60 |
|
| 61 | +# Use the legacy dlib backend |
| 62 | +drowsy run 0 --backend face_recognition |
33 | 63 | ``` |
34 | | -EAR = (||p2 - p6|| + ||p3 - p5||) / (2 × ||p1 - p4||) |
| 64 | + |
| 65 | +Press **`q`** or **Esc** to quit live mode. |
| 66 | + |
| 67 | +### Python API |
| 68 | + |
| 69 | +```python |
| 70 | +import cv2 |
| 71 | +from drowsiness import DrowsinessDetector, DrowsinessConfig |
| 72 | + |
| 73 | +cfg = DrowsinessConfig(ear_threshold=0.23, closed_frames_to_alarm=24) |
| 74 | + |
| 75 | +with DrowsinessDetector(cfg, on_event=lambda e: print("DROWSY!", e)) as detector: |
| 76 | + cap = cv2.VideoCapture(0) |
| 77 | + while True: |
| 78 | + ok, frame = cap.read() |
| 79 | + if not ok: |
| 80 | + break |
| 81 | + result = detector.process(frame) |
| 82 | + # result.state: EyeState.AWAKE | EyeState.DROWSY |
| 83 | + # result.ear: current Eye Aspect Ratio |
| 84 | + # result.event: DrowsyEvent or None (fires once per drowsy episode) |
35 | 85 | ``` |
36 | 86 |
|
| 87 | +--- |
| 88 | + |
| 89 | +## How it works |
| 90 | + |
| 91 | +### Eye Aspect Ratio |
| 92 | + |
37 | 93 | ``` |
| 94 | +EAR = (||p2 - p6|| + ||p3 - p5||) / (2 × ||p1 - p4||) |
| 95 | +
|
38 | 96 | p2 p3 |
39 | 97 | •--------• |
40 | 98 | / \ |
41 | | -p1 • • p4 ← horizontal axis |
| 99 | +p1 • • p4 |
42 | 100 | \ / |
43 | 101 | •--------• |
44 | 102 | p6 p5 |
45 | 103 | ``` |
46 | 104 |
|
47 | | -- **Eyes open** → EAR ≈ 0.3 |
| 105 | +- **Eyes open** → EAR ≈ 0.30 |
48 | 106 | - **Eyes closed** → EAR ≈ 0.05 |
49 | | -- If EAR stays below a threshold for enough consecutive frames → **🚨 ALARM** |
| 107 | +- EAR below threshold for *N* consecutive frames → **drowsy event** |
50 | 108 |
|
51 | | -### Architecture |
| 109 | +### Detection FSM |
52 | 110 |
|
53 | 111 | ``` |
54 | | -┌──────────┐ ┌──────────────┐ ┌───────────┐ ┌───────────┐ |
55 | | -│ Webcam │───▶│ Face Landmark │───▶│ Compute │───▶│ Trigger │ |
56 | | -│ Stream │ │ Detection │ │ EAR │ │ Alarm? │ |
57 | | -└──────────┘ └──────────────┘ └───────────┘ └───────────┘ |
58 | | - face_recognition scipy playsound |
| 112 | + EAR < threshold (N frames) |
| 113 | + ┌──────────┐ ─────────────────────────▶ ┌──────────┐ |
| 114 | + │ AWAKE │ │ DROWSY │ |
| 115 | + └──────────┘ ◀───────────────────────── └──────────┘ |
| 116 | + EAR ≥ threshold (M frames) |
59 | 117 | ``` |
60 | 118 |
|
61 | | ---- |
62 | | - |
63 | | -## ✨ Features |
64 | | - |
65 | | -- 🎥 Real-time webcam-based detection |
66 | | -- 👁️ Eye Aspect Ratio (EAR) algorithm |
67 | | -- 🔊 Audio alarm on drowsiness detection |
68 | | -- 📊 Live EAR display overlay |
69 | | -- ⚙️ Configurable thresholds via CLI args |
70 | | - |
71 | | ---- |
72 | | - |
73 | | -## 🚀 Getting Started |
| 119 | +The state machine de-bounces both directions: a brief blink doesn't trigger an alarm, and a single open frame doesn't immediately clear a drowsy state. |
74 | 120 |
|
75 | | -### Prerequisites |
| 121 | +### Pipeline |
76 | 122 |
|
77 | | -- Python 3.7+ |
78 | | -- Webcam |
79 | | -- CMake (for dlib): `brew install cmake` (macOS) or `apt install cmake` (Linux) |
80 | | - |
81 | | -### Installation |
82 | | - |
83 | | -```bash |
84 | | -git clone https://github.com/Sanjays2402/Drowsiness-Detection-with-OpenCV.git |
85 | | -cd Drowsiness-Detection-with-OpenCV |
86 | | -pip install -r requirements.txt |
| 123 | +``` |
| 124 | +Webcam / Video |
| 125 | + │ |
| 126 | + ▼ |
| 127 | + Landmark backend ──▶ 6 EAR points per eye |
| 128 | + (MediaPipe FaceMesh |
| 129 | + or face_recognition) |
| 130 | + │ |
| 131 | + ▼ |
| 132 | + EAR computation ──▶ average EAR |
| 133 | + │ |
| 134 | + ▼ |
| 135 | + Drowsy FSM ──▶ state, closed_frames, event? |
| 136 | + │ |
| 137 | + ├─ Alarm (background, debounced) |
| 138 | + ├─ on_event callback (your code) |
| 139 | + └─ Visualization overlay |
87 | 140 | ``` |
88 | 141 |
|
89 | | -### Usage |
90 | | - |
91 | | -```bash |
92 | | -# Default settings |
93 | | -python drowsiness_detection.py |
94 | | - |
95 | | -# Custom thresholds |
96 | | -python drowsiness_detection.py --ear-threshold 0.22 --frame-count 48 |
| 142 | +--- |
97 | 143 |
|
98 | | -# Different video source |
99 | | -python drowsiness_detection.py --video-source 1 |
| 144 | +## Configuration |
| 145 | + |
| 146 | +```python |
| 147 | +DrowsinessConfig( |
| 148 | + ear_threshold=0.25, # below = closed eye |
| 149 | + closed_frames_to_alarm=20, # frames below threshold → DROWSY |
| 150 | + open_frames_to_clear=5, # frames above threshold → AWAKE |
| 151 | + alarm_sound="assets/alert1.mp3", |
| 152 | + alarm_cooldown_s=3.0, # min seconds between alarm playbacks |
| 153 | + backend="mediapipe", # or "face_recognition" |
| 154 | + enable_alarm=True, |
| 155 | +) |
100 | 156 | ``` |
101 | 157 |
|
102 | | -Press **`q`** to quit. |
| 158 | +| CLI flag | Default | Notes | |
| 159 | +|---|---|---| |
| 160 | +| `--ear-threshold` | `0.25` | Tune lower for darker/glasses-heavy footage. | |
| 161 | +| `--frame-count` | `20` | At 30 FPS this is ~0.7s of closed eyes. | |
| 162 | +| `--open-frames` | `5` | Clears the drowsy state after a sustained reopen. | |
| 163 | +| `--alarm-cooldown` | `3.0` | Seconds. Prevents back-to-back alarms. | |
| 164 | +| `--backend` | `mediapipe` | `mediapipe` or `face_recognition`. | |
| 165 | +| `--no-alarm` | off | Disables audio (good for benchmarking). | |
| 166 | +| `--no-window` | off | Headless `run` (no `cv2.imshow`). | |
103 | 167 |
|
104 | 168 | --- |
105 | 169 |
|
106 | | -## ⚙️ Configuration |
| 170 | +## Batch analysis |
107 | 171 |
|
108 | | -| Parameter | Flag | Default | Description | |
109 | | -|-----------|------|---------|-------------| |
110 | | -| EAR Threshold | `--ear-threshold` | `0.25` | EAR below this = eyes closed | |
111 | | -| Frame Count | `--frame-count` | `60` | Consecutive frames before alarm | |
112 | | -| Video Source | `--video-source` | `0` | Webcam index | |
113 | | -| Alarm Sound | `--alarm-sound` | `assets/alert1.mp3` | Path to alert audio | |
| 172 | +`drowsy analyze` runs a video through the same detector with audio disabled and produces a small report: |
114 | 173 |
|
115 | | ---- |
116 | | - |
117 | | -## 🎬 Demo |
| 174 | +``` |
| 175 | +report/ |
| 176 | +├── ear.csv # frame-by-frame EAR & state |
| 177 | +├── events.jsonl # one record per AWAKE→DROWSY transition |
| 178 | +└── summary.json # counts, parameters, timestamps |
| 179 | +``` |
118 | 180 |
|
119 | | -> *Add a GIF or screenshot here!* |
120 | | -> |
121 | | ->  |
| 181 | +Useful for fleet review, evaluating threshold changes, or feeding downstream analytics without a GUI. |
122 | 182 |
|
123 | 183 | --- |
124 | 184 |
|
125 | | -## 📁 Project Structure |
| 185 | +## Project structure |
126 | 186 |
|
127 | 187 | ``` |
128 | | -├── drowsiness_detection.py # Main detection script |
129 | | -├── assets/ |
130 | | -│ └── alert1.mp3 # Alarm sound |
131 | | -├── requirements.txt |
132 | | -├── LICENSE |
133 | | -└── README.md |
| 188 | +drowsiness/ |
| 189 | +├── __init__.py # public API |
| 190 | +├── ear.py # eye_aspect_ratio() + helpers |
| 191 | +├── landmarks.py # MediaPipe + face_recognition backends |
| 192 | +├── alarm.py # background, cooldown-debounced audio |
| 193 | +├── detector.py # DrowsinessDetector + FSM + events |
| 194 | +├── visualization.py # cv2 overlay (EAR, FPS, banner) |
| 195 | +└── cli.py # `drowsy` entrypoint |
| 196 | +tests/ |
| 197 | +└── test_detector.py # 14 cases, mocked landmark backend |
| 198 | +assets/ |
| 199 | +└── alert1.mp3 |
134 | 200 | ``` |
135 | 201 |
|
136 | 202 | --- |
137 | 203 |
|
138 | | -## 🛠️ Tech Stack |
| 204 | +## Limitations |
| 205 | + |
| 206 | +This is still an **EAR-based** system. Known weaknesses: |
139 | 207 |
|
140 | | -- **[OpenCV](https://opencv.org/)** — Video capture & frame processing |
141 | | -- **[face_recognition](https://github.com/ageitgey/face_recognition)** — Facial landmark detection (dlib-based) |
142 | | -- **[SciPy](https://scipy.org/)** — Euclidean distance for EAR |
143 | | -- **[imutils](https://github.com/PyImageSearch/imutils)** — Video stream utilities |
144 | | -- **[playsound](https://github.com/TaylorSMarks/playsound)** — Audio alarm playback |
| 208 | +- Sunglasses / heavy bangs / extreme head pose break landmark detection. |
| 209 | +- A single threshold doesn't fit every driver — production deployments should calibrate per-driver during the first ~30s of awake driving. |
| 210 | +- No yawn / head-nod / gaze-direction signal yet (PRs welcome — `DrowsyEvent` is the right place to fan out into multi-cue fusion). |
| 211 | +- No tracking across multiple faces; the first detected face is used. Cabin-facing cameras typically only see one driver. |
145 | 212 |
|
146 | 213 | --- |
147 | 214 |
|
148 | | -## 📜 License |
| 215 | +## Citation |
149 | 216 |
|
150 | | -[MIT](LICENSE) © Sanjay Santhanam |
| 217 | +```bibtex |
| 218 | +@inproceedings{santhanam2021drowsiness, |
| 219 | + title = {Real-Time Drowsiness Detection using Computer Vision}, |
| 220 | + author = {Santhanam, S. and others}, |
| 221 | + booktitle = {2021 5th International Conference on Computing Methodologies and Communication (ICCMC)}, |
| 222 | + year = {2021}, |
| 223 | + doi = {10.1109/ICCMC51019.2021.9418325} |
| 224 | +} |
| 225 | +``` |
151 | 226 |
|
152 | | -## 👤 Author |
| 227 | +## License |
153 | 228 |
|
154 | | -**Sanjay Santhanam** — [GitHub](https://github.com/Sanjays2402) |
| 229 | +[MIT](LICENSE) © Sanjay Santhanam |
0 commit comments