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> |