|
|
<!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> |
|
|
|