Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Object Detection App</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/coco-ssd@2.2.2/dist/coco-ssd.min.js"></script> | |
| <style> | |
| .video-container { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .canvas-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| pointer-events: none; | |
| } | |
| .detection-box { | |
| position: absolute; | |
| border: 2px solid; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: flex-end; | |
| } | |
| .detection-label { | |
| background-color: rgba(0, 0, 0, 0.7); | |
| color: white; | |
| padding: 2px 5px; | |
| font-size: 12px; | |
| border-radius: 4px; | |
| margin-bottom: 2px; | |
| } | |
| .loading-bar { | |
| width: 100%; | |
| height: 4px; | |
| background-color: #e5e7eb; | |
| border-radius: 2px; | |
| overflow: hidden; | |
| } | |
| .loading-progress { | |
| height: 100%; | |
| background-color: #3b82f6; | |
| transition: width 0.3s ease; | |
| } | |
| .toggle-switch { | |
| position: relative; | |
| display: inline-block; | |
| width: 60px; | |
| height: 34px; | |
| } | |
| .toggle-switch input { | |
| opacity: 0; | |
| width: 0; | |
| height: 0; | |
| } | |
| .slider { | |
| position: absolute; | |
| cursor: pointer; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background-color: #ccc; | |
| transition: .4s; | |
| border-radius: 34px; | |
| } | |
| .slider:before { | |
| position: absolute; | |
| content: ""; | |
| height: 26px; | |
| width: 26px; | |
| left: 4px; | |
| bottom: 4px; | |
| background-color: white; | |
| transition: .4s; | |
| border-radius: 50%; | |
| } | |
| input:checked + .slider { | |
| background-color: #3b82f6; | |
| } | |
| input:checked + .slider:before { | |
| transform: translateX(26px); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <div class="max-w-4xl mx-auto bg-white rounded-xl shadow-md overflow-hidden"> | |
| <div class="p-6"> | |
| <h1 class="text-3xl font-bold text-gray-800 mb-2">Object Detection App</h1> | |
| <p class="text-gray-600 mb-6">Detect and label man-made objects using your camera or a video URL</p> | |
| <div class="flex flex-col md:flex-row gap-6 mb-6"> | |
| <div class="flex-1"> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 font-medium mb-2" for="source-select">Detection Source</label> | |
| <select id="source-select" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| <option value="camera">Camera</option> | |
| <option value="video-url">Video URL</option> | |
| <option value="file-upload">Upload Video</option> | |
| </select> | |
| </div> | |
| <div id="camera-controls" class="space-y-4"> | |
| <div class="flex items-center space-x-4"> | |
| <button id="start-camera" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">Start Camera</button> | |
| <button id="stop-camera" class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition" disabled>Stop Camera</button> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <span class="text-gray-700">Show labels:</span> | |
| <label class="toggle-switch"> | |
| <input type="checkbox" id="show-labels" checked> | |
| <span class="slider"></span> | |
| </label> | |
| </div> | |
| </div> | |
| <div id="video-url-controls" class="hidden space-y-4"> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 font-medium mb-2" for="video-url">Video URL</label> | |
| <input type="text" id="video-url" placeholder="https://example.com/video.mp4" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <button id="load-video" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">Load Video</button> | |
| </div> | |
| <div id="file-upload-controls" class="hidden space-y-4"> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 font-medium mb-2" for="video-upload">Upload Video</label> | |
| <input type="file" id="video-upload" accept="video/*" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex-1"> | |
| <div class="bg-gray-200 rounded-lg p-4"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-2">Detection Settings</h2> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-gray-700 font-medium mb-2">Confidence Threshold</label> | |
| <input type="range" id="confidence-slider" min="0" max="1" step="0.05" value="0.5" class="w-full"> | |
| <div class="flex justify-between text-sm text-gray-600"> | |
| <span>0%</span> | |
| <span id="confidence-value">50%</span> | |
| <span>100%</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 font-medium mb-2">Detection Speed</label> | |
| <select id="speed-select" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| <option value="fast">Fast (less accurate)</option> | |
| <option value="medium" selected>Medium</option> | |
| <option value="slow">Slow (more accurate)</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <div class="loading-bar mb-2 hidden" id="model-loading-bar"> | |
| <div class="loading-progress" id="model-loading-progress" style="width: 0%"></div> | |
| </div> | |
| <p class="text-sm text-gray-600" id="status-message">Click "Start Camera" or load a video to begin detection</p> | |
| </div> | |
| <div class="video-container mx-auto"> | |
| <video id="video" autoplay playsinline muted class="w-full max-h-[500px] bg-gray-900 rounded-lg hidden"></video> | |
| <canvas id="canvas" class="canvas-overlay"></canvas> | |
| </div> | |
| <div class="mt-6 hidden" id="results-container"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-2">Detection Results</h2> | |
| <div class="bg-gray-100 rounded-lg p-4"> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" id="detection-results"> | |
| <!-- Detection results will be added here --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // DOM elements | |
| const sourceSelect = document.getElementById('source-select'); | |
| const cameraControls = document.getElementById('camera-controls'); | |
| const videoUrlControls = document.getElementById('video-url-controls'); | |
| const fileUploadControls = document.getElementById('file-upload-controls'); | |
| const startCameraBtn = document.getElementById('start-camera'); | |
| const stopCameraBtn = document.getElementById('stop-camera'); | |
| const loadVideoBtn = document.getElementById('load-video'); | |
| const videoUrlInput = document.getElementById('video-url'); | |
| const videoUploadInput = document.getElementById('video-upload'); | |
| const videoElement = document.getElementById('video'); | |
| const canvasElement = document.getElementById('canvas'); | |
| const confidenceSlider = document.getElementById('confidence-slider'); | |
| const confidenceValue = document.getElementById('confidence-value'); | |
| const speedSelect = document.getElementById('speed-select'); | |
| const showLabelsToggle = document.getElementById('show-labels'); | |
| const statusMessage = document.getElementById('status-message'); | |
| const modelLoadingBar = document.getElementById('model-loading-bar'); | |
| const modelLoadingProgress = document.getElementById('model-loading-progress'); | |
| const resultsContainer = document.getElementById('results-container'); | |
| const detectionResults = document.getElementById('detection-results'); | |
| // App state | |
| let model = null; | |
| let isDetecting = false; | |
| let detectionInterval = null; | |
| let stream = null; | |
| let showLabels = true; | |
| let confidenceThreshold = 0.5; | |
| let detectionSpeed = 'medium'; | |
| // Initialize the app | |
| init(); | |
| function init() { | |
| // Event listeners | |
| sourceSelect.addEventListener('change', handleSourceChange); | |
| startCameraBtn.addEventListener('click', startCamera); | |
| stopCameraBtn.addEventListener('click', stopCamera); | |
| loadVideoBtn.addEventListener('click', loadVideoFromUrl); | |
| videoUploadInput.addEventListener('change', handleVideoUpload); | |
| confidenceSlider.addEventListener('input', updateConfidenceThreshold); | |
| speedSelect.addEventListener('change', updateDetectionSpeed); | |
| showLabelsToggle.addEventListener('change', toggleLabels); | |
| // Set canvas size to match video when it loads | |
| videoElement.addEventListener('loadedmetadata', () => { | |
| canvasElement.width = videoElement.videoWidth; | |
| canvasElement.height = videoElement.videoHeight; | |
| }); | |
| // Load the model | |
| loadModel(); | |
| } | |
| function handleSourceChange() { | |
| const source = sourceSelect.value; | |
| // Hide all controls | |
| cameraControls.classList.add('hidden'); | |
| videoUrlControls.classList.add('hidden'); | |
| fileUploadControls.classList.add('hidden'); | |
| // Show the appropriate controls | |
| if (source === 'camera') { | |
| cameraControls.classList.remove('hidden'); | |
| } else if (source === 'video-url') { | |
| videoUrlControls.classList.remove('hidden'); | |
| } else if (source === 'file-upload') { | |
| fileUploadControls.classList.remove('hidden'); | |
| } | |
| // Stop any ongoing detection | |
| stopDetection(); | |
| } | |
| async function loadModel() { | |
| try { | |
| modelLoadingBar.classList.remove('hidden'); | |
| modelLoadingProgress.style.width = '10%'; | |
| statusMessage.textContent = 'Loading object detection model...'; | |
| // Simulate progress for demo purposes | |
| const progressInterval = setInterval(() => { | |
| const currentWidth = parseInt(modelLoadingProgress.style.width); | |
| if (currentWidth < 90) { | |
| modelLoadingProgress.style.width = `${currentWidth + 10}%`; | |
| } | |
| }, 300); | |
| // Load the COCO-SSD model | |
| model = await cocoSsd.load({ | |
| base: speedSelect.value === 'fast' ? 'mobilenet_v1' : | |
| speedSelect.value === 'slow' ? 'resnet50' : 'lite_mobilenet_v2' | |
| }); | |
| clearInterval(progressInterval); | |
| modelLoadingProgress.style.width = '100%'; | |
| statusMessage.textContent = 'Model loaded successfully!'; | |
| setTimeout(() => { | |
| modelLoadingBar.classList.add('hidden'); | |
| }, 1000); | |
| } catch (error) { | |
| console.error('Error loading model:', error); | |
| statusMessage.textContent = 'Failed to load model. Please refresh the page.'; | |
| modelLoadingBar.classList.add('hidden'); | |
| } | |
| } | |
| async function startCamera() { | |
| try { | |
| statusMessage.textContent = 'Accessing camera...'; | |
| stream = await navigator.mediaDevices.getUserMedia({ video: true }); | |
| videoElement.srcObject = stream; | |
| videoElement.classList.remove('hidden'); | |
| startCameraBtn.disabled = true; | |
| stopCameraBtn.disabled = false; | |
| statusMessage.textContent = 'Camera started. Detecting objects...'; | |
| // Start detection | |
| startDetection(); | |
| } catch (error) { | |
| console.error('Error accessing camera:', error); | |
| statusMessage.textContent = 'Could not access camera. Please check permissions.'; | |
| } | |
| } | |
| function stopCamera() { | |
| if (stream) { | |
| stream.getTracks().forEach(track => track.stop()); | |
| stream = null; | |
| } | |
| videoElement.srcObject = null; | |
| videoElement.classList.add('hidden'); | |
| startCameraBtn.disabled = false; | |
| stopCameraBtn.disabled = true; | |
| // Stop detection | |
| stopDetection(); | |
| statusMessage.textContent = 'Camera stopped.'; | |
| } | |
| function loadVideoFromUrl() { | |
| const videoUrl = videoUrlInput.value.trim(); | |
| if (!videoUrl) { | |
| statusMessage.textContent = 'Please enter a valid video URL.'; | |
| return; | |
| } | |
| stopDetection(); | |
| videoElement.src = videoUrl; | |
| videoElement.classList.remove('hidden'); | |
| statusMessage.textContent = 'Loading video...'; | |
| videoElement.onerror = () => { | |
| statusMessage.textContent = 'Failed to load video. Please check the URL.'; | |
| }; | |
| videoElement.onloadeddata = () => { | |
| statusMessage.textContent = 'Video loaded. Detecting objects...'; | |
| startDetection(); | |
| }; | |
| } | |
| function handleVideoUpload(event) { | |
| const file = event.target.files[0]; | |
| if (!file) return; | |
| stopDetection(); | |
| const videoURL = URL.createObjectURL(file); | |
| videoElement.src = videoURL; | |
| videoElement.classList.remove('hidden'); | |
| statusMessage.textContent = 'Loading video...'; | |
| videoElement.onloadeddata = () => { | |
| statusMessage.textContent = 'Video loaded. Detecting objects...'; | |
| startDetection(); | |
| }; | |
| } | |
| function startDetection() { | |
| if (!model) { | |
| statusMessage.textContent = 'Model not loaded yet. Please wait...'; | |
| return; | |
| } | |
| if (isDetecting) return; | |
| isDetecting = true; | |
| // Clear previous results | |
| detectionResults.innerHTML = ''; | |
| resultsContainer.classList.remove('hidden'); | |
| // Start detecting objects in the video | |
| detectObjects(); | |
| // Set up interval for continuous detection | |
| const interval = detectionSpeed === 'fast' ? 300 : | |
| detectionSpeed === 'slow' ? 1000 : 500; | |
| detectionInterval = setInterval(detectObjects, interval); | |
| } | |
| function stopDetection() { | |
| if (detectionInterval) { | |
| clearInterval(detectionInterval); | |
| detectionInterval = null; | |
| } | |
| isDetecting = false; | |
| // Clear canvas | |
| const ctx = canvasElement.getContext('2d'); | |
| ctx.clearRect(0, 0, canvasElement.width, canvasElement.height); | |
| } | |
| async function detectObjects() { | |
| if (!isDetecting || videoElement.readyState < 2) return; | |
| try { | |
| // Get predictions from the model | |
| const predictions = await model.detect(videoElement); | |
| // Filter predictions based on confidence threshold | |
| const filteredPredictions = predictions.filter( | |
| pred => pred.score >= confidenceThreshold | |
| ); | |
| // Filter for man-made objects (you can expand this list) | |
| const manMadeObjects = filteredPredictions.filter(pred => { | |
| const objectClass = pred.class.toLowerCase(); | |
| return [ | |
| 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', | |
| 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', | |
| 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', | |
| 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', | |
| 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', | |
| 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', | |
| 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', | |
| 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', | |
| 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', | |
| 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', | |
| 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', | |
| 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', | |
| 'scissors', 'teddy bear', 'hair drier', 'toothbrush' | |
| ].includes(objectClass); | |
| }); | |
| // Draw bounding boxes and labels | |
| drawDetections(manMadeObjects); | |
| // Update results display | |
| updateResultsDisplay(manMadeObjects); | |
| } catch (error) { | |
| console.error('Error detecting objects:', error); | |
| statusMessage.textContent = 'Error detecting objects.'; | |
| } | |
| } | |
| function drawDetections(predictions) { | |
| const ctx = canvasElement.getContext('2d'); | |
| ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); | |
| // Font settings | |
| const font = "16px sans-serif"; | |
| ctx.font = font; | |
| ctx.textBaseline = "top"; | |
| predictions.forEach(prediction => { | |
| // Draw bounding box | |
| const [x, y, width, height] = prediction.bbox; | |
| ctx.strokeStyle = "#00FFFF"; | |
| ctx.lineWidth = 2; | |
| ctx.strokeRect(x, y, width, height); | |
| if (showLabels) { | |
| // Draw label background | |
| const text = `${prediction.class} ${(prediction.score * 100).toFixed(1)}%`; | |
| const textWidth = ctx.measureText(text).width; | |
| const textHeight = parseInt(font, 10); | |
| ctx.fillStyle = "rgba(0, 0, 0, 0.7)"; | |
| ctx.fillRect(x, y, textWidth + 4, textHeight + 4); | |
| // Draw text | |
| ctx.fillStyle = "#FFFFFF"; | |
| ctx.fillText(text, x, y); | |
| } | |
| }); | |
| } | |
| function updateResultsDisplay(predictions) { | |
| // Clear previous results | |
| detectionResults.innerHTML = ''; | |
| // Group predictions by class and count them | |
| const objectCounts = {}; | |
| predictions.forEach(pred => { | |
| if (!objectCounts[pred.class]) { | |
| objectCounts[pred.class] = 0; | |
| } | |
| objectCounts[pred.class]++; | |
| }); | |
| // Sort by count (descending) | |
| const sortedClasses = Object.keys(objectCounts).sort( | |
| (a, b) => objectCounts[b] - objectCounts[a] | |
| ); | |
| // Create cards for each detected object class | |
| sortedClasses.forEach(className => { | |
| const count = objectCounts[className]; | |
| const card = document.createElement('div'); | |
| card.className = 'bg-white rounded-lg shadow p-4 flex items-center'; | |
| // You could add icons for common objects here | |
| const icon = document.createElement('div'); | |
| icon.className = 'w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center mr-4'; | |
| icon.innerHTML = `<span class="text-blue-600 font-bold">${count}</span>`; | |
| const content = document.createElement('div'); | |
| content.className = 'flex-1'; | |
| const title = document.createElement('h3'); | |
| title.className = 'font-semibold text-gray-800 capitalize'; | |
| title.textContent = className; | |
| const countText = document.createElement('p'); | |
| countText.className = 'text-sm text-gray-600'; | |
| countText.textContent = `${count} detected`; | |
| content.appendChild(title); | |
| content.appendChild(countText); | |
| card.appendChild(icon); | |
| card.appendChild(content); | |
| detectionResults.appendChild(card); | |
| }); | |
| // Show message if no objects detected | |
| if (sortedClasses.length === 0) { | |
| const message = document.createElement('div'); | |
| message.className = 'col-span-3 text-center py-8 text-gray-500'; | |
| message.textContent = 'No man-made objects detected. Try adjusting the confidence threshold.'; | |
| detectionResults.appendChild(message); | |
| } | |
| } | |
| function updateConfidenceThreshold() { | |
| confidenceThreshold = parseFloat(confidenceSlider.value); | |
| confidenceValue.textContent = `${Math.round(confidenceThreshold * 100)}%`; | |
| } | |
| function updateDetectionSpeed() { | |
| detectionSpeed = speedSelect.value; | |
| // If detection is running, restart with new speed | |
| if (isDetecting) { | |
| stopDetection(); | |
| startDetection(); | |
| } | |
| } | |
| function toggleLabels() { | |
| showLabels = showLabelsToggle.checked; | |
| } | |
| </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=Danyray101/objdetect" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |