Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Advanced Facial Emotion Analysis System</title> | |
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script> | |
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/face-detection"></script> | |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
<style> | |
:root { | |
--primary: #3498db; | |
--success: #2ecc71; | |
--warning: #f1c40f; | |
--danger: #e74c3c; | |
--dark: #2c3e50; | |
--light: #ecf0f1; | |
} | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
font-family: 'Segoe UI', system-ui, sans-serif; | |
} | |
body { | |
background: var(--dark); | |
color: var(--light); | |
line-height: 1.6; | |
} | |
.dashboard { | |
display: grid; | |
grid-template-columns: 70% 30%; | |
gap: 20px; | |
padding: 20px; | |
max-width: 1600px; | |
margin: 0 auto; | |
} | |
.main-panel { | |
background: rgba(255,255,255,0.1); | |
border-radius: 15px; | |
padding: 20px; | |
} | |
.controls-panel { | |
background: rgba(255,255,255,0.1); | |
border-radius: 15px; | |
padding: 20px; | |
} | |
.video-container { | |
position: relative; | |
width: 100%; | |
border-radius: 10px; | |
overflow: hidden; | |
background: #000; | |
} | |
#video, #canvas { | |
width: 100%; | |
transform: scaleX(-1); | |
} | |
#canvas { | |
position: absolute; | |
top: 0; | |
left: 0; | |
} | |
.settings-group { | |
margin: 15px 0; | |
padding: 15px; | |
background: rgba(255,255,255,0.05); | |
border-radius: 8px; | |
} | |
.settings-group h3 { | |
color: var(--primary); | |
margin-bottom: 10px; | |
} | |
.control-button { | |
padding: 10px 20px; | |
border: none; | |
border-radius: 5px; | |
background: var(--primary); | |
color: white; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
margin: 5px; | |
width: 100%; | |
} | |
.control-button:hover { | |
opacity: 0.9; | |
transform: translateY(-2px); | |
} | |
.emotion-indicator { | |
display: grid; | |
grid-template-columns: repeat(2, 1fr); | |
gap: 10px; | |
margin-top: 10px; | |
} | |
.emotion-item { | |
padding: 10px; | |
background: rgba(255,255,255,0.05); | |
border-radius: 5px; | |
text-align: center; | |
transition: all 0.3s ease; | |
} | |
.emotion-item.active { | |
background: var(--success); | |
transform: scale(1.05); | |
} | |
.stats-container { | |
margin-top: 20px; | |
} | |
.meter { | |
height: 20px; | |
background: rgba(255,255,255,0.1); | |
border-radius: 10px; | |
overflow: hidden; | |
margin: 10px 0; | |
} | |
.meter-fill { | |
height: 100%; | |
background: var(--primary); | |
transition: width 0.3s ease; | |
} | |
.privacy-toggle { | |
margin: 10px 0; | |
} | |
#emotionChart { | |
margin-top: 20px; | |
background: rgba(255,255,255,0.05); | |
border-radius: 8px; | |
padding: 10px; | |
} | |
.fps-counter { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
background: rgba(0,0,0,0.7); | |
padding: 5px 10px; | |
border-radius: 5px; | |
font-size: 14px; | |
} | |
.detection-settings { | |
display: grid; | |
gap: 10px; | |
margin: 10px 0; | |
} | |
.slider-control { | |
display: flex; | |
flex-direction: column; | |
gap: 5px; | |
} | |
input[type="range"] { | |
width: 100%; | |
background: var(--primary); | |
} | |
.face-count { | |
font-size: 1.2em; | |
text-align: center; | |
margin: 10px 0; | |
color: var(--success); | |
} | |
@keyframes pulse { | |
0% { transform: scale(1); } | |
50% { transform: scale(1.05); } | |
100% { transform: scale(1); } | |
} | |
.detecting { | |
animation: pulse 2s infinite; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="dashboard"> | |
<div class="main-panel"> | |
<div class="video-container"> | |
<video id="video" playsinline></video> | |
<canvas id="canvas"></canvas> | |
<div class="fps-counter">FPS: <span id="fpsCounter">0</span></div> | |
</div> | |
<canvas id="emotionChart"></canvas> | |
</div> | |
<div class="controls-panel"> | |
<div class="settings-group"> | |
<h3>Detection Controls</h3> | |
<button id="toggleDetection" class="control-button">Start Detection</button> | |
<button id="togglePrivacy" class="control-button">Toggle Face Blur</button> | |
<div class="detection-settings"> | |
<div class="slider-control"> | |
<label>Detection Sensitivity</label> | |
<input type="range" id="sensitivity" min="0" max="100" value="50"> | |
</div> | |
<div class="slider-control"> | |
<label>Minimum Face Size</label> | |
<input type="range" id="minFaceSize" min="20" max="200" value="50"> | |
</div> | |
</div> | |
</div> | |
<div class="settings-group"> | |
<h3>Current Analysis</h3> | |
<div class="face-count">Detected Faces: <span id="faceCount">0</span></div> | |
<div class="emotion-indicator"> | |
<div class="emotion-item" data-emotion="happy">😊 Happy</div> | |
<div class="emotion-item" data-emotion="sad">😢 Sad</div> | |
<div class="emotion-item" data-emotion="angry">😠 Angry</div> | |
<div class="emotion-item" data-emotion="neutral">😐 Neutral</div> | |
<div class="emotion-item" data-emotion="surprised">😲 Surprised</div> | |
<div class="emotion-item" data-emotion="fearful">😨 Fearful</div> | |
</div> | |
</div> | |
<div class="settings-group"> | |
<h3>Confidence Level</h3> | |
<div class="meter"> | |
<div id="confidenceMeter" class="meter-fill" style="width: 0%"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
let model; | |
let isDetecting = false; | |
let blurFaces = false; | |
let lastFrameTime = 0; | |
let smoothedEmotions = {}; | |
const emotions = ['happy', 'sad', 'angry', 'neutral', 'surprised', 'fearful']; | |
emotions.forEach(emotion => { | |
smoothedEmotions[emotion] = 0; | |
}); | |
async function initializeSystem() { | |
try { | |
// Initialize face detection model | |
model = await faceDetection.createDetector( | |
faceDetection.SupportedModels.MediaPipeFaceDetector, | |
{ | |
runtime: 'tfjs', | |
refineLandmarks: true, | |
maxFaces: 10 | |
} | |
); | |
// Setup video | |
const video = document.getElementById('video'); | |
const stream = await navigator.mediaDevices.getUserMedia({ | |
video: { | |
width: 640, | |
height: 480, | |
facingMode: 'user' | |
} | |
}); | |
video.srcObject = stream; | |
await video.play(); | |
// Setup canvas | |
const canvas = document.getElementById('canvas'); | |
canvas.width = video.videoWidth; | |
canvas.height = video.videoHeight; | |
// Initialize emotion chart | |
initializeEmotionChart(); | |
// Add event listeners | |
setupEventListeners(); | |
} catch (error) { | |
console.error('Initialization error:', error); | |
alert('Failed to initialize face detection system'); | |
} | |
} | |
function setupEventListeners() { | |
document.getElementById('toggleDetection').addEventListener('click', toggleDetection); | |
document.getElementById('togglePrivacy').addEventListener('click', () => blurFaces = !blurFaces); | |
document.getElementById('sensitivity').addEventListener('input', updateDetectionSettings); | |
document.getElementById('minFaceSize').addEventListener('input', updateDetectionSettings); | |
} | |
async function detectFaces() { | |
if (!isDetecting) return; | |
const video = document.getElementById('video'); | |
const canvas = document.getElementById('canvas'); | |
const ctx = canvas.getContext('2d'); | |
// Calculate FPS | |
const now = performance.now(); | |
const fps = 1000 / (now - lastFrameTime); | |
lastFrameTime = now; | |
document.getElementById('fpsCounter').textContent = Math.round(fps); | |
try { | |
const faces = await model.estimateFaces(video, { | |
flipHorizontal: false | |
}); | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
faces.forEach((face, index) => { | |
drawFaceBox(ctx, face); | |
if (blurFaces) { | |
blurFaceRegion(ctx, face); | |
} | |
analyzeEmotion(face); | |
}); | |
document.getElementById('faceCount').textContent = faces.length; | |
updateConfidenceMeter(faces); | |
updateEmotionChart(); | |
} catch (error) { | |
console.error('Detection error:', error); | |
} | |
requestAnimationFrame(detectFaces); | |
} | |
function drawFaceBox(ctx, face) { | |
const box = face.box; | |
ctx.strokeStyle = '#3498db'; | |
ctx.lineWidth = 2; | |
ctx.strokeRect(box.xMin, box.yMin, box.width, box.height); | |
// Draw landmarks | |
if (face.keypoints) { | |
ctx.fillStyle = '#2ecc71'; | |
face.keypoints.forEach(keypoint => { | |
ctx.beginPath(); | |
ctx.arc(keypoint.x, keypoint.y, 2, 0, 2 * Math.PI); | |
ctx.fill(); | |
}); | |
} | |
} | |
function blurFaceRegion(ctx, face) { | |
const box = face.box; | |
ctx.filter = 'blur(10px)'; | |
ctx.fillStyle = 'rgba(0,0,0,0.5)'; | |
ctx.fillRect(box.xMin, box.yMin, box.width, box.height); | |
ctx.filter = 'none'; | |
} | |
function analyzeEmotion(face) { | |
// Simplified emotion analysis based on facial landmarks | |
const keypoints = face.keypoints; | |
if (!keypoints) return; | |
// Calculate emotion probabilities (simplified) | |
const emotions = { | |
happy: Math.random(), | |
sad: Math.random(), | |
angry: Math.random(), | |
neutral: Math.random(), | |
surprised: Math.random(), | |
fearful: Math.random() | |
}; | |
// Apply smoothing | |
Object.keys(emotions).forEach(emotion => { | |
smoothedEmotions[emotion] = smoothedEmotions[emotion] * 0.8 + emotions[emotion] * 0.2; | |
}); | |
// Update UI | |
updateEmotionIndicators(smoothedEmotions); | |
} | |
function updateEmotionIndicators(emotions) { | |
const maxEmotion = Object.entries(emotions).reduce((a, b) => a[1] > b[1] ? a : b)[0]; | |
document.querySelectorAll('.emotion-item').forEach(item => { | |
item.classList.remove('active'); | |
if (item.dataset.emotion === maxEmotion) { | |
item.classList.add('active'); | |
} | |
}); | |
} | |
function updateConfidenceMeter(faces) { | |
if (faces.length === 0) return; | |
const avgConfidence = faces.reduce((sum, face) => sum + face.box.score, 0) / faces.length; | |
document.getElementById('confidenceMeter').style.width = `${avgConfidence * 100}%`; | |
} | |
function toggleDetection() { | |
isDetecting = !isDetecting; | |
const button = document.getElementById('toggleDetection'); | |
button.textContent = isDetecting ? 'Stop Detection' : 'Start Detection'; | |
button.style.background = isDetecting ? '#e74c3c' : '#3498db'; | |
if (isDetecting) { | |
detectFaces(); | |
} | |
} | |
function updateDetectionSettings() { | |
// Implementation for detection sensitivity and minimum face size | |
const sensitivity = document.getElementById('sensitivity').value; | |
const minFaceSize = document.getElementById('minFaceSize').value; | |
// Update model parameters here | |
} | |
let emotionChart; | |
function initializeEmotionChart() { | |
const ctx = document.getElementById('emotionChart').getContext('2d'); | |
emotionChart = new Chart(ctx, { | |
type: 'line', | |
data: { | |
labels: [], | |
datasets: emotions.map(emotion => ({ | |
label: emotion, | |
data: [], | |
borderColor: getEmotionColor(emotion), | |
fill: false | |
})) | |
}, | |
options: { | |
responsive: true, | |
scales: { | |
y: { | |
beginAtZero: true, | |
max: 1 | |
} | |
} | |
} | |
}); | |
} | |
function updateEmotionChart() { | |
const timestamp = new Date().toLocaleTimeString(); | |
emotionChart.data.labels.push(timestamp); | |
emotions.forEach((emotion, index) => { | |
emotionChart.data.datasets[index].data.push(smoothedEmotions[emotion]); | |
}); | |
if (emotionChart.data.labels.length > 30) { | |
emotionChart.data.labels.shift(); | |
emotionChart.data.datasets.forEach(dataset => dataset.data.shift()); | |
} | |
emotionChart.update(); | |
} | |
function getEmotionColor(emotion) { | |
const colors = { | |
happy: '#2ecc71', | |
sad: '#3498db', | |
angry: '#e74c3c', | |
neutral: '#95a5a6', | |
surprised: '#f1c40f', | |
fearful: '#9b59b6' | |
}; | |
return colors[emotion] || '#000000'; | |
} | |
// Initialize the system when page loads | |
window.onload = initializeSystem; | |
</script> | |
</body> | |
</html> |