Sketch 2026-02-13 08:47

This sketch creates a face recognition attendance system that uses webcam input to detect faces, verify liveness through blink detection, and automatically mark attendance for recognized students. Attendance records are stored locally in the browser and can be displayed as reports showing attendance percentages.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the blink detection sensitivity — Lower EAR_THRESHOLD values require more eye closure to register a blink; higher values are more sensitive.
  2. Speed up or slow down detection — The detection interval controls how often faces are checked; lower milliseconds = more responsive but higher CPU usage.
  3. Make bounding boxes red instead of green — Change the stroke color used to draw boxes around detected faces.
  4. Increase the attendance cooldown period — Higher MARKING_DELAY values prevent the same person from being marked twice within that timeframe.
  5. Add a new known student to enroll — Extend the knownFacesData array with an additional student's name and face image URL.
  6. Remove the mirror effect from the webcam
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch builds a complete automated attendance system powered by face detection, recognition, and liveness verification. It uses the face-api.js library to detect faces in the webcam feed, compute face descriptors for recognition, and measure eye aspect ratio to confirm a real person (not a photo) is present. When a known face blinks, the system automatically records attendance with a timestamp in the browser's local storage, preventing duplicate markings with a cooldown mechanism.

The code is organized into three main sections: local data management (loading and saving attendance records), face-api.js setup and enrollment (loading pre-trained models and computing descriptors for known students), and liveness detection (eye aspect ratio calculation for blink detection). By studying this sketch, you will learn how to integrate third-party ML libraries into p5.js, work with browser local storage, handle asynchronous model loading, compute geometric features from facial landmarks, and build a real-world application combining computer vision and data persistence.

⚙️ How It Works

  1. When the sketch loads, setup() initializes the p5.js canvas and webcam capture, then asynchronously loads three pre-trained face-api.js models (tinyFaceDetector for face detection, faceLandmark68Net for 68-point facial landmarks, and faceRecognitionNet for face descriptors) from a CDN. While models load, status messages update in the DOM.
  2. After models load, enrollKnownFaces() fetches images of known students from URLs, detects their faces, and computes their face descriptors (128-dimensional vectors). These descriptors are stored in knownFaceDescriptors and passed to a FaceMatcher object that will compare unknown faces against them.
  3. When the user clicks 'Start Face Recognition', startDetection() begins calling detectFacesAndLiveness() every 500 milliseconds. This function analyzes the live webcam feed, detecting all visible faces and computing their descriptors.
  4. For each detected face, the code draws a green bounding box and compares its descriptor to known faces using faceMatcher.findBestMatch(). It also extracts the 68 facial landmarks and calculates the Eye Aspect Ratio (EAR) of the left eye using the vertical and horizontal distances between specific landmark points.
  5. If EAR drops below a threshold (0.22), a blink is detected. The system marks attendance only if: the face is recognized (matches a known student), the person blinks (liveness confirmed), and the same person hasn't been marked within the last 10 seconds (cooldown). Attendance records are saved to localStorage with name, date, and timestamp.
  6. Users can click 'Show Attendance Reports' to display a table showing each student's attendance percentage (calculated as attended sessions divided by total session days) and all their attendance timestamps. 'Clear All Local Data' erases all records and resets the system.

🎓 Concepts You'll Learn

Face detection using pre-trained neural networksFace recognition via descriptor matchingLiveness detection through eye aspect ratioAsynchronous model loading from CDNBrowser local storage for data persistenceBlink detection using facial landmarksReal-time video processing with p5.js captureCanvas transformations (translate, scale, mirror)

📝 Code Breakdown

loadAttendanceRecords()

This function demonstrates the ternary operator (condition ? trueValue : falseValue) and JSON.parse() for deserializing data from localStorage. Always provide a fallback when reading from storage in case the key doesn't exist.

function loadAttendanceRecords() {
  const records = localStorage.getItem('attendanceRecords');
  return records ? JSON.parse(records) : [];
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

conditional localStorage Retrieval with Fallback return records ? JSON.parse(records) : [];

Returns parsed attendance records from localStorage or an empty array if none exist

const records = localStorage.getItem('attendanceRecords');
Retrieves the attendance records string from the browser's localStorage; returns null if the key doesn't exist
return records ? JSON.parse(records) : [];
If records exist, parses the JSON string into a JavaScript array; otherwise returns an empty array to avoid errors

saveAttendanceRecords(records)

This function is the inverse of loadAttendanceRecords(). JSON.stringify() serializes objects and arrays into strings suitable for storage. Every time attendance is marked, this function is called to persist the updated records.

function saveAttendanceRecords(records) {
  localStorage.setItem('attendanceRecords', JSON.stringify(records));
}
Line-by-line explanation (1 lines)
localStorage.setItem('attendanceRecords', JSON.stringify(records));
Converts the JavaScript array into a JSON string and stores it in the browser's localStorage under the key 'attendanceRecords'

markAttendanceLocal(name)

This is the core function that controls attendance marking. It implements two key safeguards: cooldown (prevents rapid repeated marking) and daily uniqueness (prevents marking the same person twice on the same day). The function returns true/false to indicate success, allowing callers to handle the result.

🔬 This code checks if someone is already marked before adding them. What happens if you remove the `!` from `if (!alreadyMarkedToday)` so it runs in the opposite condition? Try it and describe what you observe.

  let records = loadAttendanceRecords();
  const alreadyMarkedToday = records.some(
    (record) => record.name === name && record.date === today
  );

  if (!alreadyMarkedToday) {
    records.push({ name, date: today, time });
function markAttendanceLocal(name) {
  const now = new Date();
  const today = now.toISOString().split('T')[0]; // "YYYY-MM-DD"
  const time = now.toTimeString().split(' ')[0]; // "HH:MM:SS"

  // Check cooldown
  if (attendanceCooldown[name] && (now.getTime() - attendanceCooldown[name]) < MARKING_DELAY) {
    statusMessage = `Attendance for ${name} recently marked.`;
    if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
    return false;
  }

  let records = loadAttendanceRecords();
  const alreadyMarkedToday = records.some(
    (record) => record.name === name && record.date === today
  );

  if (!alreadyMarkedToday) {
    records.push({ name, date: today, time });
    saveAttendanceRecords(records);
    attendanceCooldown[name] = now.getTime(); // Update cooldown
    console.log(`Attendance marked for ${name} on ${today} at ${time}`);
    statusMessage = `ATTENDANCE MARKED for ${name}!`;
    if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
    return true;
  } else {
    statusMessage = `${name} already marked attendance today.`;
    if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
    return false;
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

conditional Cooldown Prevention if (attendanceCooldown[name] && (now.getTime() - attendanceCooldown[name]) < MARKING_DELAY) {

Prevents the same person from being marked twice within MARKING_DELAY milliseconds

conditional Duplicate Daily Entry Check const alreadyMarkedToday = records.some( (record) => record.name === name && record.date === today );

Uses array.some() to check if this person already has an attendance record for today

calculation Attendance Record Object records.push({ name, date: today, time });

Creates and adds a new attendance record with name, date, and time to the records array

const now = new Date();
Creates a Date object representing the current moment
const today = now.toISOString().split('T')[0]; // "YYYY-MM-DD"
Converts the date to ISO format (YYYY-MM-DDTHH:MM:SS...) and extracts just the date part before the 'T'
const time = now.toTimeString().split(' ')[0]; // "HH:MM:SS"
Extracts the time portion (HH:MM:SS) from the date's time string, ignoring timezone info
if (attendanceCooldown[name] && (now.getTime() - attendanceCooldown[name]) < MARKING_DELAY) {
Checks if this person was recently marked and if the time since then is less than MARKING_DELAY; if true, blocks attendance marking
const alreadyMarkedToday = records.some( (record) => record.name === name && record.date === today );
Uses array.some() to check if any existing record matches both the name and today's date—returns true if found
if (!alreadyMarkedToday) {
Only proceeds to mark attendance if the person has NOT already been marked today
records.push({ name, date: today, time });
Adds a new object to the records array containing the person's name, date, and time of attendance
attendanceCooldown[name] = now.getTime(); // Update cooldown
Stores the current timestamp in the cooldown object to prevent marking this person again within MARKING_DELAY

clearAllLocalData()

This function demonstrates the confirm() dialog for destructive operations and shows good UX practice by asking for confirmation before deleting data. It also resets both persistent (localStorage) and in-memory state, then refreshes the UI to reflect the cleared state.

function clearAllLocalData() {
  if (confirm("Are you sure you want to clear all attendance data from your browser's local storage? This action cannot be undone.")) {
    localStorage.removeItem('attendanceRecords');
    knownFaceDescriptors = [];
    knownNames = [];
    attendanceCooldown = {};
    console.log("All local attendance data cleared.");
    statusMessage = "All attendance data cleared.";
    if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
    // Re-enroll known faces if needed
    enrollKnownFaces();
    // Refresh reports if visible
    if (reportsVisible) {
      displayAttendanceReports();
    }
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional User Confirmation if (confirm("Are you sure you want to clear all attendance data from your browser's local storage? This action cannot be undone.")) {

Displays a browser confirmation dialog to prevent accidental data deletion

calculation Reset All State localStorage.removeItem('attendanceRecords'); knownFaceDescriptors = []; knownNames = []; attendanceCooldown = {};

Clears all persistent and in-memory data related to attendance and face recognition

if (confirm("Are you sure you want to clear all attendance data from your browser's local storage? This action cannot be undone.")) {
Shows a browser dialog asking the user to confirm; the if block only executes if they click 'OK'
localStorage.removeItem('attendanceRecords');
Deletes the attendance records from localStorage
knownFaceDescriptors = []; knownNames = [];
Clears the in-memory arrays storing face descriptors and student names
attendanceCooldown = {};
Resets the cooldown tracking object so all students can be marked immediately after clearing
enrollKnownFaces();
Re-enrolls known faces from the image URLs, preparing the system for fresh attendance tracking
if (reportsVisible) { displayAttendanceReports(); }
If the reports window is currently visible, refreshes it to show empty data

calculateAttendancePercentageLocal(student_name)

This function introduces Set data structure for deduplication and demonstrates filter().length for counting. The attendance percentage is calculated as a ratio of this student's appearances to the total unique session days in the entire dataset.

🔬 What does this code count for 'attended'? The number of times this student appears? Now notice 'total_sessions' counts distinct dates across ALL students. If one day had 10 students present, how does that affect the percentage calculation? Try adding `console.log('attended:', attended, 'total_sessions:', total_sessions);` to see the actual numbers.

  const attended = records.filter(record => record.name === student_name).length;

  // Determine total possible attendance days: count distinct dates in all records
  const allDates = new Set(records.map(record => record.date));
  const total_sessions = allDates.size;
function calculateAttendancePercentageLocal(student_name) {
  const records = loadAttendanceRecords();
  const attended = records.filter(record => record.name === student_name).length;

  // Determine total possible attendance days: count distinct dates in all records
  const allDates = new Set(records.map(record => record.date));
  const total_sessions = allDates.size;

  if (total_sessions === 0) {
    return 0;
  }

  const percentage = (attended / total_sessions) * 100;
  return round(percentage, 2);
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Student Attendance Count const attended = records.filter(record => record.name === student_name).length;

Counts how many times this specific student appears in the attendance records

calculation Unique Session Dates const allDates = new Set(records.map(record => record.date)); const total_sessions = allDates.size;

Creates a Set of unique dates across all records to determine the total number of attendance sessions

const records = loadAttendanceRecords();
Retrieves all attendance records from localStorage
const attended = records.filter(record => record.name === student_name).length;
Filters records to only those matching the student's name, then counts them
const allDates = new Set(records.map(record => record.date));
Uses map() to extract dates from all records, then creates a Set to keep only unique dates (Sets automatically remove duplicates)
const total_sessions = allDates.size;
Gets the count of unique session dates—this is the denominator for the percentage calculation
if (total_sessions === 0) { return 0; }
Prevents division by zero: if no sessions exist, returns 0%
const percentage = (attended / total_sessions) * 100; return round(percentage, 2);
Calculates the percentage (attended sessions / total sessions × 100) and rounds to 2 decimal places

loadModels()

This function demonstrates async/await patterns and the three essential face-api.js models: detection (finding faces), landmarks (locating facial features), and descriptors (computing face identity vectors). Each await pauses execution until the model downloads, allowing the UI to show progress.

async function loadModels() {
  statusMessage = "Loading models: tinyFaceDetector...";
  if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
  await faceapi.nets.tinyFaceDetector.loadFromUri('https://cdn.jsdelivr.net/npm/face-api.js/models');
  statusMessage = "Loading models: faceLandmark68Net...";
  if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
  await faceapi.nets.faceLandmark68Net.loadFromUri('https://cdn.jsdelivr.net/npm/face-api.js/models');
  statusMessage = "Loading models: faceRecognitionNet...";
  if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
  await faceapi.nets.faceRecognitionNet.loadFromUri('https://cdn.jsdelivr.net/npm/face-api.js/models');
  console.log("All face-api.js models loaded.");
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Tiny Face Detector Model Load await faceapi.nets.tinyFaceDetector.loadFromUri('https://cdn.jsdelivr.net/npm/face-api.js/models');

Loads the lightweight face detection neural network from a CDN

calculation Facial Landmark Model Load await faceapi.nets.faceLandmark68Net.loadFromUri('https://cdn.jsdelivr.net/npm/face-api.js/models');

Loads the model that detects 68 key facial points (eyes, nose, mouth, jaw, etc.)

calculation Face Recognition Model Load await faceapi.nets.faceRecognitionNet.loadFromUri('https://cdn.jsdelivr.net/npm/face-api.js/models');

Loads the model that computes 128-dimensional face descriptors for identity matching

async function loadModels() {
Declares an async function so we can use await to wait for model downloads to complete
await faceapi.nets.tinyFaceDetector.loadFromUri('https://cdn.jsdelivr.net/npm/face-api.js/models');
Downloads and initializes the TinyFaceDetector model from a CDN; the script pauses here until loading finishes
statusMessage = "Loading models: tinyFaceDetector...";
Updates the UI to show progress while waiting for the download

enrollKnownFaces()

Enrollment is the critical first step: it computes face descriptors for all known students and initializes the FaceMatcher. The try/catch pattern ensures that a bad image URL doesn't crash the whole system. The 0.6 threshold controls how strict recognition is—lower values require closer matches.

🔬 This code fetches an image, detects a face, and stores its descriptor. What happens if you change `.withFaceDescriptor()` to just omit that part (so it only gets landmarks but not the descriptor)? Would the FaceMatcher still work? Why or why not?

      const img = await faceapi.fetchImage(data.img);
      const detections = await faceapi.detectSingleFace(img).withFaceLandmarks().withFaceDescriptor();
      if (detections) {
        knownFaceDescriptors.push(detections.descriptor);
        knownNames.push(data.name);
async function enrollKnownFaces() {
  statusMessage = "Enrolling known faces...";
  if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
  knownFaceDescriptors = [];
  knownNames = [];

  // Replace these with actual image URLs of your known students
  // For demonstration, I'm using placeholder images.
  const knownFacesData = [
    { name: "Alice", img: "https://p5js.ai/assets/alice.jpg" },
    { name: "Bob", img: "https://p5js.ai/assets/bob.jpg" },
    { name: "Charlie", img: "https://p5js.ai/assets/charlie.jpg" }
    // Add more known faces as needed
  ];

  for (const data of knownFacesData) {
    try {
      const img = await faceapi.fetchImage(data.img);
      const detections = await faceapi.detectSingleFace(img).withFaceLandmarks().withFaceDescriptor();
      if (detections) {
        knownFaceDescriptors.push(detections.descriptor);
        knownNames.push(data.name);
        console.log(`Enrolled: ${data.name}`);
      } else {
        console.warn(`Could not detect face in image for ${data.name}. Make sure the face is clear.`);
      }
    } catch (error) {
      console.error(`Error enrolling ${data.name}:`, error);
    }
  }
  faceMatcher = new faceapi.FaceMatcher(knownFaceDescriptors, 0.6); // 0.6 is a common threshold
  statusMessage = "Known faces enrolled. Ready to start.";
  if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
  console.log("FaceMatcher initialized.");
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Known Faces Data Definition const knownFacesData = [ { name: "Alice", img: "https://p5js.ai/assets/alice.jpg" }, { name: "Bob", img: "https://p5js.ai/assets/bob.jpg" }, { name: "Charlie", img: "https://p5js.ai/assets/charlie.jpg" } // Add more known faces as needed ];

Defines an array of objects, each with a student name and a URL to their face image

for-loop Enrollment Processing Loop for (const data of knownFacesData) { try { const img = await faceapi.fetchImage(data.img); const detections = await faceapi.detectSingleFace(img).withFaceLandmarks().withFaceDescriptor(); if (detections) { knownFaceDescriptors.push(detections.descriptor); knownNames.push(data.name); } } catch (error) { console.error(`Error enrolling ${data.name}:`, error); } }

For each known face, fetches its image, detects the face and computes its descriptor, storing both for later recognition

calculation FaceMatcher Initialization faceMatcher = new faceapi.FaceMatcher(knownFaceDescriptors, 0.6);

Creates the FaceMatcher object that will compare unknown face descriptors against the enrolled known faces

const knownFacesData = [
Starts an array of objects, each containing a student's name and image URL
for (const data of knownFacesData) {
Iterates through each known student's data
const img = await faceapi.fetchImage(data.img);
Downloads the image from the URL; await pauses until the download completes
const detections = await faceapi.detectSingleFace(img).withFaceLandmarks().withFaceDescriptor();
Detects the face in the image and computes its 128-dimensional descriptor; chained methods call each other
if (detections) {
Only processes if a face was successfully detected
knownFaceDescriptors.push(detections.descriptor); knownNames.push(data.name);
Stores the computed descriptor and the student's name in parallel arrays for later matching
} catch (error) { console.error(`Error enrolling ${data.name}:`, error); }
Catches and logs any errors (network issues, bad images, etc.) without crashing the entire enrollment
faceMatcher = new faceapi.FaceMatcher(knownFaceDescriptors, 0.6);
Creates the matcher object that will use the enrolled descriptors to recognize unknown faces; 0.6 is the distance threshold

calculateEAR(eyeLandmarks)

Eye Aspect Ratio (EAR) is a classic computer vision technique for detecting blinks. It relies on the geometric fact that when eyes close, the vertical distance between landmarks decreases while the horizontal width stays constant, causing the ratio to drop below a threshold. This function is the mathematical heart of liveness detection.

🔬 What does this formula do? When your eye closes, the vertical distances get smaller. What happens to the EAR value? Try adding `console.log('vertical1:', vertical1, 'vertical2:', vertical2, 'horizontal:', horizontal, 'EAR:', ear);` to see the actual numbers while your eye is open and closed.

  // Compute the Euclidean distances between the two sets of vertical eye landmarks (p2, p6) and (p3, p5)
  const vertical1 = dist(p2.x, p2.y, p6.x, p6.y);
  const vertical2 = dist(p3.x, p3.y, p5.x, p5.y);

  // Compute the Euclidean distance between the horizontal eye landmark (p1, p4)
  const horizontal = dist(p1.x, p1.y, p4.x, p4.y);

  // Compute the Eye Aspect Ratio
  const ear = (vertical1 + vertical2) / (2.0 * horizontal);
function calculateEAR(eyeLandmarks) {
  // Eye landmarks for face-api.js 68-point model (indices 36-41 for right eye, 42-47 for left eye)
  // For left eye (indices 42-47): p1=42, p2=43, p3=44, p4=45, p5=46, p6=47
  // Vertical distances: ||p3 - p5|| (44-46), ||p2 - p6|| (43-47)
  // Horizontal distance: ||p1 - p4|| (42-45)

  // Using left eye indices as they were in the Python example, assuming 68-point model:
  // p1=eyeLandmarks[0], p2=eyeLandmarks[1], p3=eyeLandmarks[2],
  // p4=eyeLandmarks[3], p5=eyeLandmarks[4], p6=eyeLandmarks[5]

  const p1 = eyeLandmarks[0];
  const p2 = eyeLandmarks[1];
  const p3 = eyeLandmarks[2];
  const p4 = eyeLandmarks[3];
  const p5 = eyeLandmarks[4];
  const p6 = eyeLandmarks[5];

  // Compute the Euclidean distances between the two sets of vertical eye landmarks (p2, p6) and (p3, p5)
  const vertical1 = dist(p2.x, p2.y, p6.x, p6.y);
  const vertical2 = dist(p3.x, p3.y, p5.x, p5.y);

  // Compute the Euclidean distance between the horizontal eye landmark (p1, p4)
  const horizontal = dist(p1.x, p1.y, p4.x, p4.y);

  // Compute the Eye Aspect Ratio
  const ear = (vertical1 + vertical2) / (2.0 * horizontal);

  // Return the EAR
  return ear;
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Eye Landmark Point Extraction const p1 = eyeLandmarks[0]; const p2 = eyeLandmarks[1]; const p3 = eyeLandmarks[2]; const p4 = eyeLandmarks[3]; const p5 = eyeLandmarks[4]; const p6 = eyeLandmarks[5];

Extracts six specific eye landmark points from the array of 68 facial landmarks

calculation Vertical and Horizontal Distance Calculations const vertical1 = dist(p2.x, p2.y, p6.x, p6.y); const vertical2 = dist(p3.x, p3.y, p5.x, p5.y); const horizontal = dist(p1.x, p1.y, p4.x, p4.y);

Computes three Euclidean distances: two vertical (eye opening height) and one horizontal (eye width)

calculation Eye Aspect Ratio Computation const ear = (vertical1 + vertical2) / (2.0 * horizontal);

Applies the EAR formula: sum of vertical distances divided by twice the horizontal distance

const p1 = eyeLandmarks[0];
Extracts the first eye landmark point (left eye corner)
const vertical1 = dist(p2.x, p2.y, p6.x, p6.y);
Calculates the Euclidean distance between p2 and p6 (first vertical measure of eye opening)
const vertical2 = dist(p3.x, p3.y, p5.x, p5.y);
Calculates the second vertical distance (confirms eye opening from another point pair)
const horizontal = dist(p1.x, p1.y, p4.x, p4.y);
Calculates the horizontal distance across the eye (width)
const ear = (vertical1 + vertical2) / (2.0 * horizontal);
Applies the EAR formula: when eyes close, vertical distances shrink while horizontal distance stays constant, so EAR decreases

setup()

setup() is called once when the sketch starts. This version is async, meaning it waits for models to download before completing. This is essential because face recognition can't start until all models are loaded. Notice how status updates keep the user informed during the waiting period.

async function setup() {
  canvas = createCanvas(windowWidth, windowHeight);
  capture = createCapture(VIDEO);
  capture.size(width, height);
  capture.hide(); // Hide the capture element, draw it on canvas instead

  // Create the status message div and append it to the body
  statusDiv = createDiv(statusMessage);
  statusDiv.id('status-message'); // Assign the ID for CSS styling

  // Load models and enroll known faces before starting detection
  await loadModels();
  await enrollKnownFaces();

  document.getElementById('startRecognition').addEventListener('click', startDetection);
  document.getElementById('showReports').addEventListener('click', displayAttendanceReports);
  document.getElementById('clearData').addEventListener('click', clearAllLocalData);

  // Create reports container (hidden by default)
  const reportsDiv = document.createElement('div');
  reportsDiv.id = 'reports-container';
  reportsDiv.innerHTML = `
    <h2>Attendance Reports</h2>
    <div id="reports-content"></div>
    <button id="closeReports">Close Reports</button>
  `;
  document.body.appendChild(reportsDiv);
  document.getElementById('closeReports').addEventListener('click', () => {
    reportsVisible = false;
    document.getElementById('reports-container').style.display = 'none';
  });
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

calculation Canvas and Capture Initialization canvas = createCanvas(windowWidth, windowHeight); capture = createCapture(VIDEO); capture.size(width, height); capture.hide();

Creates a full-window canvas and hidden webcam capture element

calculation Status Message DOM Element statusDiv = createDiv(statusMessage); statusDiv.id('status-message');

Creates a p5.js div for displaying status messages with CSS styling

calculation Async Model and Face Enrollment Loading await loadModels(); await enrollKnownFaces();

Loads face-api.js models and enrolls known student faces before any detection can occur

calculation Event Listener Attachment document.getElementById('startRecognition').addEventListener('click', startDetection); document.getElementById('showReports').addEventListener('click', displayAttendanceReports); document.getElementById('clearData').addEventListener('click', clearAllLocalData);

Connects three HTML buttons to their corresponding functions

calculation Reports Container DOM Creation const reportsDiv = document.createElement('div'); reportsDiv.id = 'reports-container'; reportsDiv.innerHTML = `...`; document.body.appendChild(reportsDiv);

Creates and inserts the reports modal HTML into the page

async function setup() {
Declares setup() as async so we can use await for model loading
canvas = createCanvas(windowWidth, windowHeight);
Creates a p5.js canvas that fills the entire window
capture = createCapture(VIDEO);
Accesses the user's webcam and creates a capture object
capture.hide();
Hides the default capture element because we'll draw it on the canvas ourselves
statusDiv = createDiv(statusMessage);
Creates a p5.js div element for displaying status updates to the user
statusDiv.id('status-message');
Assigns a CSS ID so styling rules in style.css apply to this element
await loadModels();
Pauses execution until all three face-api.js models finish downloading
await enrollKnownFaces();
Pauses execution until all known student faces are enrolled and FaceMatcher is initialized
document.getElementById('startRecognition').addEventListener('click', startDetection);
Connects the 'Start Face Recognition' button to the startDetection function
const reportsDiv = document.createElement('div');
Creates a new DOM element that will hold the attendance reports
document.body.appendChild(reportsDiv);
Adds the reports div to the HTML page so it becomes visible when needed

draw()

draw() runs 60 times per second. Here it handles two main tasks: checking if reports are visible and clearing the canvas accordingly, and drawing the mirrored webcam feed. The push/pop pattern is essential—it keeps transformations (translate, scale) local so they don't affect other drawings.

🔬 This code mirrors the webcam feed. What happens if you change `scale(-1, 1)` to `scale(1, 1)` (remove the negative)? The webcam would no longer be flipped. Try it and notice how it feels unnatural without the mirror effect.

  push();
  translate(width, 0);
  scale(-1, 1);
  image(capture, 0, 0, width, height);
  pop();
function draw() {
  if (reportsVisible) {
    clear(); // Clear canvas if reports are visible
    return;
  }

  background(220); // Clear the canvas

  // Draw the capture feed mirrored
  push();
  translate(width, 0);
  scale(-1, 1);
  image(capture, 0, 0, width, height);
  pop();

  // The status message is now managed by the statusDiv DOM element,
  // so we no longer need to draw it on the canvas with text().
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional Reports Visibility Check if (reportsVisible) { clear(); return; }

Clears canvas and exits early when reports are displayed

calculation Mirrored Webcam Feed push(); translate(width, 0); scale(-1, 1); image(capture, 0, 0, width, height); pop();

Flips the webcam feed horizontally so it appears as a mirror (like video call apps)

if (reportsVisible) {
Checks if the user has opened the attendance reports
clear();
Clears the canvas when reports are visible
return;
Exits the draw function early so the webcam feed doesn't draw over the reports
background(220);
Sets the canvas background to light gray, erasing the previous frame
push();
Saves the current transformation state (position, scale, rotation)
translate(width, 0);
Moves the origin point to the right edge of the canvas
scale(-1, 1);
Flips the x-axis (horizontal mirror effect) while leaving y unchanged
image(capture, 0, 0, width, height);
Draws the webcam feed after the flip, creating a mirror effect
pop();
Restores the previous transformation state so subsequent drawings aren't mirrored

detectFacesAndLiveness()

This is the main detection and attendance-marking function. It runs every 500ms (called by startDetection). It detects faces, recognizes them via descriptor matching, verifies liveness via blink, and marks attendance only when all conditions pass. The function also draws visual feedback (boxes, names, status) on the canvas.

🔬 This code only marks attendance if the face is recognized AND the person blinks. What happens if you remove the `&& isBlinkDetected` part and change it to just `if (name !== "Unknown")`? Would attendance be marked without a blink? Try it and see how the system becomes vulnerable to photo spoofing.

    if (name !== "Unknown" && isBlinkDetected) {
      livenessStatus = "Liveness OK ✅";
      // Mark attendance locally if recognized and liveness passes
      markAttendanceLocal(name); // This function already updates statusDiv
    } else if (name === "Unknown") {
      livenessStatus = "Liveness Failed ❌ (Unknown Face)";
    }
async function detectFacesAndLiveness() {
  if (!capture || !capture.elt || capture.elt.readyState !== 4) {
    statusMessage = "Waiting for webcam...";
    if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
    return;
  }
  if (!faceMatcher) {
    statusMessage = "Initializing face matcher...";
    if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
    return;
  }

  statusMessage = "Detecting faces...";
  if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
  const detections = await faceapi.detectAllFaces(capture.elt, new faceapi.TinyFaceDetectorOptions())
    .withFaceLandmarks()
    .withFaceDescriptors();

  if (detections.length > 0) {
    statusMessage = `Found ${detections.length} face(s).`;
    if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
  } else {
    statusMessage = "No faces detected.";
    if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
  }

  push();
  translate(width, 0);
  scale(-1, 1); // Mirror the drawing on the canvas

  for (const detection of detections) {
    // Resize detection results to canvas size
    const resizedDetections = faceapi.resizeResults(detection, { width, height });
    const box = resizedDetections.detection.box;
    const landmarks = resizedDetections.landmarks;

    // Draw bounding box
    noFill();
    stroke(0, 255, 0);
    strokeWeight(2);
    rect(box.x, box.y, box.width, box.height);

    // Draw landmarks (optional, for debugging)
    // faceapi.draw.drawFaceLandmarks(canvas.elt, resizedDetections);

    // Perform face recognition
    const bestMatch = faceMatcher.findBestMatch(resizedDetections.descriptor);
    let name = bestMatch.label === 'unknown' ? 'Unknown' : knownNames[parseInt(bestMatch.label)];
    let livenessStatus = "";

    // Perform liveness detection (blink)
    const isBlinkDetected = detectBlink(landmarks);

    if (isBlinkDetected) {
      livenessStatus = "Liveness OK ✅";
    } else {
      livenessStatus = "Liveness Failed ❌";
    }

    if (name !== "Unknown" && isBlinkDetected) {
      livenessStatus = "Liveness OK ✅";
      // Mark attendance locally if recognized and liveness passes
      markAttendanceLocal(name); // This function already updates statusDiv
    } else if (name === "Unknown") {
      livenessStatus = "Liveness Failed ❌ (Unknown Face)";
    }

    // Display name and liveness status on canvas (these are graphics, so keep on canvas)
    fill(0, 255, 0);
    noStroke();
    textSize(16);
    textAlign(CENTER, BOTTOM);
    text(name, box.x + box.width / 2, box.y - 10);
    textSize(14);
    text(livenessStatus, box.x + box.width / 2, box.y + box.height + 20);
  }
  pop();
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

conditional Webcam Readiness Check if (!capture || !capture.elt || capture.elt.readyState !== 4) {

Ensures the webcam is fully loaded and streaming before attempting face detection

conditional FaceMatcher Initialization Check if (!faceMatcher) {

Verifies that known faces have been enrolled before attempting recognition

calculation Face Detection with Landmarks and Descriptors const detections = await faceapi.detectAllFaces(capture.elt, new faceapi.TinyFaceDetectorOptions()) .withFaceLandmarks() .withFaceDescriptors();

Detects all faces in the webcam frame and computes their landmarks and descriptors

for-loop Per-Face Processing Loop for (const detection of detections) {

Iterates through each detected face to perform recognition and liveness detection

calculation Face Matching and Identification const bestMatch = faceMatcher.findBestMatch(resizedDetections.descriptor); let name = bestMatch.label === 'unknown' ? 'Unknown' : knownNames[parseInt(bestMatch.label)];

Compares the detected face descriptor against all known face descriptors and returns the closest match

conditional Liveness Verification and Attendance Marking if (name !== "Unknown" && isBlinkDetected) { livenessStatus = "Liveness OK ✅"; markAttendanceLocal(name); }

Marks attendance only if face is recognized AND a blink is detected

calculation Bounding Box and Label Drawing rect(box.x, box.y, box.width, box.height); text(name, box.x + box.width / 2, box.y - 10);

Draws the green bounding box and displays the student's name and liveness status on screen

if (!capture || !capture.elt || capture.elt.readyState !== 4) {
Checks three conditions: capture exists, the HTML element exists, and readyState is 4 (HAVE_ENOUGH_DATA). If any fail, the webcam isn't ready.
const detections = await faceapi.detectAllFaces(capture.elt, new faceapi.TinyFaceDetectorOptions())
Runs face detection on the webcam frame using TinyFaceDetector (lightweight model)
.withFaceLandmarks() .withFaceDescriptors();
Chains methods to also compute 68-point landmarks and 128-dimensional descriptors for each face
for (const detection of detections) {
Loops through each face detected in the frame
const resizedDetections = faceapi.resizeResults(detection, { width, height });
Scales detection coordinates to match the canvas size (face-api returns coordinates in original video resolution)
const bestMatch = faceMatcher.findBestMatch(resizedDetections.descriptor);
Compares this face's descriptor to all enrolled known face descriptors and returns the closest match
let name = bestMatch.label === 'unknown' ? 'Unknown' : knownNames[parseInt(bestMatch.label)];
If bestMatch is 'unknown', sets name to 'Unknown'; otherwise retrieves the actual student name
const isBlinkDetected = detectBlink(landmarks);
Calls the blink detection function with this face's landmarks
if (name !== "Unknown" && isBlinkDetected) {
Only marks attendance if BOTH conditions are true: face is recognized AND person blinked (liveness)
rect(box.x, box.y, box.width, box.height);
Draws a green rectangle around the detected face
text(name, box.x + box.width / 2, box.y - 10);
Displays the person's name centered above the bounding box

startDetection()

setInterval() creates a repeating timer that calls a function at regular intervals (in milliseconds). This function starts the main detection loop. The cleanup check prevents accidentally creating multiple overlapping intervals if the button is clicked multiple times.

function startDetection() {
  if (detectionInterval) {
    clearInterval(detectionInterval);
  }
  detectionInterval = setInterval(detectFacesAndLiveness, 500); // Run detection every 500ms
  statusMessage = "Face recognition started.";
  if (statusDiv) statusDiv.html(statusMessage); // Update DOM element
  console.log("Face recognition started.");
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

conditional Previous Interval Cleanup if (detectionInterval) { clearInterval(detectionInterval); }

Clears any existing detection interval before starting a new one (prevents multiple simultaneous intervals)

calculation Detection Interval Setup detectionInterval = setInterval(detectFacesAndLiveness, 500);

Creates a repeating timer that calls detectFacesAndLiveness() every 500 milliseconds

if (detectionInterval) {
Checks if a detection interval is already running
clearInterval(detectionInterval);
Stops the existing interval to avoid multiple simultaneous detection loops
detectionInterval = setInterval(detectFacesAndLiveness, 500);
Creates a new interval that calls detectFacesAndLiveness every 500 milliseconds (twice per second)

displayAttendanceReports()

This function generates an HTML table dynamically from stored attendance records. It demonstrates Set for deduplication, filter/map/join for data transformation, and string interpolation for HTML generation. The table shows each student's percentage and a chronological list of their attendance timestamps.

🔬 What does `new Set(records.map(record => record.name))` do? It removes duplicate names. What would happen if you removed the `new Set(...)` part and just used `records.map(record => record.name)`? You'd see the same name multiple times in the table. Try it and observe.

  // Get unique student names
  const uniqueNames = [...new Set(records.map(record => record.name))];

  let tableHTML = `
    <table>
      <thead>
        <tr>
          <th>Student Name</th>
          <th>Attendance %</th>
          <th>Details</th>
        </tr>
      </thead>
      <tbody>
  `;
function displayAttendanceReports() {
  reportsVisible = true;
  document.getElementById('reports-container').style.display = 'block';

  const reportsContent = document.getElementById('reports-content');
  reportsContent.innerHTML = ''; // Clear previous reports

  const records = loadAttendanceRecords();
  if (records.length === 0) {
    reportsContent.innerHTML = '<p>No attendance data available yet.</p>';
    return;
  }

  // Get unique student names
  const uniqueNames = [...new Set(records.map(record => record.name))];

  let tableHTML = `
    <table>
      <thead>
        <tr>
          <th>Student Name</th>
          <th>Attendance %</th>
          <th>Details</th>
        </tr>
      </thead>
      <tbody>
  `;

  for (const name of uniqueNames) {
    const percentage = calculateAttendancePercentageLocal(name);
    const details = records.filter(record => record.name === name)
                           .map(record => `${record.date} at ${record.time}`)
                           .join('<br>');
    tableHTML += `
      <tr>
        <td>${name}</td>
        <td>${percentage}%</td>
        <td>${details}</td>
      </tr>
    `;
  }

  tableHTML += `
      </tbody>
    </table>
  `;

  reportsContent.innerHTML = tableHTML;
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

calculation Reports Container Display reportsVisible = true; document.getElementById('reports-container').style.display = 'block';

Marks reports as visible and shows the reports modal on screen

conditional Empty Data Check if (records.length === 0) { reportsContent.innerHTML = '<p>No attendance data available yet.</p>'; return; }

If no records exist, displays a message and exits early

calculation Unique Student Names const uniqueNames = [...new Set(records.map(record => record.name))];

Creates a list of unique student names (removes duplicates) from all records

for-loop HTML Table Generation Loop for (const name of uniqueNames) { const percentage = calculateAttendancePercentageLocal(name); const details = records.filter(record => record.name === name) .map(record => `${record.date} at ${record.time}`) .join('<br>'); tableHTML += `... `; }

For each unique student, calculates their percentage and builds a table row

reportsVisible = true;
Sets the global flag indicating reports are now visible
document.getElementById('reports-container').style.display = 'block';
Changes the CSS display property to show the reports modal (created in setup)
const reportsContent = document.getElementById('reports-content');
Gets a reference to the div where the table will be inserted
reportsContent.innerHTML = '';
Clears any previous report content before building a new report
const records = loadAttendanceRecords();
Retrieves all attendance records from localStorage
if (records.length === 0) {
Checks if no records exist (no attendance has been marked yet)
const uniqueNames = [...new Set(records.map(record => record.name))];
Uses Set to remove duplicate names, then spreads back into an array
for (const name of uniqueNames) {
Loops through each unique student name
const percentage = calculateAttendancePercentageLocal(name);
Calls the percentage calculation function for this student
const details = records.filter(record => record.name === name) .map(record => `${record.date} at ${record.time}`) .join('<br>');
Filters records for this student, formats each as 'date at time', and joins with line breaks for display
reportsContent.innerHTML = tableHTML;
Inserts the completed HTML table into the page

dist(x1, y1, x2, y2)

This is a helper function that computes the Euclidean distance between two points. It's used by calculateEAR() to measure distances between eye landmarks. Although p5.js has a built-in dist() function, this custom implementation is defined here to ensure compatibility.

function dist(x1, y1, x2, y2) {
  return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
}
Line-by-line explanation (1 lines)
return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
Calculates Euclidean distance using the Pythagorean theorem: √[(x2-x1)² + (y2-y1)²]

windowResized()

p5.js automatically calls windowResized() whenever the browser window is resized. This function ensures both the canvas and the webcam capture scale responsively to fill the entire window.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  capture.size(width, height);
}
Line-by-line explanation (2 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the new window dimensions
capture.size(width, height);
Resizes the webcam capture element to match the new canvas size

📦 Key Variables

capture p5.Capture object

Stores the webcam video stream; used as input for face detection

let capture;
canvas p5.Renderer

Reference to the p5.js canvas where all graphics are drawn

let canvas;
faceMatcher face-api.FaceMatcher

Matches detected face descriptors against known enrolled faces for recognition

let faceMatcher;
knownFaceDescriptors array of Float32Array

Stores 128-dimensional descriptors for each enrolled known student's face

let knownFaceDescriptors = [];
knownNames array of strings

Stores the names corresponding to each enrolled face descriptor (parallel to knownFaceDescriptors)

let knownNames = [];
detectionInterval number (interval ID)

Stores the setInterval ID so the detection loop can be started/stopped

let detectionInterval;
attendanceCooldown object (map of timestamps)

Tracks the last time each person's attendance was marked to prevent rapid duplicates

let attendanceCooldown = {};
MARKING_DELAY number (milliseconds)

Minimum time in milliseconds that must pass before the same person's attendance can be marked again

const MARKING_DELAY = 10000;
statusMessage string

Holds the current status text displayed to the user (e.g., 'Detecting faces...', 'Attendance marked')

let statusMessage = 'Loading face models...';
statusDiv p5.Div

DOM element that displays the statusMessage text on screen

let statusDiv;
reportsVisible boolean

Tracks whether the attendance reports modal is currently open

let reportsVisible = false;
EAR_THRESHOLD number

Eye Aspect Ratio threshold; EAR below this value triggers a blink detection

const EAR_THRESHOLD = 0.22;
blinkCounter number

Counts the number of blinks detected; resets if no blink for BLINK_RESET_TIME

let blinkCounter = 0;
isBlinking boolean

State flag indicating whether eyes are currently detected as closed (blinking)

let isBlinking = false;
lastBlinkTime number (milliseconds since start)

Stores the time of the last detected blink; used to reset blinkCounter after BLINK_RESET_TIME

let lastBlinkTime = 0;
BLINK_RESET_TIME number (milliseconds)

Time window in which multiple blinks are counted; blink counter resets if no blinks occur within this period

const BLINK_RESET_TIME = 1000;

🔧 Potential Improvements (8)

Here are some ways this code could be enhanced:

BUG detectFacesAndLiveness() recognition logic

The code calculates attendance for ANY blink from a recognized face, not just a single intentional blink. Rapid blinks or continuous detection could mark attendance multiple times within the cooldown period.

💡 Require at least 1-2 consecutive blinks with a specific timing pattern to confirm liveness. Alternatively, increase the cooldown period or implement a state machine that tracks 'ready to mark' vs 'already processing' states.

BUG enrollKnownFaces() face detection

If any enrolled face image fails to load or doesn't contain a valid face, the descriptor array and names array become misaligned (different lengths), causing parseInt(bestMatch.label) to access the wrong name.

💡 Track indices separately or store {name, descriptor} objects in a single array instead of parallel arrays. Or add validation that ensures both arrays stay synchronized.

PERFORMANCE detectFacesAndLiveness() drawing

The function recalculates canvas transformation (translate, scale) for every single detection frame, even when the transformation is identical each time.

💡 Move the push/translate/scale/pop outside the detection loop if possible, or cache the transformation matrix to avoid redundant calculations.

STYLE Global variables and function organization

Many global variables (blinkCounter, isBlinking, lastBlinkTime, etc.) related to blink detection could be grouped into a single object or class for better organization and readability.

💡 Consider refactoring: `const blinkState = { counter: 0, isBlinking: false, lastTime: 0 };` and access as `blinkState.counter` to make code more maintainable.

FEATURE markAttendanceLocal() and displayAttendanceReports()

The system marks attendance only once per day per person, but there's no way to review or manage individual attendance records (edit, delete, or re-mark).

💡 Add UI buttons in the reports modal to allow instructors to manually mark/unmark attendance, edit timestamps, or export data as CSV.

FEATURE enrollment process

Known faces are hardcoded as image URLs and must be loaded from the web. There's no UI to add new students dynamically without editing the code.

💡 Add a webcam-based enrollment feature where instructors can click a 'Enroll New Student' button, provide a name, and the system captures their face directly from the webcam to compute and store their descriptor.

PERFORMANCE loadAttendanceRecords() and saveAttendanceRecords()

Every call to markAttendanceLocal() reads from localStorage, modifies, and writes back—this is inefficient for systems with many students or calls. JSON.parse and JSON.stringify also add CPU overhead.

💡 Cache the entire records array in memory and only persist to localStorage at specific checkpoints (e.g., every 5 marks or on a timer), reducing I/O operations.

FEATURE displayAttendanceReports()

Reports show absolute dates but don't track which sessions are 'expected' vs 'actual.' If students attend only 2 of 3 sessions, the percentage is 66%, but it's unclear if a session was skipped or not offered.

💡 Add a 'Sessions' column showing which specific dates are tracked, and let instructors mark 'no class' days so the denominator properly reflects actual expected attendance days.

🔄 Code Flow

Code flow showing loadattendancerecords, saveattendancerecords, markattendancelocal, clearalllocaldata, calculateattendancepercentagelocal, loadmodels, enrollknownfaces, calculateear, detectblink, setup, draw, detectfacesandliveness, startdetection, displayattendancereports, dist, windowresized

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> canvas-setup[canvas-setup] setup --> status-div-creation[status-div-creation] setup --> model-loading[model-loading] model-loading --> loadmodels[loadmodels] loadmodels --> tinyface-load[tinyface-load] loadmodels --> landmark-load[landmark-load] loadmodels --> recognition-load[recognition-load] setup --> button-listeners[button-listeners] setup --> enrollknownfaces[enrollknownfaces] enrollknownfaces --> known-faces-array[known-faces-array] enrollknownfaces --> enrollment-loop[enrollment-loop] enrollment-loop --> fetch-image[fetch-image] enrollment-loop --> facematcher-init[facematcher-init] setup --> draw[draw loop] draw --> reports-check[reports-check] reports-check -->|if visible| reports-visibility[reports-visibility] reports-visibility --> draw draw --> webcam-mirror[webcam-mirror] draw --> capture-ready-check[capture-ready-check] capture-ready-check --> detectfacesandliveness[detectfacesandliveness] detectfacesandliveness --> interval-cleanup[interval-cleanup] interval-cleanup --> interval-creation[interval-creation] interval-creation --> startdetection[startdetection] startdetection --> detectfacesandliveness detectfacesandliveness --> face-detection[face-detection] face-detection --> detection-loop[detection-loop] detection-loop --> face-recognition[face-recognition] face-recognition --> liveness-check[liveness-check] liveness-check -->|if recognized and blink| markattendancelocal[markattendancelocal] markattendancelocal --> cooldown-check[cooldown-check] cooldown-check --> duplicate-check[duplicate-check] duplicate-check --> record-creation[record-creation] record-creation --> saveattendancerecords[saveattendancerecords] draw --> windowresized[windowresized] click setup href "#fn-setup" click canvas-setup href "#sub-canvas-setup" click status-div-creation href "#sub-status-div-creation" click model-loading href "#sub-model-loading" click loadmodels href "#fn-loadmodels" click tinyface-load href "#sub-tinyface-load" click landmark-load href "#sub-landmark-load" click recognition-load href "#sub-recognition-load" click button-listeners href "#sub-button-listeners" click enrollknownfaces href "#fn-enrollknownfaces" click known-faces-array href "#sub-known-faces-array" click enrollment-loop href "#sub-enrollment-loop" click reports-check href "#sub-reports-check" click reports-visibility href "#sub-reports-visibility" click webcam-mirror href "#sub-webcam-mirror" click capture-ready-check href "#sub-capture-ready-check" click detectfacesandliveness href "#fn-detectfacesandliveness" click interval-cleanup href "#sub-interval-cleanup" click interval-creation href "#sub-interval-creation" click startdetection href "#fn-startdetection" click face-detection href "#sub-face-detection" click detection-loop href "#sub-detection-loop" click face-recognition href "#sub-face-recognition" click liveness-check href "#sub-liveness-check" click markattendancelocal href "#fn-markattendancelocal" click cooldown-check href "#sub-cooldown-check" click duplicate-check href "#sub-duplicate-check" click record-creation href "#sub-record-creation" click saveattendancerecords href "#fn-saveattendancerecords" click windowresized href "#fn-windowresized"

❓ Frequently Asked Questions

What visual experience does the p5.js sketch titled 'Sketch 2026-02-13 08:47' offer?

The sketch creates a dynamic visual representation of attendance tracking using facial recognition, displaying real-time status updates based on detected faces.

How can users interact with the attendance tracking feature in this p5.js sketch?

Users can interact by allowing the sketch to access their webcam, enabling it to recognize faces and mark attendance accordingly.

What creative coding concepts are demonstrated in 'Sketch 2026-02-13 08:47'?

This sketch showcases facial recognition technology integrated with local storage for attendance tracking, emphasizing real-time data processing and user interaction.

Preview

Sketch 2026-02-13 08:47 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Sketch 2026-02-13 08:47 - Code flow showing loadattendancerecords, saveattendancerecords, markattendancelocal, clearalllocaldata, calculateattendancepercentagelocal, loadmodels, enrollknownfaces, calculateear, detectblink, setup, draw, detectfacesandliveness, startdetection, displayattendancereports, dist, windowresized
Code Flow Diagram