themehmi commited on
Commit
015611f
·
verified ·
1 Parent(s): b69854e

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +80 -16
templates/index.html CHANGED
@@ -365,8 +365,7 @@
365
 
366
  <video id="local-video" autoplay playsinline class="hidden"></video>
367
 
368
- <img id="processed-feed" src="" alt="Processed Feed"
369
- class="w-full h-auto max-h-[400px] md:max-h-[600px] object-contain hidden z-10">
370
 
371
  <div
372
  class="absolute top-2 left-2 md:top-4 md:left-4 w-6 h-6 md:w-8 md:h-8 border-t-4 border-l-4 border-blue-500 opacity-70 z-20 pointer-events-none">
@@ -436,19 +435,60 @@
436
  sidebarOverlay.addEventListener('click', toggleSidebar);
437
 
438
 
439
- //MAIN CAMERA
440
  const toggleBtn = document.getElementById('toggle-camera');
441
  const localVideo = document.getElementById('local-video');
442
- const processedFeed = document.getElementById('processed-feed');
 
443
  const placeholder = document.getElementById('camera-placeholder');
444
 
445
  let isCameraOn = false;
446
  let localStream = null;
447
- let processingInterval = null;
448
 
449
  const canvas = document.createElement('canvas');
450
  const context = canvas.getContext('2d');
451
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  // TTS (SpeechSynthesis) Helper
453
  let spokenNames = new Map();
454
 
@@ -464,13 +504,18 @@
464
 
465
  async function startCamera() {
466
  try {
467
- localStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } });
 
 
 
 
 
 
468
  localVideo.srcObject = localStream;
469
  placeholder.classList.add('hidden');
470
- processedFeed.classList.remove('hidden');
471
  localVideo.onloadedmetadata = () => {
472
- canvas.width = localVideo.videoWidth;
473
- canvas.height = localVideo.videoHeight;
474
  sendFrameToServer();
475
  };
476
  } catch (err) {
@@ -482,16 +527,36 @@
482
 
483
  function stopCamera() {
484
  if (localStream) localStream.getTracks().forEach(t => t.stop());
485
- if (processingInterval) clearInterval(processingInterval);
486
- processedFeed.src = "";
487
- processedFeed.classList.add('hidden');
488
  placeholder.classList.remove('hidden');
 
489
  }
490
 
491
  async function sendFrameToServer() {
492
  if (!isCameraOn) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  context.drawImage(localVideo, 0, 0, canvas.width, canvas.height);
494
- const dataURL = canvas.toDataURL('image/jpeg', 0.7);
495
  try {
496
  const response = await fetch('/process_frame', {
497
  method: 'POST',
@@ -499,10 +564,9 @@
499
  body: JSON.stringify({ image: dataURL })
500
  });
501
  const data = await response.json();
502
- if (data.image) processedFeed.src = data.image;
503
 
504
- // Speak if attendance is successfully marked
505
  if (data.faces) {
 
506
  data.faces.forEach(face => {
507
  if (face.name && face.name !== "Unknown") {
508
  const now = Date.now();
@@ -521,7 +585,7 @@
521
  } catch (err) {
522
  console.error("Error communicating with backend", err);
523
  } finally {
524
- if (isCameraOn) setTimeout(sendFrameToServer, 250);
525
  }
526
  }
527
 
 
365
 
366
  <video id="local-video" autoplay playsinline class="hidden"></video>
367
 
368
+ <canvas id="output-canvas" class="w-full h-auto max-h-[400px] md:max-h-[600px] object-contain hidden z-10"></canvas>
 
369
 
370
  <div
371
  class="absolute top-2 left-2 md:top-4 md:left-4 w-6 h-6 md:w-8 md:h-8 border-t-4 border-l-4 border-blue-500 opacity-70 z-20 pointer-events-none">
 
435
  sidebarOverlay.addEventListener('click', toggleSidebar);
436
 
437
 
 
438
  const toggleBtn = document.getElementById('toggle-camera');
439
  const localVideo = document.getElementById('local-video');
440
+ const outputCanvas = document.getElementById('output-canvas');
441
+ const outputCtx = outputCanvas.getContext('2d');
442
  const placeholder = document.getElementById('camera-placeholder');
443
 
444
  let isCameraOn = false;
445
  let localStream = null;
446
+ let latestFaces = [];
447
 
448
  const canvas = document.createElement('canvas');
449
  const context = canvas.getContext('2d');
450
 
451
+ function renderLoop() {
452
+ if (!isCameraOn) return;
453
+ if (localVideo.readyState === localVideo.HAVE_ENOUGH_DATA) {
454
+ // Keep canvas resolution synced with original video resolution
455
+ if (outputCanvas.width !== localVideo.videoWidth) {
456
+ outputCanvas.width = localVideo.videoWidth;
457
+ outputCanvas.height = localVideo.videoHeight;
458
+ }
459
+
460
+ // Draw local webcam feed smoothly
461
+ outputCtx.drawImage(localVideo, 0, 0, outputCanvas.width, outputCanvas.height);
462
+
463
+ // Draw bounding boxes based on backend coordinates
464
+ const scaleX = outputCanvas.width / (canvas.width || 640);
465
+ const scaleY = outputCanvas.height / (canvas.height || 480);
466
+
467
+ latestFaces.forEach(face => {
468
+ const top = face.box.top * scaleY;
469
+ const right = face.box.right * scaleX;
470
+ const bottom = face.box.bottom * scaleY;
471
+ const left = face.box.left * scaleX;
472
+ const name = face.name;
473
+
474
+ outputCtx.lineWidth = 4;
475
+ outputCtx.strokeStyle = name !== "Unknown" ? "#22c55e" : "#3b82f6";
476
+ outputCtx.fillStyle = name !== "Unknown" ? "#22c55e" : "#3b82f6";
477
+
478
+ outputCtx.strokeRect(left, top, right - left, bottom - top);
479
+
480
+ let labelText = name;
481
+ outputCtx.font = "bold 20px Arial";
482
+ const textWidth = outputCtx.measureText(labelText).width;
483
+ outputCtx.fillRect(left, top - 30, textWidth + 20, 30);
484
+
485
+ outputCtx.fillStyle = "#FFFFFF";
486
+ outputCtx.fillText(labelText, left + 10, top - 8);
487
+ });
488
+ }
489
+ requestAnimationFrame(renderLoop);
490
+ }
491
+
492
  // TTS (SpeechSynthesis) Helper
493
  let spokenNames = new Map();
494
 
 
504
 
505
  async function startCamera() {
506
  try {
507
+ localStream = await navigator.mediaDevices.getUserMedia({
508
+ video: {
509
+ facingMode: 'user',
510
+ width: { ideal: 640 },
511
+ height: { ideal: 480 }
512
+ }
513
+ });
514
  localVideo.srcObject = localStream;
515
  placeholder.classList.add('hidden');
516
+ outputCanvas.classList.remove('hidden');
517
  localVideo.onloadedmetadata = () => {
518
+ requestAnimationFrame(renderLoop);
 
519
  sendFrameToServer();
520
  };
521
  } catch (err) {
 
527
 
528
  function stopCamera() {
529
  if (localStream) localStream.getTracks().forEach(t => t.stop());
530
+ outputCanvas.classList.add('hidden');
 
 
531
  placeholder.classList.remove('hidden');
532
+ latestFaces = [];
533
  }
534
 
535
  async function sendFrameToServer() {
536
  if (!isCameraOn) return;
537
+
538
+ // Resize image to max 640x480 to reduce CPU load and latency
539
+ const MAX_WIDTH = 640;
540
+ const MAX_HEIGHT = 480;
541
+ let width = localVideo.videoWidth;
542
+ let height = localVideo.videoHeight;
543
+
544
+ if (width > height) {
545
+ if (width > MAX_WIDTH) {
546
+ height *= MAX_WIDTH / width;
547
+ width = MAX_WIDTH;
548
+ }
549
+ } else {
550
+ if (height > MAX_HEIGHT) {
551
+ width *= MAX_HEIGHT / height;
552
+ height = MAX_HEIGHT;
553
+ }
554
+ }
555
+ canvas.width = width || 640;
556
+ canvas.height = height || 480;
557
+
558
  context.drawImage(localVideo, 0, 0, canvas.width, canvas.height);
559
+ const dataURL = canvas.toDataURL('image/jpeg', 0.6);
560
  try {
561
  const response = await fetch('/process_frame', {
562
  method: 'POST',
 
564
  body: JSON.stringify({ image: dataURL })
565
  });
566
  const data = await response.json();
 
567
 
 
568
  if (data.faces) {
569
+ latestFaces = data.faces;
570
  data.faces.forEach(face => {
571
  if (face.name && face.name !== "Unknown") {
572
  const now = Date.now();
 
585
  } catch (err) {
586
  console.error("Error communicating with backend", err);
587
  } finally {
588
+ if (isCameraOn) setTimeout(sendFrameToServer, 500); // reduced frequency to 2 FPS
589
  }
590
  }
591