A real-time driver drowsiness detection system with a browser-based dashboard. OpenCV watches a webcam feed for closed eyes; when the driver's eyes stay shut past a threshold, the dashboard sounds an alarm and sends an email alert with an IP-based location link - all shown live on a night-instrument-cluster style web UI.
This is a rebuild of the original desktop (cv2.imshow) version as a
Flask web app, with security hardening and an attractive dashboard front
end. See Security notes below for the full list of
hardening changes and their limits.
- 👁️ Real-time face/eye tracking (OpenCV Haar cascades), streamed to the browser as a live MJPEG feed
- 🔔 Looping audio alarm + rate-limited email alert with an IP-based Google Maps link, once eyes are closed past the threshold
- 📊 Dashboard UI: live "alertness gauge", status telltales (face / eyes / alarm), and a scrolling event log
- 🔒 Security headers (CSP, X-Frame-Options, Permissions-Policy, etc.), no
debug tracebacks by default, and no state-changing endpoints at all -
the alarm, email alerts, and developer-tools lock are all set via
.envonly, with zero UI footprint - 🖥️ Optional, honestly-labeled developer-tools lock, set by the operator
via
.env(deterrent, not a real security boundary - see below)
| Layer | Choice | Why |
|---|---|---|
| Backend | Flask | Lightweight, no build step, well suited to serving an MJPEG stream from an OpenCV loop |
| Computer vision | OpenCV (Haar cascades) | Same detection approach as the original, kept on the last 4.x release - see the requirements.txt note on why |
| Frontend | Plain HTML/CSS/JS | No bundler needed for a single-page local dashboard; keeps the whole client auditable in two small files |
| Audio alerts | Pygame | Unchanged from the original |
| Email alerts | smtplib (Gmail SMTP) | Unchanged from the original, now cooldown-limited |
| Geolocation | geocoder (IP-based) | Unchanged from the original |
- A background thread captures webcam frames and runs Haar-cascade face and eye detection on each one.
- The dashboard polls
/api/statusevery second and streams the annotated video from/video_feed. - If no eyes are detected continuously past the threshold, the alarm sounds and (if configured and outside the cooldown window) an email alert goes out with an approximate location link.
- Everything resets once eyes are detected again.
- Python 3.12–3.14 (tested on 3.14.6; opencv-python ships
abi3wheels, so it installs cleanly on 3.14 without waiting for a Python-version-specific build) - A working webcam
- A Gmail account with an App Password if you want email alerts (do not use your real password)
git clone https://github.com/mageshit24/Driver-Drowsiness-Alert.git
cd Driver-Drowsiness-Alert
# Create and activate a virtual environment
python -m venv venv
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# or, for a byte-for-byte reproducible install:
pip install -r requirements-lock.txt# .env
HOST=127.0.0.1
PORT=5000
FLASK_DEBUG=false
SECRET_KEY=secret_key_generated
CAMERA_INDEX=0
CLOSE_THRESHOLD_SECONDS=5
JPEG_QUALITY=80
ALARM_ENABLED=true
EMAIL_ALERTS_ENABLED=true
DEVTOOLS_LOCK_ENABLED=false
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
EMAIL_SENDER=sender_email
EMAIL_PASSWORD=sender_app_password
EMAIL_RECEIVER=receiver_email
ALERT_EMAIL_COOLDOWN_SECONDS=120Fill in .env with your values. At minimum, set a SECRET_KEY:
python -c "import secrets; print(secrets.token_hex(32))"Email alerts are optional - if EMAIL_SENDER / EMAIL_PASSWORD /
EMAIL_RECEIVER are left blank, the dashboard just shows email alerts as
"not configured" and the alarm/detection still work normally.
The alarm sound, email alerts, and developer-tools lock are all
operator-set booleans in .env - ALARM_ENABLED, EMAIL_ALERTS_ENABLED,
and DEVTOOLS_LOCK_ENABLED. None of these appears in the dashboard UI at
all, not even as a read-only indicator - they're purely server
configuration. To change one, edit .env and restart the app (Ctrl+C,
then python app.py again - config is only read at startup). See
Security notes for what the devtools lock does and
doesn't protect against before turning it on.
python app.pyOpen http://127.0.0.1:5000 in your browser. By default the server only
binds to localhost - see Security notes before setting
HOST=0.0.0.0 to expose it on your network.
This documents what changed from the original desktop script and, just as importantly, what the changes do and don't protect against. This app is built for local, single-user use (you, monitoring your own drive) - it is not designed for multi-tenant or public deployment.
Credential handling
- No credential ever had a hardcoded fallback (this was already true in
the original
app.py, and is preserved here).EMAIL_SENDER,EMAIL_PASSWORD,EMAIL_RECEIVER,SECRET_KEYall come from.env, which is git-ignored. - If email isn't configured, the feature disables itself with a status message instead of crashing or logging a scary warning at import time.
Code / information exposure
DEBUGdefaults to off. The Werkzeug debugger (which lets anyone who can reach an error page execute arbitrary Python) is never enabled unless you explicitly setFLASK_DEBUG=true.- Custom 404/500 handlers return a minimal JSON error instead of a stack trace, so even a misconfiguration can't leak file paths or internals.
requirements.txtwas UTF-16-encoded in the original repo (an encoding artifact, not a security issue by itself, but it silently breakspip install -r requirements.txton some setups) — rewritten as plain UTF-8 with real, current, verified pins. See the note at the top of that file about the OpenCV 5.xCascadeClassifierremoval.requirements.txtusespygame-ceinstead ofpygame. Plainpygamehas no prebuilt wheel for Python 3.14 yet, so installing it on 3.14 tries to compile from source and fails (missingdistutils.msvccompiler, which modern Python removed).pygame-ceis the actively maintained, API-compatible community fork -import pygamestill works unchanged - and it ships a real Python 3.14 wheel.- The app binds to
127.0.0.1by default. SettingHOST=0.0.0.0to reach it from other devices is an explicit, opt-in choice - read that as "now anyone on this network can watch the camera feed and flip your alarm/email toggles" before doing it.
Response hardening - every response carries:
X-Content-Type-Options: nosniffX-Frame-Options: DENY(can't be framed/clickjacked)Referrer-Policy: no-referrerPermissions-Policy: camera=(), microphone=(), geolocation=()- the browser never needs any of these (video comes from the server-side OpenCV loop, notgetUserMedia; location comes from server-side IP lookup, not the browser's Geolocation API), so they're explicitly denied- A
Content-Security-Policyrestricting scripts/styles/connections to the app's own origin plus Google Fonts
Request hardening
- Every route in this app is read-only (
/,/video_feed,/api/status). The alarm, email alerts, and developer-tools lock are all read from.envat startup - there is no endpoint that accepts a POST and changes server state, so there's no CSRF surface to defend in the first place. This is a deliberate simplification over an earlier version that had a toggle-driven/api/settingsendpoint and a dashboard panel showing these settings; both are gone now - the settings are server configuration, not part of the app's surface. - Session cookies are
HttpOnlyandSameSite=Lax.
Alert abuse prevention
send_alert()is cooldown-limited (ALERT_EMAIL_COOLDOWN_SECONDS, default 120s). Without this, a flickering camera or false detection loop could spam the receiver's inbox indefinitely.- SMTP failures are caught and summarized as
failed: <ExceptionType>in the server log only - never returned to the browser, never including the raw exception text (which could echo back connection details).
Dependency hygiene
All pinned versions in requirements.txt / requirements-lock.txt were
checked with pip-audit against
the PyPI advisory database - no known vulnerabilities at time of writing.
Re-run pip-audit -r requirements-lock.txt periodically; pins go stale.
The dashboard can lock developer tools on its own tab, controlled by
DEVTOOLS_LOCK_ENABLED in .env (default false). It is not a
dashboard toggle on purpose - a setting anyone using the UI could flip
wouldn't be worth much, so it's server-configured only. Being honest
about what it does:
- When on, it blocks
F12,Ctrl+Shift+I/J/C,Ctrl+U, and right-click, and shows a warning overlay if it detects the browser window resizing in a way consistent with docked dev tools. - This is a client-side JavaScript deterrent, not a security boundary. Anyone can disable JavaScript, use their browser's menu instead of a shortcut, undock dev tools before the resize check triggers, or open dev tools on a different tab and navigate over. There is no way to actually prevent a user from inspecting a page their own browser renders - no website can.
- It exists because it's a common ask for internal/demo tools where the goal is discouraging casual poking, not stopping a determined person. Don't rely on it to hide anything sensitive.
The real reason this app is safe to open dev tools on: there's nothing sensitive in the browser to find. Camera frames are processed server-side; only the rendered JPEG stream and a small non-secret status JSON (booleans, durations) ever reach the client. Credentials, SMTP details, and the geolocation lookup all stay server-side. That's the actual "code exposure prevention" here - the devtools lock is a UX nicety on top of it, not the mechanism.
- No authentication. Anyone who can reach the bound host/port sees the
live camera feed. Keep
HOSTat127.0.0.1unless you add auth in front of it (e.g. a reverse proxy with basic auth) first. - No HTTPS/TLS out of the box - fine for
127.0.0.1, not fine if you expose this beyond your own machine. - No rate limiting on
/video_feedor/api/statusbeyond what a single-user local tool needs.
- Yawn / mouth detection for additional fatigue signals
- SMS alerts via Twilio
- Deep learning-based eye state classification (CNN) for higher accuracy
- Mobile companion app
Magesh Hariram K
This project is open source. Consider adding an MIT License file if you plan to accept contributions.