-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
196 lines (162 loc) · 7.18 KB
/
Copy pathapp.js
File metadata and controls
196 lines (162 loc) · 7.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import { FaceLandmarker, FilesetResolver } from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3/+esm";
const video = document.getElementById("webcam");
const canvasElement = document.getElementById("output_canvas");
const canvasCtx = canvasElement.getContext("2d");
const toggleBtn = document.getElementById("toggle-monitor-btn");
const loadingText = document.getElementById("loading");
// UI Metric Elements
const scoreElement = document.getElementById("attention-score");
const warningsElement = document.getElementById("fatigue-warnings");
const statusText = document.getElementById("status-text");
let faceLandmarker;
let runningMode = "VIDEO";
let isMonitoring = false;
let lastVideoTime = -1;
// Sleep and Fatigue Analysis Variables
let fatigueWarningsCount = 0;
let consecutiveClosedFrames = 0;
const EAR_THRESHOLD = 0.25; // Eye closure threshold (adjustable between 0.20 - 0.30)
const CLOSED_FRAMES_THRESHOLD = 15; // Duration of eyes closed (approx. 0.5 seconds at 30FPS)
// Eye Coordinate Indices in MediaPipe Face Mesh
const LEFT_EYE = [133, 160, 158, 33, 144, 153];
const RIGHT_EYE = [362, 385, 387, 263, 373, 380];
// Initialize the Model
async function initializeModel() {
try {
const vision = await FilesetResolver.forVisionTasks(
"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3/wasm"
);
faceLandmarker = await FaceLandmarker.createFromOptions(vision, {
baseOptions: {
modelAssetPath: `https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task`,
// If crashing on GPU, you can change "GPU" to "CPU" here
delegate: "GPU"
},
outputFaceBlendshapes: true,
runningMode: runningMode,
numFaces: 1
});
loadingText.style.display = "none";
console.log("AI Model Loaded Successfully.");
} catch (error) {
console.error("Error loading AI model:", error);
loadingText.innerText = "Model Failed to Load! (Check F12 Console for details)";
loadingText.style.color = "#ef4444"; // Red warning color
}
}
// Enable / Disable Camera
async function enableCam() {
// Warn if the model is not loaded (instead of failing silently)
if (!faceLandmarker) {
alert("The AI model is still loading. Please wait a few seconds and try again.");
return;
}
if (isMonitoring) {
isMonitoring = false;
video.srcObject.getTracks().forEach(track => track.stop());
toggleBtn.innerText = "Start Monitoring";
toggleBtn.style.backgroundColor = "#3b82f6";
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
resetAlarm();
} else {
isMonitoring = true;
toggleBtn.innerText = "Stop Monitoring";
toggleBtn.style.backgroundColor = "#ef4444";
const constraints = { video: { facingMode: "user" } };
// Request camera access and catch errors
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
video.srcObject = stream;
video.addEventListener("loadeddata", predictWebcam);
}).catch((err) => {
console.error("Camera access error:", err);
alert("Could not access the camera! Ensure camera permissions are granted in browser settings and a webcam is connected.");
// Reset button if an error occurs
isMonitoring = false;
toggleBtn.innerText = "Start Monitoring";
toggleBtn.style.backgroundColor = "#3b82f6";
});
}
}
// Euclidean Distance Calculation Function (Mathematical Formula)
function euclideanDistance(p1, p2) {
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}
// Eye Aspect Ratio (EAR) Calculation
function calculateEAR(landmarks, eyeIndices) {
const p1 = landmarks[eyeIndices[0]]; // Inner corner
const p2 = landmarks[eyeIndices[1]]; // Top 1
const p3 = landmarks[eyeIndices[2]]; // Top 2
const p4 = landmarks[eyeIndices[3]]; // Outer corner
const p5 = landmarks[eyeIndices[4]]; // Bottom 2
const p6 = landmarks[eyeIndices[5]]; // Bottom 1
const vertical_1 = euclideanDistance(p2, p6);
const vertical_2 = euclideanDistance(p3, p5);
const horizontal = euclideanDistance(p1, p4);
return (vertical_1 + vertical_2) / (2.0 * horizontal);
}
// AI Inference Loop
async function predictWebcam() {
if (!isMonitoring) return;
canvasElement.width = video.videoWidth;
canvasElement.height = video.videoHeight;
let startTimeMs = performance.now();
if (lastVideoTime !== video.currentTime) {
lastVideoTime = video.currentTime;
const results = faceLandmarker.detectForVideo(video, startTimeMs);
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
if (results.faceLandmarks && results.faceLandmarks.length > 0) {
const landmarks = results.faceLandmarks[0];
// 1. EAR Calculation
const leftEAR = calculateEAR(landmarks, LEFT_EYE);
const rightEAR = calculateEAR(landmarks, RIGHT_EYE);
const avgEAR = (leftEAR + rightEAR) / 2.0;
// 2. Attention Score UI Update (Mapping EAR 0.15 - 0.35 to 0% - 100%)
let attentionScore = Math.max(0, Math.min(100, (avgEAR - 0.15) * 500));
scoreElement.innerText = Math.round(attentionScore) + "%";
// 3. Fatigue and Sleep Logic
if (avgEAR < EAR_THRESHOLD) {
consecutiveClosedFrames++;
if (consecutiveClosedFrames >= CLOSED_FRAMES_THRESHOLD) {
triggerAlarm();
}
} else {
// If eyes were closed past the threshold and are now open, increment warning count.
if (consecutiveClosedFrames >= CLOSED_FRAMES_THRESHOLD) {
fatigueWarningsCount++;
warningsElement.innerText = fatigueWarningsCount;
resetAlarm();
}
consecutiveClosedFrames = 0; // Reset counter when eyes open
}
drawFaceMesh(landmarks);
}
}
if (isMonitoring) {
window.requestAnimationFrame(predictWebcam);
}
}
// Alarms and UI Management
function triggerAlarm() {
statusText.innerText = "Sleeping - Wake Up!";
statusText.className = "status-danger";
// Note: Audio alarms using the HTML5 Audio API can be added here in the future.
}
function resetAlarm() {
statusText.innerText = "Safe & Focused on the Road";
statusText.className = "status-safe";
}
// Simple drawing function for testing (Eye contour only)
function drawFaceMesh(landmarks) {
canvasCtx.fillStyle = "#ef4444";
const allEyes = [...LEFT_EYE, ...RIGHT_EYE];
for (let i = 0; i < allEyes.length; i++) {
const point = landmarks[allEyes[i]];
const x = point.x * canvasElement.width;
const y = point.y * canvasElement.height;
canvasCtx.beginPath();
canvasCtx.arc(x, y, 2, 0, 2 * Math.PI);
canvasCtx.fill();
}
}
toggleBtn.addEventListener("click", enableCam);
initializeModel();