|
<!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/[email protected]/dist/tf.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/[email protected]/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> |