| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>🎭 AI Emotion Detector | koyelog</title> |
| |
| |
| <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet"> |
| |
| |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Poppins', sans-serif; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| min-height: 100vh; |
| padding: 20px; |
| color: #333; |
| } |
| |
| .container { |
| max-width: 1200px; |
| margin: 0 auto; |
| } |
| |
| |
| .header { |
| text-align: center; |
| color: white; |
| padding: 40px 20px; |
| margin-bottom: 40px; |
| animation: fadeInDown 0.8s ease; |
| } |
| |
| .header h1 { |
| font-size: 3.5rem; |
| font-weight: 700; |
| margin-bottom: 15px; |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.2); |
| } |
| |
| .header p { |
| font-size: 1.2rem; |
| opacity: 0.95; |
| } |
| |
| .badge { |
| display: inline-block; |
| background: rgba(255,255,255,0.2); |
| padding: 8px 20px; |
| border-radius: 20px; |
| margin: 10px 5px; |
| font-size: 0.9rem; |
| backdrop-filter: blur(10px); |
| } |
| |
| |
| .main-card { |
| background: white; |
| border-radius: 25px; |
| box-shadow: 0 20px 60px rgba(0,0,0,0.2); |
| overflow: hidden; |
| animation: fadeInUp 0.8s ease; |
| } |
| |
| |
| .tabs { |
| display: flex; |
| background: #f8f9fa; |
| border-bottom: 2px solid #e9ecef; |
| } |
| |
| .tab-button { |
| flex: 1; |
| padding: 20px; |
| border: none; |
| background: transparent; |
| font-size: 1.1rem; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| color: #666; |
| } |
| |
| .tab-button:hover { |
| background: rgba(102, 126, 234, 0.1); |
| } |
| |
| .tab-button.active { |
| background: white; |
| color: #667eea; |
| border-bottom: 3px solid #667eea; |
| } |
| |
| .tab-button i { |
| margin-right: 10px; |
| } |
| |
| |
| .tab-content { |
| display: none; |
| padding: 40px; |
| animation: fadeIn 0.5s ease; |
| } |
| |
| .tab-content.active { |
| display: block; |
| } |
| |
| |
| .upload-section { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 30px; |
| margin-bottom: 30px; |
| } |
| |
| .upload-area { |
| border: 3px dashed #667eea; |
| border-radius: 20px; |
| padding: 40px; |
| text-align: center; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| background: #f8f9fa; |
| } |
| |
| .upload-area:hover { |
| background: #e9ecef; |
| border-color: #764ba2; |
| transform: translateY(-5px); |
| } |
| |
| .upload-area i { |
| font-size: 4rem; |
| color: #667eea; |
| margin-bottom: 20px; |
| } |
| |
| .upload-area input[type="file"] { |
| display: none; |
| } |
| |
| |
| #webcam-video, #preview-image { |
| width: 100%; |
| border-radius: 15px; |
| box-shadow: 0 4px 16px rgba(0,0,0,0.1); |
| } |
| |
| .button { |
| display: inline-block; |
| padding: 15px 40px; |
| margin: 10px 5px; |
| border: none; |
| border-radius: 50px; |
| font-size: 1.1rem; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| box-shadow: 0 4px 15px rgba(0,0,0,0.2); |
| } |
| |
| .button-primary { |
| background: linear-gradient(135deg, #667eea, #764ba2); |
| color: white; |
| } |
| |
| .button-primary:hover { |
| transform: translateY(-3px); |
| box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); |
| } |
| |
| .button-secondary { |
| background: #6c757d; |
| color: white; |
| } |
| |
| .button-secondary:hover { |
| background: #5a6268; |
| } |
| |
| |
| .result-section { |
| background: linear-gradient(135deg, #f8f9fa, #e9ecef); |
| border-radius: 20px; |
| padding: 30px; |
| margin-top: 30px; |
| display: none; |
| animation: fadeIn 0.5s ease; |
| } |
| |
| .result-section.show { |
| display: block; |
| } |
| |
| .emotion-result { |
| text-align: center; |
| padding: 40px; |
| background: white; |
| border-radius: 20px; |
| margin-bottom: 30px; |
| box-shadow: 0 4px 16px rgba(0,0,0,0.1); |
| } |
| |
| .emotion-emoji { |
| font-size: 120px; |
| animation: bounce 1s ease infinite; |
| } |
| |
| .emotion-name { |
| font-size: 3rem; |
| font-weight: 700; |
| margin: 20px 0; |
| } |
| |
| .confidence-bar { |
| width: 100%; |
| height: 40px; |
| background: #e9ecef; |
| border-radius: 20px; |
| overflow: hidden; |
| margin: 20px 0; |
| box-shadow: inset 0 2px 4px rgba(0,0,0,0.1); |
| } |
| |
| .confidence-fill { |
| height: 100%; |
| background: linear-gradient(90deg, #667eea, #764ba2); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: white; |
| font-weight: 600; |
| transition: width 1s ease; |
| } |
| |
| |
| .emotions-list { |
| background: white; |
| padding: 25px; |
| border-radius: 15px; |
| box-shadow: 0 4px 16px rgba(0,0,0,0.1); |
| } |
| |
| .emotion-item { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 15px; |
| margin: 10px 0; |
| background: #f8f9fa; |
| border-radius: 10px; |
| transition: all 0.3s ease; |
| } |
| |
| .emotion-item:hover { |
| background: #e9ecef; |
| transform: translateX(5px); |
| } |
| |
| .emotion-label { |
| display: flex; |
| align-items: center; |
| gap: 15px; |
| font-weight: 500; |
| } |
| |
| .emotion-label span { |
| font-size: 2rem; |
| } |
| |
| .emotion-bar { |
| width: 200px; |
| height: 10px; |
| background: #e9ecef; |
| border-radius: 5px; |
| overflow: hidden; |
| } |
| |
| .emotion-bar-fill { |
| height: 100%; |
| transition: width 0.5s ease; |
| } |
| |
| |
| @keyframes fadeInDown { |
| from { |
| opacity: 0; |
| transform: translateY(-30px); |
| } |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| @keyframes fadeInUp { |
| from { |
| opacity: 0; |
| transform: translateY(30px); |
| } |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; } |
| to { opacity: 1; } |
| } |
| |
| @keyframes bounce { |
| 0%, 100% { transform: translateY(0); } |
| 50% { transform: translateY(-20px); } |
| } |
| |
| |
| .loading { |
| display: none; |
| text-align: center; |
| padding: 40px; |
| } |
| |
| .loading.show { |
| display: block; |
| } |
| |
| .spinner { |
| border: 5px solid #f3f3f3; |
| border-top: 5px solid #667eea; |
| border-radius: 50%; |
| width: 60px; |
| height: 60px; |
| animation: spin 1s linear infinite; |
| margin: 0 auto 20px; |
| } |
| |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| |
| .footer { |
| text-align: center; |
| color: white; |
| padding: 40px 20px; |
| margin-top: 40px; |
| background: rgba(0,0,0,0.2); |
| border-radius: 20px; |
| backdrop-filter: blur(10px); |
| } |
| |
| .footer a { |
| color: white; |
| text-decoration: underline; |
| } |
| |
| |
| @media (max-width: 768px) { |
| .header h1 { |
| font-size: 2.5rem; |
| } |
| |
| .upload-section { |
| grid-template-columns: 1fr; |
| } |
| |
| .tabs { |
| flex-direction: column; |
| } |
| |
| .emotion-name { |
| font-size: 2rem; |
| } |
| |
| .emotion-emoji { |
| font-size: 80px; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| |
| <div class="header"> |
| <h1>🎭 AI Emotion Detector</h1> |
| <p>Powered by Vision Transformer | 98.80% Accuracy</p> |
| <div> |
| <span class="badge">Model: koyelog/face</span> |
| <span class="badge">7 Emotions</span> |
| <span class="badge">Real-time Detection</span> |
| </div> |
| </div> |
|
|
| |
| <div class="main-card"> |
| |
| <div class="tabs"> |
| <button class="tab-button active" onclick="switchTab('webcam')"> |
| <i class="fas fa-video"></i> Live Webcam |
| </button> |
| <button class="tab-button" onclick="switchTab('upload')"> |
| <i class="fas fa-upload"></i> Upload Image |
| </button> |
| </div> |
|
|
| |
| <div id="webcam-tab" class="tab-content active"> |
| <h2 style="text-align: center; margin-bottom: 30px;"> |
| <i class="fas fa-camera"></i> Capture Your Emotion |
| </h2> |
|
|
| <div class="upload-section"> |
| <div> |
| <video id="webcam-video" autoplay playsinline></video> |
| <canvas id="webcam-canvas" style="display: none;"></canvas> |
| <div style="text-align: center; margin-top: 20px;"> |
| <button class="button button-primary" onclick="startWebcam()"> |
| <i class="fas fa-play"></i> Start Webcam |
| </button> |
| <button class="button button-secondary" onclick="captureAndDetect()"> |
| <i class="fas fa-camera"></i> Capture & Detect |
| </button> |
| </div> |
| </div> |
| <div id="webcam-result"></div> |
| </div> |
| </div> |
|
|
| |
| <div id="upload-tab" class="tab-content"> |
| <h2 style="text-align: center; margin-bottom: 30px;"> |
| <i class="fas fa-cloud-upload-alt"></i> Upload Face Image |
| </h2> |
|
|
| <div class="upload-section"> |
| <div> |
| <div class="upload-area" onclick="document.getElementById('file-input').click()"> |
| <i class="fas fa-image"></i> |
| <h3>Click to Upload</h3> |
| <p>or drag & drop your image here</p> |
| <p style="color: #999; margin-top: 10px;">Supports: JPG, PNG, JPEG</p> |
| <input type="file" id="file-input" accept="image/*" onchange="handleFileUpload(event)"> |
| </div> |
| <img id="preview-image" style="display: none; margin-top: 20px;"> |
| </div> |
| <div id="upload-result"></div> |
| </div> |
| </div> |
|
|
| |
| <div id="loading" class="loading"> |
| <div class="spinner"></div> |
| <p>Analyzing emotion...</p> |
| </div> |
| </div> |
|
|
| |
| <div class="footer"> |
| <h3>📊 Model Information</h3> |
| <p><strong>Model:</strong> koyelog/face | <strong>Architecture:</strong> Vision Transformer (ViT)</p> |
| <p><strong>Parameters:</strong> 85.8M | <strong>Training Accuracy:</strong> 99.29% | <strong>Validation Accuracy:</strong> 98.80%</p> |
| <p><strong>Emotions:</strong> 😠 Angry | 🤢 Disgust | 😨 Fear | 😊 Happy | 😢 Sad | 😲 Surprise | 😐 Neutral</p> |
| <p style="margin-top: 20px;"> |
| Created by <strong>Koyeliya Ghosh</strong> | |
| <a href="https://huggingface.co/koyelog/face" target="_blank">View Model on HuggingFace</a> |
| </p> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const API_URL = 'YOUR_HUGGINGFACE_SPACE_URL/api/predict'; |
| |
| const EMOTIONS = { |
| 0: { name: 'Angry', emoji: '😠', color: '#ff4444' }, |
| 1: { name: 'Disgust', emoji: '🤢', color: '#44ff44' }, |
| 2: { name: 'Fear', emoji: '😨', color: '#9944ff' }, |
| 3: { name: 'Happy', emoji: '😊', color: '#ffdd44' }, |
| 4: { name: 'Sad', emoji: '😢', color: '#4444ff' }, |
| 5: { name: 'Surprise', emoji: '😲', color: '#ff44ff' }, |
| 6: { name: 'Neutral', emoji: '😐', color: '#888888' } |
| }; |
| |
| let webcamStream = null; |
| |
| |
| function switchTab(tab) { |
| document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active')); |
| document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); |
| |
| if (tab === 'webcam') { |
| document.querySelector('.tab-button:first-child').classList.add('active'); |
| document.getElementById('webcam-tab').classList.add('active'); |
| } else { |
| document.querySelector('.tab-button:last-child').classList.add('active'); |
| document.getElementById('upload-tab').classList.add('active'); |
| } |
| } |
| |
| |
| async function startWebcam() { |
| try { |
| webcamStream = await navigator.mediaDevices.getUserMedia({ |
| video: { facingMode: 'user' } |
| }); |
| document.getElementById('webcam-video').srcObject = webcamStream; |
| } catch (error) { |
| alert('Error accessing webcam: ' + error.message); |
| } |
| } |
| |
| |
| function captureAndDetect() { |
| const video = document.getElementById('webcam-video'); |
| const canvas = document.getElementById('webcam-canvas'); |
| |
| canvas.width = video.videoWidth; |
| canvas.height = video.videoHeight; |
| canvas.getContext('2d').drawImage(video, 0, 0); |
| |
| canvas.toBlob(blob => { |
| detectEmotion(blob, 'webcam-result'); |
| }, 'image/jpeg'); |
| } |
| |
| |
| function handleFileUpload(event) { |
| const file = event.target.files[0]; |
| if (!file) return; |
| |
| const reader = new FileReader(); |
| reader.onload = (e) => { |
| const preview = document.getElementById('preview-image'); |
| preview.src = e.target.result; |
| preview.style.display = 'block'; |
| |
| detectEmotion(file, 'upload-result'); |
| }; |
| reader.readAsDataURL(file); |
| } |
| |
| |
| async function detectEmotion(imageBlob, resultId) { |
| document.getElementById('loading').classList.add('show'); |
| |
| |
| setTimeout(() => { |
| const mockResult = { |
| predicted_id: 3, |
| confidence: 0.87, |
| probabilities: [0.02, 0.01, 0.03, 0.87, 0.04, 0.02, 0.01] |
| }; |
| |
| displayResult(mockResult, resultId); |
| document.getElementById('loading').classList.remove('show'); |
| }, 2000); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| |
| function displayResult(result, containerId) { |
| const emotion = EMOTIONS[result.predicted_id]; |
| const confidence = (result.confidence * 100).toFixed(2); |
| |
| let html = ` |
| <div class="emotion-result" style="background: linear-gradient(135deg, ${emotion.color}15, ${emotion.color}30);"> |
| <div class="emotion-emoji">${emotion.emoji}</div> |
| <div class="emotion-name" style="color: ${emotion.color};">${emotion.name}</div> |
| <p style="font-size: 1.5rem; color: #666;">Confidence: ${confidence}%</p> |
| <div class="confidence-bar"> |
| <div class="confidence-fill" style="width: ${confidence}%; background: ${emotion.color};"> |
| ${confidence}% |
| </div> |
| </div> |
| </div> |
| |
| <div class="emotions-list"> |
| <h3 style="margin-bottom: 20px;">📊 All Emotions</h3> |
| `; |
| |
| result.probabilities.forEach((prob, idx) => { |
| const emo = EMOTIONS[idx]; |
| const percentage = (prob * 100).toFixed(1); |
| html += ` |
| <div class="emotion-item"> |
| <div class="emotion-label"> |
| <span>${emo.emoji}</span> |
| <strong>${emo.name}</strong> |
| </div> |
| <div style="display: flex; align-items: center; gap: 15px;"> |
| <div class="emotion-bar"> |
| <div class="emotion-bar-fill" style="width: ${percentage}%; background: ${emo.color};"></div> |
| </div> |
| <span style="font-weight: 600; min-width: 50px;">${percentage}%</span> |
| </div> |
| </div> |
| `; |
| }); |
| |
| html += '</div>'; |
| document.getElementById(containerId).innerHTML = html; |
| } |
| |
| |
| const uploadArea = document.querySelector('.upload-area'); |
| |
| uploadArea.addEventListener('dragover', (e) => { |
| e.preventDefault(); |
| uploadArea.style.background = '#e9ecef'; |
| }); |
| |
| uploadArea.addEventListener('dragleave', () => { |
| uploadArea.style.background = '#f8f9fa'; |
| }); |
| |
| uploadArea.addEventListener('drop', (e) => { |
| e.preventDefault(); |
| uploadArea.style.background = '#f8f9fa'; |
| |
| const file = e.dataTransfer.files[0]; |
| if (file && file.type.startsWith('image/')) { |
| const event = { target: { files: [file] } }; |
| handleFileUpload(event); |
| } |
| }); |
| </script> |
| </body> |
| </html> |
|
|