Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Face Detection App</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; font-family: Arial, sans-serif; } | |
| body { background: linear-gradient(135deg, #667eea, #764ba2); min-height: 100vh; padding: 20px; } | |
| .container { max-width: 1000px; margin: 0 auto; background: white; border-radius: 15px; padding: 30px; } | |
| header { text-align: center; margin-bottom: 30px; } | |
| h1 { color: #333; margin-bottom: 10px; } | |
| .subtitle { color: #666; } | |
| .main-content { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 30px; } | |
| @media (max-width: 768px) { .main-content { grid-template-columns: 1fr; } } | |
| .input-section, .output-section { background: #f8f9fa; padding: 25px; border-radius: 10px; border: 2px dashed #dee2e6; } | |
| .section-title { color: #495057; margin-bottom: 20px; } | |
| .input-options { display: flex; gap: 10px; margin-bottom: 20px; } | |
| .btn { padding: 12px; border: none; border-radius: 8px; background: #6c757d; color: white; cursor: pointer; flex: 1; } | |
| .btn:hover { background: #5a6268; } | |
| .btn.active { background: #007bff; } | |
| .upload-area { border: 2px dashed #adb5bd; border-radius: 10px; padding: 40px 20px; text-align: center; margin-bottom: 20px; cursor: pointer; } | |
| .upload-area:hover { border-color: #007bff; } | |
| .upload-icon { font-size: 3rem; margin-bottom: 15px; color: #6c757d; } | |
| .camera-container { position: relative; width: 100%; height: 300px; background: #000; border-radius: 10px; overflow: hidden; margin-bottom: 20px; display: none; } | |
| #videoElement { width: 100%; height: 100%; object-fit: cover; } | |
| .capture-btn { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); background: #dc3545; width: 60px; height: 60px; border-radius: 50%; border: 4px solid white; cursor: pointer; } | |
| .slider-container { margin: 20px 0; } | |
| .slider-label { display: flex; justify-content: space-between; margin-bottom: 10px; color: #495057; } | |
| .slider { width: 100%; height: 8px; background: #dee2e6; border-radius: 5px; outline: none; } | |
| .slider::-webkit-slider-thumb { width: 20px; height: 20px; border-radius: 50%; background: #007bff; cursor: pointer; } | |
| .detect-btn { width: 100%; padding: 15px; background: #28a745; color: white; border: none; border-radius: 8px; font-size: 1.1rem; cursor: pointer; } | |
| .detect-btn:hover { background: #218838; } | |
| .detect-btn:disabled { background: #6c757d; cursor: not-allowed; } | |
| .image-container { width: 100%; height: 300px; background: #e9ecef; border-radius: 10px; overflow: hidden; margin-bottom: 20px; display: flex; align-items: center; justify-content: center; } | |
| #outputImage { max-width: 100%; max-height: 100%; display: none; } | |
| .placeholder-text { color: #6c757d; text-align: center; } | |
| .results-container { background: white; border-radius: 10px; padding: 20px; border: 2px solid #e9ecef; } | |
| .result-item { display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #dee2e6; } | |
| .result-item:last-child { border-bottom: none; } | |
| .result-label { font-weight: bold; color: #495057; } | |
| .result-value { color: #007bff; font-weight: bold; } | |
| .loading { display: none; text-align: center; margin: 20px 0; } | |
| .spinner { border: 4px solid #f3f3f3; border-radius: 50%; border-top: 4px solid #007bff; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto 15px; } | |
| @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } | |
| #fileInput { display: none; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1>Face Detection App</h1> | |
| <p class="subtitle">Upload an image or use camera to detect faces</p> | |
| </header> | |
| <div class="main-content"> | |
| <div class="input-section"> | |
| <h2 class="section-title">Input</h2> | |
| <div class="input-options"> | |
| <button id="uploadBtn" class="btn active">Upload Image</button> | |
| <button id="cameraBtn" class="btn">Use Camera</button> | |
| </div> | |
| <div id="uploadArea" class="upload-area"> | |
| <div class="upload-icon">📁</div> | |
| <p>Click to upload an image</p> | |
| <input type="file" id="fileInput" accept="image/*"> | |
| </div> | |
| <div id="cameraContainer" class="camera-container"> | |
| <video id="videoElement" autoplay playsinline></video> | |
| <button id="captureBtn" class="capture-btn"></button> | |
| </div> | |
| <div class="slider-container"> | |
| <div class="slider-label"> | |
| <span>Detection Scale:</span> | |
| <span id="scaleValue">1.1</span> | |
| </div> | |
| <input type="range" min="1.1" max="2.0" step="0.1" value="1.1" class="slider" id="scaleSlider"> | |
| </div> | |
| <button id="detectBtn" class="detect-btn" disabled>Detect Faces</button> | |
| <div id="loading" class="loading"> | |
| <div class="spinner"></div> | |
| <p>Processing image...</p> | |
| </div> | |
| </div> | |
| <div class="output-section"> | |
| <h2 class="section-title">Output</h2> | |
| <div class="image-container"> | |
| <img id="outputImage" alt="Processed Image"> | |
| <p id="placeholderText" class="placeholder-text">Processed image will appear here</p> | |
| </div> | |
| <div id="resultsContainer" class="results-container" style="display: none;"> | |
| <h3 style="margin-bottom: 15px; color: #495057;">Detection Results</h3> | |
| <div class="result-item"> | |
| <span class="result-label">Faces Detected:</span> | |
| <span id="facesCount" class="result-value">0</span> | |
| </div> | |
| <div id="facesDetails"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const uploadBtn = document.getElementById('uploadBtn'); | |
| const cameraBtn = document.getElementById('cameraBtn'); | |
| const uploadArea = document.getElementById('uploadArea'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const cameraContainer = document.getElementById('cameraContainer'); | |
| const videoElement = document.getElementById('videoElement'); | |
| const captureBtn = document.getElementById('captureBtn'); | |
| const scaleSlider = document.getElementById('scaleSlider'); | |
| const scaleValue = document.getElementById('scaleValue'); | |
| const detectBtn = document.getElementById('detectBtn'); | |
| const outputImage = document.getElementById('outputImage'); | |
| const placeholderText = document.getElementById('placeholderText'); | |
| const resultsContainer = document.getElementById('resultsContainer'); | |
| const facesCount = document.getElementById('facesCount'); | |
| const facesDetails = document.getElementById('facesDetails'); | |
| const loading = document.getElementById('loading'); | |
| let currentImage = null; | |
| let stream = null; | |
| uploadBtn.addEventListener('click', () => { | |
| uploadBtn.classList.add('active'); | |
| cameraBtn.classList.remove('active'); | |
| uploadArea.style.display = 'block'; | |
| cameraContainer.style.display = 'none'; | |
| stopCamera(); | |
| }); | |
| cameraBtn.addEventListener('click', () => { | |
| cameraBtn.classList.add('active'); | |
| uploadBtn.classList.remove('active'); | |
| uploadArea.style.display = 'none'; | |
| cameraContainer.style.display = 'block'; | |
| startCamera(); | |
| }); | |
| uploadArea.addEventListener('click', () => fileInput.click()); | |
| fileInput.addEventListener('change', handleImageUpload); | |
| captureBtn.addEventListener('click', captureImage); | |
| detectBtn.addEventListener('click', detectFaces); | |
| scaleSlider.addEventListener('input', () => scaleValue.textContent = scaleSlider.value); | |
| function handleImageUpload(event) { | |
| const file = event.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| currentImage = e.target.result; | |
| outputImage.src = currentImage; | |
| outputImage.style.display = 'block'; | |
| placeholderText.style.display = 'none'; | |
| detectBtn.disabled = false; | |
| resultsContainer.style.display = 'none'; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| } | |
| function startCamera() { | |
| if (navigator.mediaDevices?.getUserMedia) { | |
| navigator.mediaDevices.getUserMedia({ video: true }) | |
| .then(mediaStream => { | |
| stream = mediaStream; | |
| videoElement.srcObject = stream; | |
| detectBtn.disabled = false; | |
| }) | |
| .catch(error => { | |
| console.error("Camera error: ", error); | |
| alert("Unable to access camera. Please check permissions."); | |
| }); | |
| } else { | |
| alert("Your browser doesn't support camera access."); | |
| } | |
| } | |
| function stopCamera() { | |
| if (stream) { | |
| stream.getTracks().forEach(track => track.stop()); | |
| stream = null; | |
| } | |
| } | |
| function captureImage() { | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = videoElement.videoWidth; | |
| canvas.height = videoElement.videoHeight; | |
| const ctx = canvas.getContext('2d'); | |
| ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); | |
| currentImage = canvas.toDataURL('image/png'); | |
| outputImage.src = currentImage; | |
| outputImage.style.display = 'block'; | |
| placeholderText.style.display = 'none'; | |
| resultsContainer.style.display = 'none'; | |
| } | |
| async function detectFaces() { | |
| if (!currentImage) { | |
| alert("Please upload an image or capture from camera first."); | |
| return; | |
| } | |
| loading.style.display = 'block'; | |
| detectBtn.disabled = true; | |
| try { | |
| const response = await fetch('/detect', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ image: currentImage, scale: scaleSlider.value }) | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| outputImage.src = data.result_image; | |
| displayResults(data.faces_detected, data.face_data); | |
| } else { | |
| alert('Error detecting faces: ' + data.error); | |
| } | |
| } catch (error) { | |
| alert('Error detecting faces. Please try again.'); | |
| } finally { | |
| loading.style.display = 'none'; | |
| detectBtn.disabled = false; | |
| } | |
| } | |
| function displayResults(faceCount, faceData) { | |
| facesCount.textContent = faceCount; | |
| facesDetails.innerHTML = ''; | |
| faceData.forEach(face => { | |
| const faceElement = document.createElement('div'); | |
| faceElement.className = 'result-item'; | |
| faceElement.innerHTML = ` | |
| <span class="result-label">Face ${face.id}:</span> | |
| <span class="result-value">${face.gender}, ${face.age}</span> | |
| `; | |
| facesDetails.appendChild(faceElement); | |
| }); | |
| resultsContainer.style.display = 'block'; | |
| } | |
| uploadBtn.click(); | |
| </script> | |
| </body> | |
| </html> |