first HF push
Browse files- .gitignore +4 -0
- README.md +14 -1
- ads.py +31 -0
- app.py +87 -0
- audio/wake_up.wav +0 -0
- audio_handling.py +93 -0
- drowsy_detection.py +187 -0
- requirements.txt +10 -0
.gitignore
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/.vscode
|
2 |
+
__pycache__
|
3 |
+
drowsiness-detection-course
|
4 |
+
main_2.py
|
README.md
CHANGED
@@ -10,4 +10,17 @@ pinned: false
|
|
10 |
license: afl-3.0
|
11 |
---
|
12 |
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
license: afl-3.0
|
11 |
---
|
12 |
|
13 |
+
# Drowsiness-Detection-Using-Mediapipe-Streamlit
|
14 |
+
|
15 |
+
A drowsiness detection application created using mediapipe, streamlit and streamlit-webrtc
|
16 |
+
|
17 |
+
This repository contains the app deployment code on streamlit cloud. <br>
|
18 |
+
To understand more refer to my blogpost: [Driver Drowsiness Detection Using Mediapipe In Python | LearnOpenCV](https://learnopencv.com/driver-drowsiness-detection-using-mediapipe-in-python/)
|
19 |
+
|
20 |
+
|
21 |
+
|
22 |
+
Libraries used:
|
23 |
+
|
24 |
+
1. Mediapipe face mesh: https://google.github.io/mediapipe/solutions/face_mesh.html
|
25 |
+
2. Streamlit: https://streamlit.io/
|
26 |
+
3. streamlit-webrtc: https://github.com/whitphx/streamlit-webrtc
|
ads.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
css_string = """
|
2 |
+
<style>
|
3 |
+
|
4 |
+
.sidebar {max-width:100%; float:right}
|
5 |
+
.side-block img {width:100%}
|
6 |
+
.side-block {margin-bottom:15px}
|
7 |
+
.side-block a.button {margin-bottom:30px; background: #006CFF; color: #fff;font-weight: 500;font-size: 16px;line-height: 20px;padding: 15px;display: inline-block;max-width: 300px;border-radius: 5px; text-decoration:none; }
|
8 |
+
.side-block a.button:hover {background:#000}
|
9 |
+
|
10 |
+
</style>
|
11 |
+
|
12 |
+
<div class="sidebar">
|
13 |
+
|
14 |
+
<div class="side-block">
|
15 |
+
<a target="_blank" href="https://opencv.org/courses" rel="noopener">
|
16 |
+
<img src="https://learnopencv.com/wp-content/uploads/2022/03/opencv-course1.png" alt="Opencv Courses">
|
17 |
+
</div>
|
18 |
+
<div class="side-block">
|
19 |
+
<a href="https://learnopencv.com" class="button ">Subscribe To My Newsletter</a>
|
20 |
+
<div class="side-block">
|
21 |
+
</div>
|
22 |
+
<a target="_blank" href="https://pallet.xyz/list/ai-jobs?" rel="noopener">
|
23 |
+
<img src="https://learnopencv.com/wp-content/uploads/2022/02/learnopencv-ai-jobs.jpg" alt="Opencv Courses">
|
24 |
+
</div>
|
25 |
+
<div class="side-block">
|
26 |
+
<a target="_blank" href="https://bigvision.ai" rel="noopener">
|
27 |
+
<img src="https://learnopencv.com/wp-content/uploads/2022/02/bigvision.jpg" alt="Opencv Courses"></a>
|
28 |
+
</div>
|
29 |
+
</div>
|
30 |
+
|
31 |
+
"""
|
app.py
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import av
|
3 |
+
import threading
|
4 |
+
import streamlit as st
|
5 |
+
import streamlit_nested_layout
|
6 |
+
from streamlit_webrtc import VideoHTMLAttributes, webrtc_streamer
|
7 |
+
|
8 |
+
from audio_handling import AudioFrameHandler
|
9 |
+
from drowsy_detection import VideoFrameHandler
|
10 |
+
from ads import css_string
|
11 |
+
|
12 |
+
|
13 |
+
# Define the audio file to use.
|
14 |
+
alarm_file_path = os.path.join("audio", "wake_up.wav")
|
15 |
+
|
16 |
+
# Streamlit Components
|
17 |
+
st.set_page_config(
|
18 |
+
page_title="Drowsiness Detection | LearnOpenCV",
|
19 |
+
page_icon="https://learnopencv.com/wp-content/uploads/2017/12/favicon.png",
|
20 |
+
layout="wide", # centered, wide
|
21 |
+
initial_sidebar_state="expanded",
|
22 |
+
menu_items={
|
23 |
+
"About": "### Visit www.learnopencv.com for more exciting tutorials!!!",
|
24 |
+
},
|
25 |
+
)
|
26 |
+
|
27 |
+
|
28 |
+
col1, col2 = st.columns(spec=[6, 2], gap="medium")
|
29 |
+
|
30 |
+
with col1:
|
31 |
+
st.title("Drowsiness Detection!!!🥱😪😴")
|
32 |
+
with st.container():
|
33 |
+
c1, c2 = st.columns(spec=[1, 1])
|
34 |
+
with c1:
|
35 |
+
# The amount of time (in seconds) to wait before sounding the alarm.
|
36 |
+
WAIT_TIME = st.slider("Seconds to wait before sounding alarm:", 0.0, 5.0, 1.0, 0.25)
|
37 |
+
|
38 |
+
with c2:
|
39 |
+
# Lowest valid value of Eye Aspect Ratio. Ideal values [0.15, 0.2].
|
40 |
+
EAR_THRESH = st.slider("Eye Aspect Ratio threshold:", 0.0, 0.4, 0.18, 0.01)
|
41 |
+
|
42 |
+
thresholds = {
|
43 |
+
"EAR_THRESH": EAR_THRESH,
|
44 |
+
"WAIT_TIME": WAIT_TIME,
|
45 |
+
}
|
46 |
+
|
47 |
+
# For streamlit-webrtc
|
48 |
+
video_handler = VideoFrameHandler()
|
49 |
+
audio_handler = AudioFrameHandler(sound_file_path=alarm_file_path)
|
50 |
+
|
51 |
+
lock = threading.Lock() # For thread-safe access & to prevent race-condition.
|
52 |
+
shared_state = {"play_alarm": False}
|
53 |
+
|
54 |
+
|
55 |
+
def video_frame_callback(frame: av.VideoFrame):
|
56 |
+
frame = frame.to_ndarray(format="bgr24") # Decode and convert frame to RGB
|
57 |
+
|
58 |
+
frame, play_alarm = video_handler.process(frame, thresholds) # Process frame
|
59 |
+
with lock:
|
60 |
+
shared_state["play_alarm"] = play_alarm # Update shared state
|
61 |
+
|
62 |
+
return av.VideoFrame.from_ndarray(frame, format="bgr24") # Encode and return BGR frame
|
63 |
+
|
64 |
+
|
65 |
+
def audio_frame_callback(frame: av.AudioFrame):
|
66 |
+
with lock: # access the current “play_alarm” state
|
67 |
+
play_alarm = shared_state["play_alarm"]
|
68 |
+
|
69 |
+
new_frame: av.AudioFrame = audio_handler.process(frame, play_sound=play_alarm)
|
70 |
+
return new_frame
|
71 |
+
|
72 |
+
|
73 |
+
# https://github.com/whitphx/streamlit-webrtc/blob/main/streamlit_webrtc/config.py
|
74 |
+
|
75 |
+
with col1:
|
76 |
+
ctx = webrtc_streamer(
|
77 |
+
key="drowsiness-detection",
|
78 |
+
video_frame_callback=video_frame_callback,
|
79 |
+
audio_frame_callback=audio_frame_callback,
|
80 |
+
rtc_configuration={"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]}, # Add this to config for cloud deployment.
|
81 |
+
media_stream_constraints={"video": {"height": {"ideal": 480}}, "audio": True},
|
82 |
+
video_html_attrs=VideoHTMLAttributes(autoPlay=True, controls=False, muted=False),
|
83 |
+
)
|
84 |
+
|
85 |
+
with col2:
|
86 |
+
# Banner for newsletter subscription, jobs, and consulting.
|
87 |
+
st.markdown(css_string, unsafe_allow_html=True)
|
audio/wake_up.wav
ADDED
Binary file (28.9 kB). View file
|
|
audio_handling.py
ADDED
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import av
|
2 |
+
import numpy as np
|
3 |
+
from pydub import AudioSegment
|
4 |
+
|
5 |
+
|
6 |
+
class AudioFrameHandler:
|
7 |
+
"""To play/pass custom audio based on some event"""
|
8 |
+
|
9 |
+
def __init__(self, sound_file_path: str = ""):
|
10 |
+
|
11 |
+
self.custom_audio = AudioSegment.from_file(file=sound_file_path, format="wav")
|
12 |
+
self.custom_audio_len = len(self.custom_audio)
|
13 |
+
|
14 |
+
self.ms_per_audio_segment: int = 20
|
15 |
+
self.audio_segment_shape: tuple
|
16 |
+
|
17 |
+
self.play_state_tracker: dict = {"curr_segment": -1} # Currently playing segment
|
18 |
+
self.audio_segments_created: bool = False
|
19 |
+
self.audio_segments: list = []
|
20 |
+
|
21 |
+
def prepare_audio(self, frame: av.AudioFrame):
|
22 |
+
raw_samples = frame.to_ndarray()
|
23 |
+
sound = AudioSegment(
|
24 |
+
data=raw_samples.tobytes(),
|
25 |
+
sample_width=frame.format.bytes,
|
26 |
+
frame_rate=frame.sample_rate,
|
27 |
+
channels=len(frame.layout.channels),
|
28 |
+
)
|
29 |
+
|
30 |
+
self.ms_per_audio_segment = len(sound)
|
31 |
+
self.audio_segment_shape = raw_samples.shape
|
32 |
+
|
33 |
+
self.custom_audio = self.custom_audio.set_channels(sound.channels)
|
34 |
+
self.custom_audio = self.custom_audio.set_frame_rate(sound.frame_rate)
|
35 |
+
self.custom_audio = self.custom_audio.set_sample_width(sound.sample_width)
|
36 |
+
|
37 |
+
self.audio_segments = [
|
38 |
+
self.custom_audio[i : i + self.ms_per_audio_segment]
|
39 |
+
for i in range(0, self.custom_audio_len - self.custom_audio_len % self.ms_per_audio_segment, self.ms_per_audio_segment)
|
40 |
+
]
|
41 |
+
self.total_segments = len(self.audio_segments) - 1 # -1 because we start from 0.
|
42 |
+
|
43 |
+
self.audio_segments_created = True
|
44 |
+
|
45 |
+
def process(self, frame: av.AudioFrame, play_sound: bool = False):
|
46 |
+
|
47 |
+
"""
|
48 |
+
Takes in the current input audio frame and based on play_sound boolean value
|
49 |
+
either starts sending the custom audio frame or dampens the frame wave to emulate silence.
|
50 |
+
|
51 |
+
For eg. playing a notification based on some event.
|
52 |
+
"""
|
53 |
+
|
54 |
+
if not self.audio_segments_created:
|
55 |
+
self.prepare_audio(frame)
|
56 |
+
|
57 |
+
raw_samples = frame.to_ndarray()
|
58 |
+
_curr_segment = self.play_state_tracker["curr_segment"]
|
59 |
+
|
60 |
+
if play_sound:
|
61 |
+
if _curr_segment < self.total_segments:
|
62 |
+
_curr_segment += 1
|
63 |
+
else:
|
64 |
+
_curr_segment = 0
|
65 |
+
|
66 |
+
sound = self.audio_segments[_curr_segment]
|
67 |
+
|
68 |
+
else:
|
69 |
+
if -1 < _curr_segment < self.total_segments:
|
70 |
+
_curr_segment += 1
|
71 |
+
sound = self.audio_segments[_curr_segment]
|
72 |
+
else:
|
73 |
+
_curr_segment = -1
|
74 |
+
sound = AudioSegment(
|
75 |
+
data=raw_samples.tobytes(),
|
76 |
+
sample_width=frame.format.bytes,
|
77 |
+
frame_rate=frame.sample_rate,
|
78 |
+
channels=len(frame.layout.channels),
|
79 |
+
)
|
80 |
+
sound = sound.apply_gain(-100)
|
81 |
+
|
82 |
+
self.play_state_tracker["curr_segment"] = _curr_segment
|
83 |
+
|
84 |
+
channel_sounds = sound.split_to_mono()
|
85 |
+
channel_samples = [s.get_array_of_samples() for s in channel_sounds]
|
86 |
+
|
87 |
+
new_samples = np.array(channel_samples).T
|
88 |
+
|
89 |
+
new_samples = new_samples.reshape(self.audio_segment_shape)
|
90 |
+
new_frame = av.AudioFrame.from_ndarray(new_samples, layout=frame.layout.name)
|
91 |
+
new_frame.sample_rate = frame.sample_rate
|
92 |
+
|
93 |
+
return new_frame
|
drowsy_detection.py
ADDED
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import time
|
3 |
+
import numpy as np
|
4 |
+
import mediapipe as mp
|
5 |
+
from mediapipe.python.solutions.drawing_utils import _normalized_to_pixel_coordinates as denormalize_coordinates
|
6 |
+
|
7 |
+
|
8 |
+
def get_mediapipe_app(
|
9 |
+
max_num_faces=1,
|
10 |
+
refine_landmarks=True,
|
11 |
+
min_detection_confidence=0.5,
|
12 |
+
min_tracking_confidence=0.5,
|
13 |
+
):
|
14 |
+
"""Initialize and return Mediapipe FaceMesh Solution Graph object"""
|
15 |
+
face_mesh = mp.solutions.face_mesh.FaceMesh(
|
16 |
+
max_num_faces=max_num_faces,
|
17 |
+
refine_landmarks=refine_landmarks,
|
18 |
+
min_detection_confidence=min_detection_confidence,
|
19 |
+
min_tracking_confidence=min_tracking_confidence,
|
20 |
+
)
|
21 |
+
|
22 |
+
return face_mesh
|
23 |
+
|
24 |
+
|
25 |
+
def distance(point_1, point_2):
|
26 |
+
"""Calculate l2-norm between two points"""
|
27 |
+
dist = sum([(i - j) ** 2 for i, j in zip(point_1, point_2)]) ** 0.5
|
28 |
+
return dist
|
29 |
+
|
30 |
+
|
31 |
+
def get_ear(landmarks, refer_idxs, frame_width, frame_height):
|
32 |
+
"""
|
33 |
+
Calculate Eye Aspect Ratio for one eye.
|
34 |
+
|
35 |
+
Args:
|
36 |
+
landmarks: (list) Detected landmarks list
|
37 |
+
refer_idxs: (list) Index positions of the chosen landmarks
|
38 |
+
in order P1, P2, P3, P4, P5, P6
|
39 |
+
|
40 |
+
frame_width: (int) Width of captured frame
|
41 |
+
frame_height: (int) Height of captured frame
|
42 |
+
|
43 |
+
Returns:
|
44 |
+
ear: (float) Eye aspect ratio
|
45 |
+
"""
|
46 |
+
try:
|
47 |
+
# Compute the euclidean distance between the horizontal
|
48 |
+
coords_points = []
|
49 |
+
for i in refer_idxs:
|
50 |
+
lm = landmarks[i]
|
51 |
+
coord = denormalize_coordinates(lm.x, lm.y, frame_width, frame_height)
|
52 |
+
coords_points.append(coord)
|
53 |
+
|
54 |
+
# Eye landmark (x, y)-coordinates
|
55 |
+
P2_P6 = distance(coords_points[1], coords_points[5])
|
56 |
+
P3_P5 = distance(coords_points[2], coords_points[4])
|
57 |
+
P1_P4 = distance(coords_points[0], coords_points[3])
|
58 |
+
|
59 |
+
# Compute the eye aspect ratio
|
60 |
+
ear = (P2_P6 + P3_P5) / (2.0 * P1_P4)
|
61 |
+
|
62 |
+
except:
|
63 |
+
ear = 0.0
|
64 |
+
coords_points = None
|
65 |
+
|
66 |
+
return ear, coords_points
|
67 |
+
|
68 |
+
|
69 |
+
def calculate_avg_ear(landmarks, left_eye_idxs, right_eye_idxs, image_w, image_h):
|
70 |
+
# Calculate Eye aspect ratio
|
71 |
+
|
72 |
+
left_ear, left_lm_coordinates = get_ear(landmarks, left_eye_idxs, image_w, image_h)
|
73 |
+
right_ear, right_lm_coordinates = get_ear(landmarks, right_eye_idxs, image_w, image_h)
|
74 |
+
Avg_EAR = (left_ear + right_ear) / 2.0
|
75 |
+
|
76 |
+
return Avg_EAR, (left_lm_coordinates, right_lm_coordinates)
|
77 |
+
|
78 |
+
|
79 |
+
def plot_eye_landmarks(frame, left_lm_coordinates, right_lm_coordinates, color):
|
80 |
+
for lm_coordinates in [left_lm_coordinates, right_lm_coordinates]:
|
81 |
+
if lm_coordinates:
|
82 |
+
for coord in lm_coordinates:
|
83 |
+
cv2.circle(frame, coord, 2, color, -1)
|
84 |
+
|
85 |
+
frame = cv2.flip(frame, 1)
|
86 |
+
return frame
|
87 |
+
|
88 |
+
|
89 |
+
def plot_text(image, text, origin, color, font=cv2.FONT_HERSHEY_SIMPLEX, fntScale=0.8, thickness=2):
|
90 |
+
image = cv2.putText(image, text, origin, font, fntScale, color, thickness)
|
91 |
+
return image
|
92 |
+
|
93 |
+
|
94 |
+
class VideoFrameHandler:
|
95 |
+
def __init__(self):
|
96 |
+
"""
|
97 |
+
Initialize the necessary constants, mediapipe app
|
98 |
+
and tracker variables
|
99 |
+
"""
|
100 |
+
# Left and right eye chosen landmarks.
|
101 |
+
self.eye_idxs = {
|
102 |
+
"left": [362, 385, 387, 263, 373, 380],
|
103 |
+
"right": [33, 160, 158, 133, 153, 144],
|
104 |
+
}
|
105 |
+
|
106 |
+
# Used for coloring landmark points.
|
107 |
+
# Its value depends on the current EAR value.
|
108 |
+
self.RED = (0, 0, 255) # BGR
|
109 |
+
self.GREEN = (0, 255, 0) # BGR
|
110 |
+
|
111 |
+
# Initializing Mediapipe FaceMesh solution pipeline
|
112 |
+
self.facemesh_model = get_mediapipe_app()
|
113 |
+
|
114 |
+
# For tracking counters and sharing states in and out of callbacks.
|
115 |
+
self.state_tracker = {
|
116 |
+
"start_time": time.perf_counter(),
|
117 |
+
"DROWSY_TIME": 0.0, # Holds the amount of time passed with EAR < EAR_THRESH
|
118 |
+
"COLOR": self.GREEN,
|
119 |
+
"play_alarm": False,
|
120 |
+
}
|
121 |
+
|
122 |
+
self.EAR_txt_pos = (10, 30)
|
123 |
+
|
124 |
+
def process(self, frame: np.array, thresholds: dict):
|
125 |
+
"""
|
126 |
+
This function is used to implement our Drowsy detection algorithm
|
127 |
+
|
128 |
+
Args:
|
129 |
+
frame: (np.array) Input frame matrix.
|
130 |
+
thresholds: (dict) Contains the two threshold values
|
131 |
+
WAIT_TIME and EAR_THRESH.
|
132 |
+
|
133 |
+
Returns:
|
134 |
+
The processed frame and a boolean flag to
|
135 |
+
indicate if the alarm should be played or not.
|
136 |
+
"""
|
137 |
+
|
138 |
+
# To improve performance,
|
139 |
+
# mark the frame as not writeable to pass by reference.
|
140 |
+
frame.flags.writeable = False
|
141 |
+
frame_h, frame_w, _ = frame.shape
|
142 |
+
|
143 |
+
DROWSY_TIME_txt_pos = (10, int(frame_h // 2 * 1.7))
|
144 |
+
ALM_txt_pos = (10, int(frame_h // 2 * 1.85))
|
145 |
+
|
146 |
+
results = self.facemesh_model.process(frame)
|
147 |
+
|
148 |
+
if results.multi_face_landmarks:
|
149 |
+
landmarks = results.multi_face_landmarks[0].landmark
|
150 |
+
EAR, coordinates = calculate_avg_ear(landmarks, self.eye_idxs["left"], self.eye_idxs["right"], frame_w, frame_h)
|
151 |
+
frame = plot_eye_landmarks(frame, coordinates[0], coordinates[1], self.state_tracker["COLOR"])
|
152 |
+
|
153 |
+
if EAR < thresholds["EAR_THRESH"]:
|
154 |
+
|
155 |
+
# Increase DROWSY_TIME to track the time period with EAR less than threshold
|
156 |
+
# and reset the start_time for the next iteration.
|
157 |
+
end_time = time.perf_counter()
|
158 |
+
|
159 |
+
self.state_tracker["DROWSY_TIME"] += end_time - self.state_tracker["start_time"]
|
160 |
+
self.state_tracker["start_time"] = end_time
|
161 |
+
self.state_tracker["COLOR"] = self.RED
|
162 |
+
|
163 |
+
if self.state_tracker["DROWSY_TIME"] >= thresholds["WAIT_TIME"]:
|
164 |
+
self.state_tracker["play_alarm"] = True
|
165 |
+
plot_text(frame, "WAKE UP! WAKE UP", ALM_txt_pos, self.state_tracker["COLOR"])
|
166 |
+
|
167 |
+
else:
|
168 |
+
self.state_tracker["start_time"] = time.perf_counter()
|
169 |
+
self.state_tracker["DROWSY_TIME"] = 0.0
|
170 |
+
self.state_tracker["COLOR"] = self.GREEN
|
171 |
+
self.state_tracker["play_alarm"] = False
|
172 |
+
|
173 |
+
EAR_txt = f"EAR: {round(EAR, 2)}"
|
174 |
+
DROWSY_TIME_txt = f"DROWSY: {round(self.state_tracker['DROWSY_TIME'], 3)} Secs"
|
175 |
+
plot_text(frame, EAR_txt, self.EAR_txt_pos, self.state_tracker["COLOR"])
|
176 |
+
plot_text(frame, DROWSY_TIME_txt, DROWSY_TIME_txt_pos, self.state_tracker["COLOR"])
|
177 |
+
|
178 |
+
else:
|
179 |
+
self.state_tracker["start_time"] = time.perf_counter()
|
180 |
+
self.state_tracker["DROWSY_TIME"] = 0.0
|
181 |
+
self.state_tracker["COLOR"] = self.GREEN
|
182 |
+
self.state_tracker["play_alarm"] = False
|
183 |
+
|
184 |
+
# Flip the frame horizontally for a selfie-view display.
|
185 |
+
frame = cv2.flip(frame, 1)
|
186 |
+
|
187 |
+
return frame, self.state_tracker["play_alarm"]
|
requirements.txt
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
cryptography
|
2 |
+
pyOpenSSL
|
3 |
+
aiortc
|
4 |
+
numpy
|
5 |
+
opencv-python-headless
|
6 |
+
pydub
|
7 |
+
mediapipe
|
8 |
+
streamlit
|
9 |
+
streamlit_webrtc
|
10 |
+
streamlit-nested-layout
|