Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>MicroMind Vision Sentinel</title> | |
| <style> | |
| body { font-family: sans-serif; background: #111; color: white; text-align: center; } | |
| #container { position: relative; display: inline-block; margin-top: 20px; } | |
| video { border: 2px solid #333; border-radius: 8px; } | |
| canvas { position: absolute; top: 0; left: 0; pointer-events: none; } | |
| #status { margin: 10px; font-weight: bold; color: #0f0; } | |
| #config { color: #888; font-size: 0.9em; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>👁️ MicroMind Vision Sentinel</h1> | |
| <div id="status">System Status: CONNECTING...</div> | |
| <div id="config">Target: Unknown | Conf: 0.0</div> | |
| <div id="container"> | |
| <video id="video" width="640" height="480" autoplay playsinline></video> | |
| <canvas id="canvas" width="640" height="480"></canvas> | |
| </div> | |
| <script> | |
| const video = document.getElementById('video'); | |
| const canvas = document.getElementById('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const statusEl = document.getElementById('status'); | |
| const configEl = document.getElementById('config'); | |
| // 1. Start Webcam | |
| navigator.mediaDevices.getUserMedia({ video: true }) | |
| .then(stream => { video.srcObject = stream; }) | |
| .catch(err => { console.error("Camera Error:", err); statusEl.innerText = "CAMERA ERROR"; statusEl.style.color = "red"; }); | |
| // 2. Processing Loop | |
| setInterval(() => { | |
| if (video.readyState === video.HAVE_ENOUGH_DATA) { | |
| // Draw video frame to hidden canvas to convert to blob | |
| const tempCanvas = document.createElement('canvas'); | |
| tempCanvas.width = video.videoWidth; | |
| tempCanvas.height = video.videoHeight; | |
| tempCanvas.getContext('2d').drawImage(video, 0, 0); | |
| tempCanvas.toBlob(blob => { | |
| const formData = new FormData(); | |
| formData.append('image', blob); | |
| // Send to Python Backend | |
| fetch('/process_frame', { method: 'POST', body: formData }) | |
| .then(res => res.json()) | |
| .then(data => { | |
| // Update UI | |
| statusEl.innerText = `STATUS: ${data.status}`; | |
| statusEl.style.color = data.status === "ARMED" ? "#0f0" : "#fa0"; | |
| if (data.target) { | |
| configEl.innerText = `Target: ${data.target.toUpperCase()}`; | |
| } | |
| // Clear previous drawings | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Draw detections | |
| if (data.detections) { | |
| data.detections.forEach(det => { | |
| const [x1, y1, x2, y2] = det.bbox; | |
| // Scale coordinates if video size differs from canvas | |
| const scaleX = canvas.width / video.videoWidth; | |
| const scaleY = canvas.height / video.videoHeight; | |
| ctx.strokeStyle = '#00FF00'; | |
| ctx.lineWidth = 3; | |
| ctx.strokeRect(x1 * scaleX, y1 * scaleY, (x2-x1) * scaleX, (y2-y1) * scaleY); | |
| ctx.fillStyle = '#00FF00'; | |
| ctx.font = '16px Arial'; | |
| ctx.fillText(`${det.label} (${det.confidence})`, x1 * scaleX, (y1 * scaleY) - 5); | |
| }); | |
| } | |
| }) | |
| .catch(err => console.error("API Error", err)); | |
| }, 'image/jpeg'); | |
| } | |
| }, 500); // Process every 500ms (2 FPS) | |
| </script> | |
| </body> | |
| </html> |