objdetect / index.html
Danyray101's picture
Add 3 files
ce4dba5 verified
<!DOCTYPE html>
<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>