Spaces:
Running
Running
Download Face-Expresion.html from YOUSEF2434/Computer-Vision-Lab: direct link, hf CLI and curl.
- Browser
- Download file 7.85 kB
-
https://huggingface.co/spaces/YOUSEF2434/Computer-Vision-Lab/resolve/main/Face-Expresion.html
- Command line
-
hf download hf://spaces/YOUSEF2434/Computer-Vision-Lab/Face-Expresion.html
-
curl -L -o Face-Expresion.html https://huggingface.co/spaces/YOUSEF2434/Computer-Vision-Lab/resolve/main/Face-Expresion.html
7.85 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Expression Recognition</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| height: 100vh; | |
| background-color: #1a1a1a; | |
| font-family: 'Segoe UI', sans-serif; | |
| color: white; | |
| } | |
| .video-container { | |
| position: relative; | |
| width: 640px; | |
| height: 480px; | |
| background: #000; | |
| border-radius: 12px; | |
| overflow: hidden; | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.5); | |
| } | |
| video { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| /* Visual mirror only */ | |
| transform: scaleX(-1); | |
| } | |
| canvas { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| /* Canvas stays normal */ | |
| } | |
| #loader { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0,0,0,0.9); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 10; | |
| } | |
| .spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 4px solid #f3f3f3; | |
| border-top: 4px solid #9b59b6; /* Purple for expressions */ | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin-bottom: 15px; | |
| } | |
| @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } | |
| .controls { margin-top: 20px; display: flex; gap: 15px; } | |
| button { padding: 12px 30px; font-size: 16px; border: none; border-radius: 50px; cursor: pointer; font-weight: 600; transition: transform 0.1s; } | |
| button:active { transform: scale(0.95); } | |
| #btnCapture { background: linear-gradient(135deg, #28a745, #218838); color: white; } | |
| #btnCapture:disabled { background: #555; cursor: not-allowed; } | |
| #btnRetake { background: linear-gradient(135deg, #dc3545, #c82333); color: white; display: none; } | |
| #status { margin-top: 15px; color: #ccc; font-size: 14px; } | |
| </style> | |
| <script src="https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js"></script> | |
| </head> | |
| <body> | |
| <div class="video-container"> | |
| <video id="video" autoplay muted playsinline></video> | |
| <canvas id="canvas"></canvas> | |
| <div id="loader"> | |
| <div class="spinner"></div> | |
| <div id="loadingText">Loading Expression Models...</div> | |
| </div> | |
| </div> | |
| <div class="controls"> | |
| <button id="btnCapture" disabled>Wait...</button> | |
| <button id="btnRetake">Retake</button> | |
| </div> | |
| <div id="status">Initializing system...</div> | |
| <script> | |
| const video = document.getElementById('video'); | |
| const canvas = document.getElementById('canvas'); | |
| const btnCapture = document.getElementById('btnCapture'); | |
| const btnRetake = document.getElementById('btnRetake'); | |
| const statusText = document.getElementById('status'); | |
| const loader = document.getElementById('loader'); | |
| // Use the same reliable CDN | |
| const MODEL_URL = 'https://cdn.jsdelivr.net/gh/cgarciagl/face-api.js@0.22.2/weights/'; | |
| async function init() { | |
| try { | |
| // Load SSD MobileNet (High Accuracy Detector) | |
| await faceapi.nets.ssdMobilenetv1.loadFromUri(MODEL_URL); | |
| // Load Face Expression Model | |
| await faceapi.nets.faceExpressionNet.loadFromUri(MODEL_URL); | |
| startCamera(); | |
| } catch (error) { | |
| alert("Error loading models: " + error); | |
| } | |
| } | |
| function startCamera() { | |
| navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480 } }) | |
| .then(stream => { video.srcObject = stream; }) | |
| .catch(err => { console.error(err); }); | |
| } | |
| video.addEventListener('play', () => { | |
| const displaySize = { width: video.videoWidth, height: video.videoHeight }; | |
| faceapi.matchDimensions(canvas, displaySize); | |
| loader.style.display = 'none'; | |
| btnCapture.disabled = false; | |
| btnCapture.innerText = "Capture Expression"; | |
| statusText.innerText = "Ready. Show me an emotion!"; | |
| }); | |
| btnCapture.addEventListener('click', async () => { | |
| if (video.paused) return; | |
| video.pause(); | |
| btnCapture.style.display = 'none'; | |
| btnRetake.style.display = 'inline-block'; | |
| statusText.innerText = "Analyzing Expressions..."; | |
| const displaySize = { width: video.videoWidth, height: video.videoHeight }; | |
| faceapi.matchDimensions(canvas, displaySize); | |
| // Detect Faces + Expressions | |
| const detections = await faceapi.detectAllFaces(video, new faceapi.SsdMobilenetv1Options({ minConfidence: 0.5 })) | |
| .withFaceExpressions(); | |
| const resizedDetections = faceapi.resizeResults(detections, displaySize); | |
| const ctx = canvas.getContext('2d'); | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // 1. Draw Boxes (Mirrored Context) | |
| ctx.save(); | |
| ctx.scale(-1, 1); | |
| ctx.translate(-canvas.width, 0); | |
| faceapi.draw.drawDetections(canvas, resizedDetections); | |
| ctx.restore(); | |
| // 2. Draw Expressions (Normal Context) | |
| resizedDetections.forEach(result => { | |
| const expressions = result.expressions; | |
| // Sort expressions to find the top one | |
| const sorted = Object.entries(expressions).sort((a, b) => b[1] - a[1]); | |
| const topEmotion = sorted[0]; // [emotion, score] | |
| // Calculate mirrored position for the text/bars | |
| const box = result.detection.box; | |
| const mirroredX = canvas.width - box.x - box.width; | |
| const mirroredPos = { x: mirroredX, y: box.bottomLeft.y }; | |
| // Draw the top emotion text | |
| new faceapi.draw.DrawTextField( | |
| [`${topEmotion[0]} (${Math.round(topEmotion[1] * 100)}%)`], | |
| mirroredPos | |
| ).draw(canvas); | |
| // OPTIONAL: Draw the full expression bar chart | |
| // We offset it slightly so it doesn't overlap the box too much | |
| const minConfidence = 0.1; // Only show emotions above 10% | |
| faceapi.draw.drawFaceExpressions(canvas, resizedDetections, minConfidence, mirroredPos); | |
| }); | |
| if (detections.length === 0) statusText.innerText = "No face detected."; | |
| else statusText.innerText = `Analysis Done. Found ${detections.length} face(s).`; | |
| }); | |
| btnRetake.addEventListener('click', () => { | |
| canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); | |
| video.play(); | |
| btnCapture.style.display = 'inline-block'; | |
| btnRetake.style.display = 'none'; | |
| statusText.innerText = "Ready."; | |
| }); | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |