|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Live Object Detection</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/coco-ssd"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<style> |
|
.detection-box { |
|
position: absolute; |
|
border: 3px solid; |
|
border-radius: 4px; |
|
font-weight: bold; |
|
text-shadow: 1px 1px 1px rgba(0,0,0,0.8); |
|
padding: 2px 4px; |
|
white-space: nowrap; |
|
} |
|
.video-container { |
|
position: relative; |
|
overflow: hidden; |
|
border-radius: 12px; |
|
box-shadow: 0 10px 25px rgba(0,0,0,0.2); |
|
} |
|
.detection-canvas { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
pointer-events: none; |
|
} |
|
.stats-card { |
|
backdrop-filter: blur(10px); |
|
background: rgba(255, 255, 255, 0.1); |
|
} |
|
.detection-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); |
|
gap: 8px; |
|
} |
|
.detection-item { |
|
transition: all 0.3s ease; |
|
} |
|
.detection-item:hover { |
|
transform: translateY(-3px); |
|
} |
|
.pulse { |
|
animation: pulse 2s infinite; |
|
} |
|
@keyframes pulse { |
|
0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); } |
|
70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } |
|
100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gradient-to-br from-gray-900 to-gray-800 min-h-screen text-white"> |
|
<div class="container mx-auto px-4 py-8"> |
|
<header class="text-center mb-12"> |
|
<h1 class="text-4xl md:text-5xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-500"> |
|
Live Object Detection |
|
</h1> |
|
<p class="text-gray-300 max-w-2xl mx-auto"> |
|
Real-time object detection using your device's camera powered by TensorFlow.js and COCO-SSD model. |
|
Grant camera access to start detecting objects in your environment. |
|
</p> |
|
</header> |
|
|
|
<main> |
|
<div class="flex flex-col lg:flex-row gap-8"> |
|
|
|
<div class="lg:w-2/3"> |
|
<div class="video-container relative"> |
|
<video id="video" autoplay playsinline class="w-full h-auto rounded-lg bg-gray-800 aspect-video"></video> |
|
<canvas id="canvas" class="detection-canvas w-full h-auto rounded-lg"></canvas> |
|
|
|
|
|
<div id="cameraPrompt" class="absolute inset-0 flex flex-col items-center justify-center bg-gray-900 bg-opacity-80 rounded-lg"> |
|
<div class="text-center p-8"> |
|
<i class="fas fa-camera text-6xl text-blue-400 mb-4"></i> |
|
<h2 class="text-2xl font-bold mb-2">Camera Access Required</h2> |
|
<p class="text-gray-300 mb-6">Please allow camera access to enable live object detection</p> |
|
<button id="startBtn" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-8 rounded-full text-lg transition-all transform hover:scale-105 pulse"> |
|
Start Detection |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="mt-6 flex flex-wrap gap-4 justify-center"> |
|
<button id="toggleDetectionBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg flex items-center disabled:opacity-50"> |
|
<i class="fas fa-play mr-2"></i> Start Detection |
|
</button> |
|
<button id="toggleCameraBtn" class="bg-gray-700 hover:bg-gray-600 text-white font-medium py-2 px-6 rounded-lg flex items-center"> |
|
<i class="fas fa-sync mr-2"></i> Switch Camera |
|
</button> |
|
<button id="captureBtn" class="bg-purple-600 hover:bg-purple-700 text-white font-medium py-2 px-6 rounded-lg flex items-center"> |
|
<i class="fas fa-camera mr-2"></i> Capture Frame |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="lg:w-1/3"> |
|
<div class="bg-gray-800 bg-opacity-50 rounded-xl p-6 h-full"> |
|
<h2 class="text-2xl font-bold mb-4 flex items-center"> |
|
<i class="fas fa-list mr-3 text-blue-400"></i> Detection Results |
|
</h2> |
|
|
|
<div class="stats-card rounded-lg p-4 mb-6"> |
|
<div class="grid grid-cols-3 gap-4 text-center"> |
|
<div> |
|
<div class="text-sm text-gray-300">Objects</div> |
|
<div id="objectCount" class="text-2xl font-bold">0</div> |
|
</div> |
|
<div> |
|
<div class="text-sm text-gray-300">FPS</div> |
|
<div id="fpsCounter" class="text-2xl font-bold">0</div> |
|
</div> |
|
<div> |
|
<div class="text-sm text-gray-300">Confidence</div> |
|
<div id="avgConfidence" class="text-2xl font-bold">0%</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<h3 class="text-lg font-semibold mb-3 flex items-center"> |
|
<i class="fas fa-boxes mr-2 text-blue-400"></i> Detected Objects |
|
</h3> |
|
|
|
<div id="detectionList" class="detection-grid"> |
|
|
|
<div class="text-center py-4 text-gray-400"> |
|
No objects detected yet |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</main> |
|
|
|
<footer class="mt-16 text-center text-gray-400"> |
|
<p>Powered by TensorFlow.js and COCO-SSD model | Built with Tailwind CSS</p> |
|
<p class="mt-2">Note: Processing happens entirely in your browser - no data is sent to any server</p> |
|
</footer> |
|
</div> |
|
|
|
<script> |
|
|
|
const video = document.getElementById('video'); |
|
const canvas = document.getElementById('canvas'); |
|
const ctx = canvas.getContext('2d'); |
|
const cameraPrompt = document.getElementById('cameraPrompt'); |
|
const startBtn = document.getElementById('startBtn'); |
|
const toggleDetectionBtn = document.getElementById('toggleDetectionBtn'); |
|
const toggleCameraBtn = document.getElementById('toggleCameraBtn'); |
|
const captureBtn = document.getElementById('captureBtn'); |
|
const objectCount = document.getElementById('objectCount'); |
|
const fpsCounter = document.getElementById('fpsCounter'); |
|
const avgConfidence = document.getElementById('avgConfidence'); |
|
const detectionList = document.getElementById('detectionList'); |
|
|
|
|
|
let model = null; |
|
let detectionActive = false; |
|
let stream = null; |
|
let currentFacingMode = 'environment'; |
|
let lastTimestamp = 0; |
|
let frameCount = 0; |
|
let fps = 0; |
|
let detectedObjects = []; |
|
|
|
|
|
const colors = [ |
|
'#FF5252', '#FF4081', '#E040FB', '#7C4DFF', |
|
'#536DFE', '#448AFF', '#40C4FF', '#18FFFF', |
|
'#64FFDA', '#69F0AE', '#B2FF59', '#EEFF41' |
|
]; |
|
|
|
|
|
async function init() { |
|
try { |
|
|
|
model = await cocoSsd.load(); |
|
console.log('Model loaded successfully'); |
|
|
|
|
|
startBtn.addEventListener('click', startCamera); |
|
toggleDetectionBtn.addEventListener('click', toggleDetection); |
|
toggleCameraBtn.addEventListener('click', switchCamera); |
|
captureBtn.addEventListener('click', captureFrame); |
|
|
|
|
|
toggleDetectionBtn.disabled = true; |
|
toggleCameraBtn.disabled = true; |
|
captureBtn.disabled = true; |
|
} catch (error) { |
|
console.error('Error loading model:', error); |
|
alert('Failed to load the object detection model. Please try again later.'); |
|
} |
|
} |
|
|
|
|
|
async function startCamera() { |
|
try { |
|
stream = await navigator.mediaDevices.getUserMedia({ |
|
video: { facingMode: currentFacingMode } |
|
}); |
|
|
|
video.srcObject = stream; |
|
cameraPrompt.classList.add('hidden'); |
|
toggleDetectionBtn.disabled = false; |
|
toggleCameraBtn.disabled = false; |
|
captureBtn.disabled = false; |
|
|
|
|
|
video.addEventListener('loadedmetadata', () => { |
|
|
|
canvas.width = video.videoWidth; |
|
canvas.height = video.videoHeight; |
|
|
|
|
|
if (detectionActive) { |
|
detectFrame(); |
|
} |
|
}); |
|
} catch (error) { |
|
console.error('Error accessing camera:', error); |
|
cameraPrompt.querySelector('p').textContent = 'Camera access denied. Please allow camera permissions and refresh the page.'; |
|
} |
|
} |
|
|
|
|
|
async function switchCamera() { |
|
if (!stream) return; |
|
|
|
|
|
stream.getTracks().forEach(track => track.stop()); |
|
|
|
|
|
currentFacingMode = currentFacingMode === 'environment' ? 'user' : 'environment'; |
|
|
|
|
|
await startCamera(); |
|
} |
|
|
|
|
|
function toggleDetection() { |
|
detectionActive = !detectionActive; |
|
|
|
if (detectionActive) { |
|
toggleDetectionBtn.innerHTML = '<i class="fas fa-pause mr-2"></i> Pause Detection'; |
|
detectFrame(); |
|
} else { |
|
toggleDetectionBtn.innerHTML = '<i class="fas fa-play mr-2"></i> Resume Detection'; |
|
} |
|
} |
|
|
|
|
|
function captureFrame() { |
|
if (!detectionActive) return; |
|
|
|
|
|
const tempCanvas = document.createElement('canvas'); |
|
tempCanvas.width = canvas.width; |
|
tempCanvas.height = canvas.height; |
|
const tempCtx = tempCanvas.getContext('2d'); |
|
|
|
|
|
tempCtx.drawImage(video, 0, 0, tempCanvas.width, tempCanvas.height); |
|
|
|
|
|
tempCtx.drawImage(canvas, 0, 0); |
|
|
|
|
|
const link = document.createElement('a'); |
|
link.download = 'object-detection-' + new Date().toISOString().replace(/:/g, '-') + '.png'; |
|
link.href = tempCanvas.toDataURL('image/png'); |
|
link.click(); |
|
|
|
|
|
showNotification('Frame captured successfully!'); |
|
} |
|
|
|
|
|
function showNotification(message) { |
|
const notification = document.createElement('div'); |
|
notification.className = 'fixed bottom-4 right-4 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg z-50 animate-fadeIn'; |
|
notification.textContent = message; |
|
document.body.appendChild(notification); |
|
|
|
setTimeout(() => { |
|
notification.classList.add('animate-fadeOut'); |
|
setTimeout(() => { |
|
document.body.removeChild(notification); |
|
}, 500); |
|
}, 3000); |
|
} |
|
|
|
|
|
async function detectFrame() { |
|
if (!detectionActive || !model) return; |
|
|
|
|
|
const startTime = performance.now(); |
|
|
|
try { |
|
|
|
const predictions = await model.detect(video); |
|
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
detectedObjects = []; |
|
let totalConfidence = 0; |
|
|
|
predictions.forEach((prediction, index) => { |
|
|
|
const [x, y, width, height] = prediction.bbox; |
|
const label = prediction.class; |
|
const score = Math.round(prediction.score * 100); |
|
|
|
|
|
detectedObjects.push({ |
|
label, |
|
score, |
|
color: colors[index % colors.length] |
|
}); |
|
|
|
|
|
ctx.strokeStyle = colors[index % colors.length]; |
|
ctx.lineWidth = 3; |
|
ctx.strokeRect(x, y, width, height); |
|
|
|
|
|
ctx.fillStyle = colors[index % colors.length]; |
|
const textWidth = ctx.measureText(`${label} ${score}%`).width; |
|
ctx.fillRect(x, y, textWidth + 10, 25); |
|
|
|
|
|
ctx.fillStyle = 'white'; |
|
ctx.font = 'bold 16px Arial'; |
|
ctx.fillText(`${label} ${score}%`, x + 5, y + 18); |
|
|
|
|
|
totalConfidence += score; |
|
}); |
|
|
|
|
|
objectCount.textContent = predictions.length; |
|
const avgConf = predictions.length > 0 ? Math.round(totalConfidence / predictions.length) : 0; |
|
avgConfidence.textContent = `${avgConf}%`; |
|
|
|
|
|
updateDetectionList(); |
|
|
|
|
|
frameCount++; |
|
const elapsed = startTime - lastTimestamp; |
|
|
|
if (elapsed >= 1000) { |
|
fps = Math.round((frameCount * 1000) / elapsed); |
|
fpsCounter.textContent = fps; |
|
frameCount = 0; |
|
lastTimestamp = startTime; |
|
} |
|
} catch (error) { |
|
console.error('Detection error:', error); |
|
} |
|
|
|
|
|
if (detectionActive) { |
|
requestAnimationFrame(detectFrame); |
|
} |
|
} |
|
|
|
|
|
function updateDetectionList() { |
|
if (detectedObjects.length === 0) { |
|
detectionList.innerHTML = '<div class="text-center py-4 text-gray-400 col-span-3">No objects detected</div>'; |
|
return; |
|
} |
|
|
|
|
|
detectionList.innerHTML = ''; |
|
|
|
|
|
detectedObjects.forEach(obj => { |
|
const item = document.createElement('div'); |
|
item.className = 'detection-item bg-gray-700 rounded-lg p-3 flex flex-col items-center'; |
|
item.innerHTML = ` |
|
<div class="w-12 h-12 rounded-full mb-2 flex items-center justify-center" style="background-color: ${obj.color}"> |
|
<i class="fas fa-box text-white text-xl"></i> |
|
</div> |
|
<div class="font-semibold">${obj.label}</div> |
|
<div class="text-sm text-gray-300">${obj.score}%</div> |
|
`; |
|
detectionList.appendChild(item); |
|
}); |
|
} |
|
|
|
|
|
window.addEventListener('DOMContentLoaded', init); |
|
</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=Prathamesh1420/live-camera-object-detection" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |