| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Scale AI - Human Detection</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.18.0/dist/tf.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/hand-pose-detection@1.0.0/dist/hand-pose-detection.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/coco-ssd@2.2.2/dist/coco-ssd.min.js"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .toggle-checkbox:checked { |
| right: 0; |
| background-color: #10B981; |
| } |
| .toggle-checkbox:checked + .toggle-label { |
| background-color: #A7F3D0; |
| } |
| .detection-box { |
| position: absolute; |
| border: 3px solid; |
| border-radius: 4px; |
| z-index: 10; |
| } |
| .person-box { |
| border-color: #EF4444; |
| } |
| .hand-box { |
| border-color: #10B981; |
| } |
| .pinch-indicator { |
| position: absolute; |
| width: 20px; |
| height: 20px; |
| background-color: rgba(16, 185, 129, 0.7); |
| border-radius: 50%; |
| z-index: 20; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-900 text-white min-h-screen flex flex-col"> |
| |
| <header class="bg-gray-800 py-4 px-6 flex justify-between items-center shadow-lg"> |
| <div class="flex items-center space-x-2"> |
| <div class="w-10 h-10 bg-emerald-500 rounded-full flex items-center justify-center"> |
| <i class="fas fa-robot text-xl"></i> |
| </div> |
| <h1 class="text-2xl font-bold">Scale AI</h1> |
| </div> |
| |
| |
| <div class="flex items-center space-x-3"> |
| <span id="status-text" class="text-sm font-medium">Detection: Off</span> |
| <div class="relative inline-block w-12 mr-2 align-middle select-none"> |
| <input type="checkbox" id="toggle" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/> |
| <label for="toggle" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label> |
| </div> |
| </div> |
| </header> |
|
|
| |
| <main class="flex-grow flex flex-col items-center justify-center p-6"> |
| <div class="relative w-full max-w-3xl h-96 md:h-[32rem] bg-gray-800 rounded-xl overflow-hidden shadow-2xl"> |
| <video id="video" class="w-full h-full object-cover" playsinline autoplay muted></video> |
| <canvas id="canvas" class="absolute top-0 left-0 w-full h-full"></canvas> |
| |
| |
| <div id="loading" class="absolute inset-0 flex items-center justify-center bg-gray-900 bg-opacity-70"> |
| <div class="text-center"> |
| <div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-emerald-500 mx-auto mb-4"></div> |
| <p class="text-lg font-medium">Loading AI models...</p> |
| </div> |
| </div> |
| |
| |
| <div id="instructions" class="absolute bottom-4 left-4 right-4 bg-gray-800 bg-opacity-80 p-3 rounded-lg text-sm hidden"> |
| <p>👆 Pinch fingers together to toggle detection</p> |
| <p class="mt-1">👤 Person detection (red box)</p> |
| <p class="mt-1">✋ Hand detection (green box)</p> |
| </div> |
| </div> |
| |
| <div class="mt-6 text-center max-w-2xl"> |
| <h2 class="text-xl font-bold mb-2">Human Detection System</h2> |
| <p class="text-gray-300">Scale AI's advanced computer vision detects humans and hand gestures in real-time. Toggle detection with the switch or by pinching your fingers together.</p> |
| </div> |
| </main> |
|
|
| |
| <footer class="bg-gray-800 py-4 px-6 text-center text-sm text-gray-400"> |
| <p>© 2023 Scale AI Technologies. All rights reserved.</p> |
| </footer> |
|
|
| <script> |
| |
| const video = document.getElementById('video'); |
| const canvas = document.getElementById('canvas'); |
| const ctx = canvas.getContext('2d'); |
| const toggleSwitch = document.getElementById('toggle'); |
| const statusText = document.getElementById('status-text'); |
| const loadingIndicator = document.getElementById('loading'); |
| const instructions = document.getElementById('instructions'); |
| |
| |
| let isDetectionActive = false; |
| let personDetector = null; |
| let handDetector = null; |
| let lastPinchTime = 0; |
| let animationId = null; |
| |
| |
| async function init() { |
| try { |
| |
| await Promise.all([ |
| loadPersonDetectionModel(), |
| loadHandDetectionModel() |
| ]); |
| |
| |
| await startCamera(); |
| |
| |
| loadingIndicator.classList.add('hidden'); |
| instructions.classList.remove('hidden'); |
| |
| |
| detect(); |
| } catch (error) { |
| console.error('Initialization error:', error); |
| loadingIndicator.innerHTML = ` |
| <div class="text-center"> |
| <i class="fas fa-exclamation-triangle text-red-500 text-4xl mb-3"></i> |
| <p class="text-lg font-medium">Failed to initialize</p> |
| <p class="text-sm mt-2">${error.message}</p> |
| <button onclick="window.location.reload()" class="mt-4 px-4 py-2 bg-emerald-500 rounded-md hover:bg-emerald-600 transition"> |
| Try Again |
| </button> |
| </div> |
| `; |
| } |
| } |
| |
| |
| async function loadPersonDetectionModel() { |
| personDetector = await cocoSsd.load(); |
| console.log('Person detection model loaded'); |
| } |
| |
| |
| async function loadHandDetectionModel() { |
| handDetector = await handPoseDetection.createDetector( |
| handPoseDetection.SupportedModels.MediaPipeHands, { |
| runtime: 'tfjs', |
| modelType: 'full', |
| maxHands: 2 |
| } |
| ); |
| console.log('Hand detection model loaded'); |
| } |
| |
| |
| async function startCamera() { |
| const stream = await navigator.mediaDevices.getUserMedia({ |
| video: { |
| width: { ideal: 1280 }, |
| height: { ideal: 720 }, |
| facingMode: 'user' |
| }, |
| audio: false |
| }); |
| |
| video.srcObject = stream; |
| |
| return new Promise((resolve) => { |
| video.onloadedmetadata = () => { |
| |
| canvas.width = video.videoWidth; |
| canvas.height = video.videoHeight; |
| resolve(); |
| }; |
| }); |
| } |
| |
| |
| async function detect() { |
| if (!isDetectionActive) { |
| |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| animationId = requestAnimationFrame(detect); |
| return; |
| } |
| |
| |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| |
| |
| if (personDetector) { |
| const persons = await personDetector.detect(video); |
| drawPersonBoxes(persons); |
| } |
| |
| |
| if (handDetector) { |
| const hands = await handDetector.estimateHands(video); |
| drawHandBoxes(hands); |
| checkForPinchGesture(hands); |
| } |
| |
| |
| animationId = requestAnimationFrame(detect); |
| } |
| |
| |
| function drawPersonBoxes(persons) { |
| persons.forEach(person => { |
| if (person.class === 'person') { |
| const [x, y, width, height] = person.bbox; |
| |
| |
| const box = document.createElement('div'); |
| box.className = 'detection-box person-box'; |
| box.style.left = `${x}px`; |
| box.style.top = `${y}px`; |
| box.style.width = `${width}px`; |
| box.style.height = `${height}px`; |
| |
| |
| document.querySelectorAll('.person-box').forEach(el => el.remove()); |
| canvas.parentNode.appendChild(box); |
| |
| |
| ctx.fillStyle = '#EF4444'; |
| ctx.font = '16px Arial'; |
| ctx.fillText( |
| `Person (${Math.round(person.score * 100)}%)`, |
| x, |
| y > 20 ? y - 5 : y + 20 |
| ); |
| } |
| }); |
| } |
| |
| |
| function drawHandBoxes(hands) { |
| |
| document.querySelectorAll('.hand-box, .pinch-indicator').forEach(el => el.remove()); |
| |
| hands.forEach(hand => { |
| const keypoints = hand.keypoints; |
| |
| |
| let minX = Infinity, minY = Infinity, maxX = 0, maxY = 0; |
| |
| keypoints.forEach(point => { |
| minX = Math.min(minX, point.x); |
| minY = Math.min(minY, point.y); |
| maxX = Math.max(maxX, point.x); |
| maxY = Math.max(maxY, point.y); |
| }); |
| |
| const width = maxX - minX; |
| const height = maxY - minY; |
| |
| |
| const box = document.createElement('div'); |
| box.className = 'detection-box hand-box'; |
| box.style.left = `${minX}px`; |
| box.style.top = `${minY}px`; |
| box.style.width = `${width}px`; |
| box.style.height = `${height}px`; |
| canvas.parentNode.appendChild(box); |
| |
| |
| if (isHandPinching(hand)) { |
| const indexTip = keypoints.find(p => p.name === 'index_finger_tip'); |
| const thumbTip = keypoints.find(p => p.name === 'thumb_tip'); |
| |
| if (indexTip && thumbTip) { |
| const pinchX = (indexTip.x + thumbTip.x) / 2; |
| const pinchY = (indexTip.y + thumbTip.y) / 2; |
| |
| const indicator = document.createElement('div'); |
| indicator.className = 'pinch-indicator'; |
| indicator.style.left = `${pinchX - 10}px`; |
| indicator.style.top = `${pinchY - 10}px`; |
| canvas.parentNode.appendChild(indicator); |
| } |
| } |
| }); |
| } |
| |
| |
| function isHandPinching(hand) { |
| const keypoints = hand.keypoints; |
| const indexTip = keypoints.find(p => p.name === 'index_finger_tip'); |
| const thumbTip = keypoints.find(p => p.name === 'thumb_tip'); |
| |
| if (!indexTip || !thumbTip) return false; |
| |
| |
| const dx = indexTip.x - thumbTip.x; |
| const dy = indexTip.y - thumbTip.y; |
| const distance = Math.sqrt(dx * dx + dy * dy); |
| |
| |
| return distance < 30; |
| } |
| |
| |
| function checkForPinchGesture(hands) { |
| const now = Date.now(); |
| |
| |
| if (now - lastPinchTime < 1000) return; |
| |
| |
| const isPinching = hands.some(hand => isHandPinching(hand)); |
| |
| if (isPinching) { |
| lastPinchTime = now; |
| toggleDetection(); |
| } |
| } |
| |
| |
| function toggleDetection() { |
| isDetectionActive = !isDetectionActive; |
| toggleSwitch.checked = isDetectionActive; |
| statusText.textContent = `Detection: ${isDetectionActive ? 'On' : 'Off'}`; |
| |
| |
| const statusColor = isDetectionActive ? 'text-emerald-400' : 'text-red-400'; |
| statusText.className = `text-sm font-medium ${statusColor}`; |
| |
| |
| if (isDetectionActive && !animationId) { |
| detect(); |
| } |
| } |
| |
| |
| toggleSwitch.addEventListener('change', toggleDetection); |
| |
| |
| window.addEventListener('DOMContentLoaded', init); |
| |
| |
| window.addEventListener('beforeunload', () => { |
| if (animationId) { |
| cancelAnimationFrame(animationId); |
| } |
| |
| if (video.srcObject) { |
| video.srcObject.getTracks().forEach(track => track.stop()); |
| } |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=cmbai13/scale-ai" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |