| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>VisionAI - Interactive Object Detection</title> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| :root { |
| --primary: #4361ee; |
| --secondary: #3f37c9; |
| --accent: #4cc9f0; |
| --light: #f8f9fa; |
| --dark: #212529; |
| --success: #4caf50; |
| --warning: #ff9800; |
| --danger: #f44336; |
| --shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
| --transition: all 0.3s ease; |
| } |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| } |
| |
| body { |
| background-color: #f5f7fa; |
| color: var(--dark); |
| line-height: 1.6; |
| overflow-x: hidden; |
| } |
| |
| .container { |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 20px; |
| } |
| |
| header { |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); |
| color: white; |
| padding: 20px 0; |
| text-align: center; |
| border-radius: 0 0 20px 20px; |
| box-shadow: var(--shadow); |
| margin-bottom: 30px; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| header::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPjxkZWZzPjxwYXR0ZXJuIGlkPSJwYXR0ZXJuIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiIHBhdHRlcm5UcmFuc2Zvcm09InJvdGF0ZSg0NSkiPjxyZWN0IHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0icmdiYSgyNTUsMjU1LDI1NSwwLjAzKSIvPjwvcGF0dGVybj48L2RlZnM+PHJlY3QgZmlsbD0idXJsKCNwYXR0ZXJuKSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIvPjwvc3ZnPg=='); |
| opacity: 0.5; |
| } |
| |
| header h1 { |
| font-size: 2.5rem; |
| margin-bottom: 10px; |
| position: relative; |
| animation: fadeInDown 0.8s ease; |
| } |
| |
| header p { |
| font-size: 1.1rem; |
| opacity: 0.9; |
| position: relative; |
| animation: fadeInUp 0.8s ease; |
| } |
| |
| @keyframes fadeInDown { |
| from { |
| opacity: 0; |
| transform: translateY(-30px); |
| } |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| @keyframes fadeInUp { |
| from { |
| opacity: 0; |
| transform: translateY(30px); |
| } |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| .main-content { |
| display: flex; |
| flex-direction: column; |
| gap: 30px; |
| } |
| |
| .detection-section { |
| background-color: white; |
| border-radius: 15px; |
| box-shadow: var(--shadow); |
| padding: 25px; |
| display: flex; |
| flex-direction: column; |
| gap: 20px; |
| } |
| |
| .section-title { |
| font-size: 1.5rem; |
| color: var(--primary); |
| margin-bottom: 10px; |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .section-title i { |
| font-size: 1.8rem; |
| } |
| |
| .controls { |
| display: flex; |
| flex-wrap: wrap; |
| gap: 15px; |
| margin-bottom: 20px; |
| } |
| |
| .btn { |
| background-color: var(--primary); |
| color: white; |
| border: none; |
| padding: 10px 20px; |
| border-radius: 50px; |
| cursor: pointer; |
| font-size: 1rem; |
| font-weight: 600; |
| transition: var(--transition); |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| box-shadow: var(--shadow); |
| } |
| |
| .btn:hover { |
| background-color: var(--secondary); |
| transform: translateY(-2px); |
| box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); |
| } |
| |
| .btn:active { |
| transform: translateY(0); |
| } |
| |
| .btn.btn-outline { |
| background-color: transparent; |
| border: 2px solid var(--primary); |
| color: var(--primary); |
| } |
| |
| .btn.btn-outline:hover { |
| background-color: var(--primary); |
| color: white; |
| } |
| |
| .btn.btn-success { |
| background-color: var(--success); |
| } |
| |
| .btn.btn-warning { |
| background-color: var(--warning); |
| } |
| |
| .btn.btn-danger { |
| background-color: var(--danger); |
| } |
| |
| .btn.btn-accent { |
| background-color: var(--accent); |
| } |
| |
| .btn:disabled { |
| background-color: #cccccc; |
| cursor: not-allowed; |
| transform: none; |
| box-shadow: none; |
| } |
| |
| .video-container { |
| position: relative; |
| width: 100%; |
| max-width: 800px; |
| margin: 0 auto; |
| border-radius: 10px; |
| overflow: hidden; |
| box-shadow: var(--shadow); |
| } |
| |
| #video { |
| width: 100%; |
| display: block; |
| background-color: #e9ecef; |
| } |
| |
| #canvas { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| } |
| |
| .file-input { |
| display: none; |
| } |
| |
| .file-label { |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| padding: 12px 20px; |
| background-color: var(--primary); |
| color: white; |
| border-radius: 50px; |
| cursor: pointer; |
| transition: var(--transition); |
| box-shadow: var(--shadow); |
| max-width: 300px; |
| } |
| |
| .file-label:hover { |
| background-color: var(--secondary); |
| transform: translateY(-2px); |
| } |
| |
| .detection-results { |
| margin-top: 20px; |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |
| gap: 15px; |
| } |
| |
| .detection-card { |
| background-color: white; |
| border-radius: 10px; |
| padding: 15px; |
| box-shadow: var(--shadow); |
| transition: var(--transition); |
| } |
| |
| .detection-card:hover { |
| transform: translateY(-5px); |
| box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); |
| } |
| |
| .detection-label { |
| font-weight: bold; |
| color: var(--primary); |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| margin-bottom: 5px; |
| } |
| |
| .detection-confidence { |
| height: 6px; |
| background-color: #e9ecef; |
| border-radius: 3px; |
| margin-bottom: 8px; |
| overflow: hidden; |
| } |
| |
| .confidence-bar { |
| height: 100%; |
| background: linear-gradient(90deg, var(--accent), var(--primary)); |
| border-radius: 3px; |
| } |
| |
| .detection-stats { |
| display: flex; |
| justify-content: space-between; |
| font-size: 0.8rem; |
| color: #6c757d; |
| } |
| |
| .loading { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| gap: 15px; |
| padding: 30px; |
| background-color: rgba(255, 255, 255, 0.8); |
| border-radius: 10px; |
| margin: 20px 0; |
| } |
| |
| .spinner { |
| width: 40px; |
| height: 40px; |
| border: 4px solid rgba(67, 97, 238, 0.2); |
| border-top-color: var(--primary); |
| border-radius: 50%; |
| animation: spin 1s linear infinite; |
| } |
| |
| @keyframes spin { |
| to { |
| transform: rotate(360deg); |
| } |
| } |
| |
| .stats-container { |
| display: flex; |
| gap: 20px; |
| flex-wrap: wrap; |
| } |
| |
| .stat-card { |
| flex: 1; |
| min-width: 150px; |
| background-color: white; |
| border-radius: 10px; |
| padding: 20px; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| box-shadow: var(--shadow); |
| transition: var(--transition); |
| } |
| |
| .stat-card:hover { |
| transform: translateY(-5px); |
| box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15); |
| } |
| |
| .stat-value { |
| font-size: 2.5rem; |
| font-weight: bold; |
| color: var(--primary); |
| line-height: 1; |
| } |
| |
| .stat-label { |
| font-size: 0.9rem; |
| color: #6c757d; |
| text-align: center; |
| } |
| |
| footer { |
| text-align: center; |
| padding: 30px 0; |
| margin-top: 50px; |
| color: #6c757d; |
| font-size: 0.9rem; |
| } |
| |
| @media (max-width: 768px) { |
| header h1 { |
| font-size: 2rem; |
| } |
| |
| .controls { |
| flex-direction: column; |
| align-items: stretch; |
| } |
| |
| .file-label { |
| max-width: 100%; |
| } |
| |
| .detection-results { |
| grid-template-columns: 1fr 1fr; |
| } |
| |
| .stats-container { |
| flex-direction: column; |
| } |
| } |
| |
| |
| .toggle-container { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .switch { |
| position: relative; |
| display: inline-block; |
| width: 60px; |
| height: 34px; |
| } |
| |
| .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: var(--primary); |
| } |
| |
| input:focus + .slider { |
| box-shadow: 0 0 1px var(--primary); |
| } |
| |
| input:checked + .slider:before { |
| transform: translateX(26px); |
| } |
| |
| |
| .select-container { |
| position: relative; |
| min-width: 200px; |
| } |
| |
| .custom-select { |
| appearance: none; |
| -webkit-appearance: none; |
| -moz-appearance: none; |
| width: 100%; |
| padding: 10px 20px; |
| border: 2px solid #e9ecef; |
| border-radius: 50px; |
| background-color: white; |
| font-size: 1rem; |
| font-weight: 600; |
| color: var(--dark); |
| cursor: pointer; |
| transition: var(--transition); |
| box-shadow: var(--shadow); |
| } |
| |
| .custom-select:focus { |
| outline: none; |
| border-color: var(--primary); |
| } |
| |
| .select-container::after { |
| content: "\f078"; |
| font-family: "Font Awesome 6 Free"; |
| font-weight: 900; |
| position: absolute; |
| top: 50%; |
| right: 20px; |
| transform: translateY(-50%); |
| pointer-events: none; |
| color: var(--primary); |
| } |
| </style> |
| </head> |
| <body> |
| <header> |
| <div class="container"> |
| <h1><i class="fas fa-eye"></i> VisionAI</h1> |
| <p>Real-time interactive object detection powered by TensorFlow.js</p> |
| </div> |
| </header> |
|
|
| <div class="container"> |
| <div class="main-content"> |
| <section class="detection-section"> |
| <h2 class="section-title"><i class="fas fa-camera"></i> Real-time Detection</h2> |
| <div class="controls"> |
| <button id="startBtn" class="btn btn-success"> |
| <i class="fas fa-play"></i> Start Webcam |
| </button> |
| <button id="stopBtn" class="btn btn-danger" disabled> |
| <i class="fas fa-stop"></i> Stop Webcam |
| </button> |
| <div class="toggle-container"> |
| <span>Detection:</span> |
| <label class="switch"> |
| <input type="checkbox" id="detectToggle" checked> |
| <span class="slider"></span> |
| </label> |
| </div> |
| <div class="select-container"> |
| <select id="modelSelect" class="custom-select"> |
| <option value="lite">Lite Model (Fast)</option> |
| <option value="default" selected>Default Model (Balanced)</option> |
| <option value="heavy">Heavy Model (Accurate)</option> |
| </select> |
| </div> |
| </div> |
| <div class="video-container"> |
| <video id="video" autoplay muted playsinline></video> |
| <canvas id="canvas"></canvas> |
| </div> |
| <div class="loading" id="modelLoading" style="display: none;"> |
| <div class="spinner"></div> |
| <p>Loading AI model. Please wait...</p> |
| </div> |
| <div id="statsContainer" class="stats-container"> |
| <div class="stat-card"> |
| <div class="stat-value" id="detectionCount">0</div> |
| <div class="stat-label">Objects Detected</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-value" id="fpsCount">0</div> |
| <div class="stat-label">FPS</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-value" id="avgConfidence">0%</div> |
| <div class="stat-label">Avg Confidence</div> |
| </div> |
| </div> |
| </section> |
|
|
| <section class="detection-section"> |
| <h2 class="section-title"><i class="fas fa-image"></i> Image Detection</h2> |
| <div class="controls"> |
| <label for="fileInput" class="file-label"> |
| <i class="fas fa-upload"></i> Upload Image |
| </label> |
| <input type="file" id="fileInput" accept="image/*" class="file-input"> |
| <button id="detectBtn" class="btn btn-accent" disabled> |
| <i class="fas fa-search"></i> Detect Objects |
| </button> |
| <button id="clearBtn" class="btn btn-outline"> |
| <i class="fas fa-trash-alt"></i> Clear |
| </button> |
| </div> |
| <div id="imageContainer" style="display: flex; justify-content: center; margin-bottom: 20px;"> |
| <img id="uploadedImage" style="max-width: 100%; border-radius: 10px; display: none; box-shadow: var(--shadow);"> |
| <canvas id="imageCanvas" style="display: none; max-width: 100%; border-radius: 10px; box-shadow: var(--shadow);"></canvas> |
| </div> |
| <div class="loading" id="imageLoading" style="display: none;"> |
| <div class="spinner"></div> |
| <p>Detecting objects in image...</p> |
| </div> |
| <div id="imageResultsContainer"> |
| <h3 class="section-title"><i class="fas fa-list"></i> Detection Results</h3> |
| <div id="detectionResults" class="detection-results"></div> |
| </div> |
| </section> |
| </div> |
| </div> |
|
|
| <footer> |
| <div class="container"> |
| <p>Interactive Object Detection Demo using TensorFlow.js COCO-SSD model</p> |
| <p>Created with <i class="fas fa-heart" style="color: var(--danger);"></i> for computer vision enthusiasts</p> |
| </div> |
| </footer> |
|
|
| |
| <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.11.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> |
|
|
| <script> |
| |
| const video = document.getElementById('video'); |
| const canvas = document.getElementById('canvas'); |
| const ctx = canvas.getContext('2d'); |
| const startBtn = document.getElementById('startBtn'); |
| const stopBtn = document.getElementById('stopBtn'); |
| const detectToggle = document.getElementById('detectToggle'); |
| const modelSelect = document.getElementById('modelSelect'); |
| const modelLoading = document.getElementById('modelLoading'); |
| const detectionCount = document.getElementById('detectionCount'); |
| const fpsCount = document.getElementById('fpsCount'); |
| const avgConfidence = document.getElementById('avgConfidence'); |
| const fileInput = document.getElementById('fileInput'); |
| const uploadedImage = document.getElementById('uploadedImage'); |
| const imageCanvas = document.getElementById('imageCanvas'); |
| const imageCtx = imageCanvas.getContext('2d'); |
| const detectBtn = document.getElementById('detectBtn'); |
| const clearBtn = document.getElementById('clearBtn'); |
| const imageLoading = document.getElementById('imageLoading'); |
| const detectionResults = document.getElementById('detectionResults'); |
| |
| |
| let model = null; |
| let stream = null; |
| let detectionActive = true; |
| let lastTime = 0; |
| let frameCount = 0; |
| let fps = 0; |
| let objectsDetected = 0; |
| let totalConfidence = 0; |
| |
| |
| async function init() { |
| |
| modelLoading.style.display = 'flex'; |
| try { |
| model = await cocoSsd.load({ |
| base: modelSelect.value |
| }); |
| console.log('Model loaded successfully'); |
| } catch (error) { |
| console.error('Error loading model:', error); |
| alert('Failed to load the AI model. Please try again.'); |
| } |
| modelLoading.style.display = 'none'; |
| |
| |
| startBtn.addEventListener('click', startWebcam); |
| stopBtn.addEventListener('click', stopWebcam); |
| detectToggle.addEventListener('change', toggleDetection); |
| modelSelect.addEventListener('change', reloadModel); |
| |
| fileInput.addEventListener('change', handleFileUpload); |
| detectBtn.addEventListener('click', detectInImage); |
| clearBtn.addEventListener('click', clearImageDetection); |
| |
| |
| requestAnimationFrame(updateFPS); |
| } |
| |
| |
| async function startWebcam() { |
| try { |
| stream = await navigator.mediaDevices.getUserMedia({ |
| video: { |
| facingMode: 'environment', |
| width: { ideal: 1280 }, |
| height: { ideal: 720 } |
| }, |
| audio: false |
| }); |
| video.srcObject = stream; |
| startBtn.disabled = true; |
| stopBtn.disabled = false; |
| |
| |
| video.onloadedmetadata = () => { |
| |
| canvas.width = video.videoWidth; |
| canvas.height = video.videoHeight; |
| |
| |
| detectObjectsInVideo(); |
| }; |
| } catch (error) { |
| console.error('Error accessing webcam:', error); |
| alert('Could not access the webcam. Make sure you have granted camera permissions.'); |
| } |
| } |
| |
| function stopWebcam() { |
| if (stream) { |
| stream.getTracks().forEach(track => track.stop()); |
| video.srcObject = null; |
| startBtn.disabled = false; |
| stopBtn.disabled = true; |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| } |
| } |
| |
| |
| async function detectObjectsInVideo() { |
| if (!stream || !detectionActive || !model) return; |
| |
| try { |
| |
| const predictions = await model.detect(video); |
| |
| |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| |
| |
| drawDetections(predictions, ctx); |
| |
| |
| objectsDetected = predictions.length; |
| detectionCount.textContent = objectsDetected; |
| |
| |
| if (predictions.length > 0) { |
| totalConfidence = predictions.reduce((sum, pred) => sum + pred.score, 0); |
| avgConfidence.textContent = Math.round((totalConfidence / predictions.length) * 100) + '%'; |
| } else { |
| avgConfidence.textContent = '0%'; |
| } |
| |
| |
| requestAnimationFrame(detectObjectsInVideo); |
| } catch (error) { |
| console.error('Detection error:', error); |
| setTimeout(detectObjectsInVideo, 1000); |
| } |
| } |
| |
| |
| async function detectInImage() { |
| if (!uploadedImage.style.display || !model) return; |
| |
| imageLoading.style.display = 'flex'; |
| detectBtn.disabled = true; |
| |
| try { |
| |
| imageCanvas.width = uploadedImage.width; |
| imageCanvas.height = uploadedImage.height; |
| |
| |
| imageCtx.drawImage(uploadedImage, 0, 0, imageCanvas.width, imageCanvas.height); |
| |
| |
| const predictions = await model.detect(imageCanvas); |
| |
| |
| uploadedImage.style.display = 'none'; |
| imageCanvas.style.display = 'block'; |
| |
| |
| drawDetections(predictions, imageCtx); |
| |
| |
| displayDetectionResults(predictions); |
| } catch (error) { |
| console.error('Image detection error:', error); |
| alert('Error detecting objects in image. Please try again.'); |
| } |
| |
| imageLoading.style.display = 'none'; |
| detectBtn.disabled = false; |
| } |
| |
| |
| function drawDetections(predictions, context) { |
| predictions.forEach(prediction => { |
| |
| context.beginPath(); |
| context.rect( |
| prediction.bbox[0], |
| prediction.bbox[1], |
| prediction.bbox[2], |
| prediction.bbox[3] |
| ); |
| context.lineWidth = 3; |
| context.strokeStyle = '#' + Math.floor(Math.random()*16777215).toString(16); |
| context.fillStyle = '#' + Math.floor(Math.random()*16777215).toString(16); |
| context.stroke(); |
| |
| |
| context.font = '16px Arial'; |
| const textWidth = context.measureText(`${prediction.class} ${(prediction.score * 100).toFixed(1)}%`).width; |
| context.fillRect( |
| prediction.bbox[0], |
| prediction.bbox[1] - 25, |
| textWidth + 10, |
| 25 |
| ); |
| |
| |
| context.fillStyle = '#ffffff'; |
| context.fillText( |
| `${prediction.class} ${(prediction.score * 100).toFixed(1)}%`, |
| prediction.bbox[0] + 5, |
| prediction.bbox[1] - 7 |
| ); |
| }); |
| } |
| |
| function displayDetectionResults(predictions) { |
| |
| detectionResults.innerHTML = ''; |
| |
| |
| const classCounts = {}; |
| const classConfidences = {}; |
| |
| predictions.forEach(pred => { |
| if (!classCounts[pred.class]) { |
| classCounts[pred.class] = 0; |
| classConfidences[pred.class] = 0; |
| } |
| classCounts[pred.class]++; |
| classConfidences[pred.class] += pred.score; |
| }); |
| |
| |
| Object.keys(classCounts).forEach(className => { |
| const count = classCounts[className]; |
| const avgConfidence = (classConfidences[className] / count) * 100; |
| |
| const card = document.createElement('div'); |
| card.className = 'detection-card'; |
| card.innerHTML = ` |
| <div class="detection-label"> |
| <i class="fas fa-tag"></i> ${className} |
| </div> |
| <div class="detection-confidence"> |
| <div class="confidence-bar" style="width: ${avgConfidence}%"></div> |
| </div> |
| <div class="detection-stats"> |
| <span>${count}x</span> |
| <span>${avgConfidence.toFixed(1)}%</span> |
| </div> |
| `; |
| |
| detectionResults.appendChild(card); |
| }); |
| |
| |
| if (predictions.length === 0) { |
| const noResults = document.createElement('div'); |
| noResults.className = 'detection-card'; |
| noResults.textContent = 'No objects detected'; |
| detectionResults.appendChild(noResults); |
| } |
| } |
| |
| function toggleDetection() { |
| detectionActive = detectToggle.checked; |
| if (detectionActive && stream) { |
| detectObjectsInVideo(); |
| } else { |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| } |
| } |
| |
| async function reloadModel() { |
| modelLoading.style.display = 'flex'; |
| if (model) { |
| model.dispose(); |
| } |
| try { |
| model = await cocoSsd.load({ |
| base: modelSelect.value |
| }); |
| console.log('Model reloaded with:', modelSelect.value); |
| } catch (error) { |
| console.error('Error reloading model:', error); |
| } |
| modelLoading.style.display = 'none'; |
| |
| |
| if (detectionActive && stream) { |
| detectObjectsInVideo(); |
| } |
| |
| |
| if (uploadedImage.style.display === 'block') { |
| detectInImage(); |
| } |
| } |
| |
| function handleFileUpload(event) { |
| const file = event.target.files[0]; |
| if (file) { |
| const reader = new FileReader(); |
| reader.onload = function(e) { |
| uploadedImage.src = e.target.result; |
| uploadedImage.style.display = 'block'; |
| imageCanvas.style.display = 'none'; |
| detectBtn.disabled = false; |
| }; |
| reader.readAsDataURL(file); |
| } |
| } |
| |
| function clearImageDetection() { |
| uploadedImage.src = ''; |
| uploadedImage.style.display = 'none'; |
| imageCanvas.style.display = 'none'; |
| detectBtn.disabled = true; |
| fileInput.value = ''; |
| detectionResults.innerHTML = ''; |
| } |
| |
| function updateFPS(timestamp) { |
| frameCount++; |
| |
| if (timestamp >= lastTime + 1000) { |
| fps = Math.round((frameCount * 1000) / (timestamp - lastTime)); |
| fpsCount.textContent = fps; |
| frameCount = 0; |
| lastTime = timestamp; |
| } |
| |
| requestAnimationFrame(updateFPS); |
| } |
| |
| |
| document.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 <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> |
| </html> |