scale-ai / index.html
cmbai13's picture
Add 3 files
8aa8fe9 verified
<!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 -->
<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>
<!-- Toggle Switch -->
<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 Content -->
<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>
<!-- Loading Indicator -->
<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>
<!-- Instructions -->
<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 -->
<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>
// DOM Elements
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');
// State
let isDetectionActive = false;
let personDetector = null;
let handDetector = null;
let lastPinchTime = 0;
let animationId = null;
// Initialize the app
async function init() {
try {
// Load models
await Promise.all([
loadPersonDetectionModel(),
loadHandDetectionModel()
]);
// Start camera
await startCamera();
// Hide loading indicator
loadingIndicator.classList.add('hidden');
instructions.classList.remove('hidden');
// Start detection loop
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>
`;
}
}
// Load person detection model
async function loadPersonDetectionModel() {
personDetector = await cocoSsd.load();
console.log('Person detection model loaded');
}
// Load hand detection model
async function loadHandDetectionModel() {
handDetector = await handPoseDetection.createDetector(
handPoseDetection.SupportedModels.MediaPipeHands, {
runtime: 'tfjs',
modelType: 'full',
maxHands: 2
}
);
console.log('Hand detection model loaded');
}
// Start camera
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 = () => {
// Set canvas dimensions to match video
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
resolve();
};
});
}
// Main detection loop
async function detect() {
if (!isDetectionActive) {
// Clear canvas if detection is off
ctx.clearRect(0, 0, canvas.width, canvas.height);
animationId = requestAnimationFrame(detect);
return;
}
// Clear previous drawings
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Detect persons
if (personDetector) {
const persons = await personDetector.detect(video);
drawPersonBoxes(persons);
}
// Detect hands
if (handDetector) {
const hands = await handDetector.estimateHands(video);
drawHandBoxes(hands);
checkForPinchGesture(hands);
}
// Continue the loop
animationId = requestAnimationFrame(detect);
}
// Draw bounding boxes around detected persons
function drawPersonBoxes(persons) {
persons.forEach(person => {
if (person.class === 'person') {
const [x, y, width, height] = person.bbox;
// Draw box
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`;
// Clear previous boxes and add new one
document.querySelectorAll('.person-box').forEach(el => el.remove());
canvas.parentNode.appendChild(box);
// Draw label
ctx.fillStyle = '#EF4444';
ctx.font = '16px Arial';
ctx.fillText(
`Person (${Math.round(person.score * 100)}%)`,
x,
y > 20 ? y - 5 : y + 20
);
}
});
}
// Draw bounding boxes around detected hands
function drawHandBoxes(hands) {
// Clear previous hand boxes and pinch indicators
document.querySelectorAll('.hand-box, .pinch-indicator').forEach(el => el.remove());
hands.forEach(hand => {
const keypoints = hand.keypoints;
// Calculate bounding box
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;
// Draw box
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);
// Draw pinch indicator if hand is pinching
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);
}
}
});
}
// Check if hand is making a pinch gesture
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;
// Calculate distance between index and thumb tips
const dx = indexTip.x - thumbTip.x;
const dy = indexTip.y - thumbTip.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Consider it a pinch if distance is small
return distance < 30;
}
// Check for pinch gesture to toggle detection
function checkForPinchGesture(hands) {
const now = Date.now();
// Only allow toggle every 1 second to prevent rapid toggling
if (now - lastPinchTime < 1000) return;
// Check if any hand is pinching
const isPinching = hands.some(hand => isHandPinching(hand));
if (isPinching) {
lastPinchTime = now;
toggleDetection();
}
}
// Toggle detection state
function toggleDetection() {
isDetectionActive = !isDetectionActive;
toggleSwitch.checked = isDetectionActive;
statusText.textContent = `Detection: ${isDetectionActive ? 'On' : 'Off'}`;
// Visual feedback
const statusColor = isDetectionActive ? 'text-emerald-400' : 'text-red-400';
statusText.className = `text-sm font-medium ${statusColor}`;
// If turning on, start detection immediately
if (isDetectionActive && !animationId) {
detect();
}
}
// Event Listeners
toggleSwitch.addEventListener('change', toggleDetection);
// Initialize the app
window.addEventListener('DOMContentLoaded', init);
// Clean up on page unload
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>