Mousco commited on
Commit
100d48c
·
verified ·
1 Parent(s): 2e3fabd

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +739 -19
index.html CHANGED
@@ -1,19 +1,739 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Suivi d'Objet en Temps Réel - Vision IA</title>
7
+
8
+ <!-- Importation de polices et d'icônes pour le style -->
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+
12
+ <style>
13
+ :root {
14
+ --primary: #00f2ff;
15
+ --primary-dim: rgba(0, 242, 255, 0.1);
16
+ --secondary: #ff0055;
17
+ --bg-dark: #0a0e17;
18
+ --bg-panel: #111827;
19
+ --text-main: #e2e8f0;
20
+ --text-muted: #94a3b8;
21
+ --border: #1e293b;
22
+ --success: #10b981;
23
+ }
24
+
25
+ * {
26
+ box-sizing: border-box;
27
+ margin: 0;
28
+ padding: 0;
29
+ }
30
+
31
+ body {
32
+ font-family: 'Inter', sans-serif;
33
+ background-color: var(--bg-dark);
34
+ color: var(--text-main);
35
+ height: 100vh;
36
+ display: flex;
37
+ flex-direction: column;
38
+ overflow: hidden;
39
+ }
40
+
41
+ /* En-tête */
42
+ header {
43
+ background: var(--bg-panel);
44
+ border-bottom: 1px solid var(--border);
45
+ padding: 1rem 2rem;
46
+ display: flex;
47
+ justify-content: space-between;
48
+ align-items: center;
49
+ z-index: 10;
50
+ }
51
+
52
+ .brand {
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 10px;
56
+ font-weight: 600;
57
+ font-size: 1.2rem;
58
+ color: var(--primary);
59
+ text-transform: uppercase;
60
+ letter-spacing: 1px;
61
+ }
62
+
63
+ .brand i {
64
+ font-size: 1.4rem;
65
+ }
66
+
67
+ .anycoder-link {
68
+ font-size: 0.85rem;
69
+ color: var(--text-muted);
70
+ text-decoration: none;
71
+ border: 1px solid var(--border);
72
+ padding: 0.4rem 0.8rem;
73
+ border-radius: 4px;
74
+ transition: all 0.3s ease;
75
+ }
76
+
77
+ .anycoder-link:hover {
78
+ color: var(--primary);
79
+ border-color: var(--primary);
80
+ box-shadow: 0 0 10px var(--primary-dim);
81
+ }
82
+
83
+ /* Layout Principal */
84
+ main {
85
+ flex: 1;
86
+ display: grid;
87
+ grid-template-columns: 1fr 320px;
88
+ gap: 0;
89
+ height: calc(100vh - 70px);
90
+ }
91
+
92
+ /* Zone de la Caméra / Canvas */
93
+ .viewport {
94
+ position: relative;
95
+ background: #000;
96
+ display: flex;
97
+ justify-content: center;
98
+ align-items: center;
99
+ overflow: hidden;
100
+ }
101
+
102
+ video {
103
+ display: none; /* Vidéo cachée, on affiche le canvas traité */
104
+ }
105
+
106
+ canvas {
107
+ max-width: 100%;
108
+ max-height: 100%;
109
+ cursor: crosshair;
110
+ }
111
+
112
+ .overlay-msg {
113
+ position: absolute;
114
+ color: var(--text-muted);
115
+ text-align: center;
116
+ pointer-events: none;
117
+ }
118
+
119
+ .overlay-msg i {
120
+ font-size: 3rem;
121
+ margin-bottom: 1rem;
122
+ opacity: 0.5;
123
+ }
124
+
125
+ /* Panneau de Contrôle */
126
+ .controls {
127
+ background: var(--bg-panel);
128
+ border-left: 1px solid var(--border);
129
+ padding: 1.5rem;
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: 1.5rem;
133
+ overflow-y: auto;
134
+ }
135
+
136
+ .panel-section {
137
+ background: rgba(255, 255, 255, 0.03);
138
+ border: 1px solid var(--border);
139
+ border-radius: 8px;
140
+ padding: 1rem;
141
+ }
142
+
143
+ .panel-title {
144
+ font-size: 0.85rem;
145
+ text-transform: uppercase;
146
+ color: var(--text-muted);
147
+ margin-bottom: 1rem;
148
+ font-weight: 600;
149
+ letter-spacing: 0.5px;
150
+ display: flex;
151
+ justify-content: space-between;
152
+ }
153
+
154
+ /* Boutons */
155
+ .btn {
156
+ width: 100%;
157
+ padding: 0.8rem;
158
+ border: none;
159
+ border-radius: 6px;
160
+ font-weight: 600;
161
+ cursor: pointer;
162
+ transition: all 0.2s;
163
+ display: flex;
164
+ justify-content: center;
165
+ align-items: center;
166
+ gap: 8px;
167
+ font-family: inherit;
168
+ }
169
+
170
+ .btn-primary {
171
+ background: var(--primary);
172
+ color: #000;
173
+ }
174
+
175
+ .btn-primary:hover {
176
+ background: #6ff9ff;
177
+ box-shadow: 0 0 15px var(--primary-dim);
178
+ }
179
+
180
+ .btn-danger {
181
+ background: rgba(255, 0, 85, 0.1);
182
+ color: var(--secondary);
183
+ border: 1px solid var(--secondary);
184
+ }
185
+
186
+ .btn-danger:hover {
187
+ background: var(--secondary);
188
+ color: #fff;
189
+ }
190
+
191
+ /* Sliders */
192
+ .control-group {
193
+ margin-bottom: 1rem;
194
+ }
195
+
196
+ .control-group label {
197
+ display: flex;
198
+ justify-content: space-between;
199
+ font-size: 0.9rem;
200
+ margin-bottom: 0.5rem;
201
+ }
202
+
203
+ input[type="range"] {
204
+ width: 100%;
205
+ height: 6px;
206
+ background: var(--border);
207
+ border-radius: 3px;
208
+ outline: none;
209
+ -webkit-appearance: none;
210
+ }
211
+
212
+ input[type="range"]::-webkit-slider-thumb {
213
+ -webkit-appearance: none;
214
+ width: 16px;
215
+ height: 16px;
216
+ background: var(--primary);
217
+ border-radius: 50%;
218
+ cursor: pointer;
219
+ transition: transform 0.1s;
220
+ }
221
+
222
+ input[type="range"]::-webkit-slider-thumb:hover {
223
+ transform: scale(1.2);
224
+ }
225
+
226
+ /* Indicateurs de données */
227
+ .data-grid {
228
+ display: grid;
229
+ grid-template-columns: 1fr 1fr;
230
+ gap: 0.5rem;
231
+ }
232
+
233
+ .data-item {
234
+ background: rgba(0, 0, 0, 0.2);
235
+ padding: 0.5rem;
236
+ border-radius: 4px;
237
+ }
238
+
239
+ .data-label {
240
+ font-size: 0.7rem;
241
+ color: var(--text-muted);
242
+ }
243
+
244
+ .data-value {
245
+ font-family: 'JetBrains Mono', monospace;
246
+ font-size: 1rem;
247
+ color: var(--primary);
248
+ }
249
+
250
+ /* Indicateur de couleur cible */
251
+ .target-color-preview {
252
+ width: 100%;
253
+ height: 40px;
254
+ border-radius: 4px;
255
+ border: 1px solid var(--border);
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ color: #fff;
260
+ text-shadow: 0 1px 3px rgba(0,0,0,0.8);
261
+ font-weight: bold;
262
+ font-size: 0.9rem;
263
+ margin-bottom: 1rem;
264
+ transition: background 0.2s;
265
+ }
266
+
267
+ /* Toast de notification */
268
+ .toast-container {
269
+ position: fixed;
270
+ bottom: 20px;
271
+ left: 50%;
272
+ transform: translateX(-50%);
273
+ z-index: 100;
274
+ display: flex;
275
+ flex-direction: column;
276
+ gap: 10px;
277
+ }
278
+
279
+ .toast {
280
+ background: rgba(17, 24, 39, 0.9);
281
+ border: 1px solid var(--primary);
282
+ color: var(--text-main);
283
+ padding: 10px 20px;
284
+ border-radius: 4px;
285
+ font-size: 0.9rem;
286
+ backdrop-filter: blur(5px);
287
+ animation: slideUp 0.3s ease-out forwards;
288
+ display: flex;
289
+ align-items: center;
290
+ gap: 10px;
291
+ }
292
+
293
+ @keyframes slideUp {
294
+ from { opacity: 0; transform: translateY(20px); }
295
+ to { opacity: 1; transform: translateY(0); }
296
+ }
297
+
298
+ /* Responsive Design */
299
+ @media (max-width: 900px) {
300
+ main {
301
+ grid-template-columns: 1fr;
302
+ grid-template-rows: 1fr auto;
303
+ overflow-y: auto;
304
+ }
305
+
306
+ .controls {
307
+ border-left: none;
308
+ border-top: 1px solid var(--border);
309
+ height: auto;
310
+ }
311
+ }
312
+ </style>
313
+ </head>
314
+ <body>
315
+
316
+ <header>
317
+ <div class="brand">
318
+ <i class="fa-solid fa-crosshairs"></i>
319
+ <span>Vision Tracker</span>
320
+ </div>
321
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
322
+ Built with anycoder <i class="fa-solid fa-external-link-alt" style="font-size: 0.7em; margin-left:5px;"></i>
323
+ </a>
324
+ </header>
325
+
326
+ <main>
327
+ <!-- Zone d'affichage -->
328
+ <section class="viewport" id="viewport">
329
+ <div class="overlay-msg" id="placeholder">
330
+ <i class="fa-solid fa-video-slash"></i>
331
+ <p>Caméra arrêtée</p>
332
+ </div>
333
+ <video id="video" playsinline></video>
334
+ <canvas id="canvas"></canvas>
335
+ </section>
336
+
337
+ <!-- Panneau de contrôle -->
338
+ <aside class="controls">
339
+
340
+ <div class="panel-section">
341
+ <div class="panel-title">Contrôle Caméra</div>
342
+ <button id="btn-toggle-cam" class="btn btn-primary">
343
+ <i class="fa-solid fa-power-off"></i> Démarrer Caméra
344
+ </button>
345
+ </div>
346
+
347
+ <div class="panel-section">
348
+ <div class="panel-title">
349
+ Configuration Tracking
350
+ <i class="fa-solid fa-sliders"></i>
351
+ </div>
352
+
353
+ <div class="target-color-preview" id="color-preview" style="background-color: #333;">
354
+ Aucune couleur sélectionnée
355
+ </div>
356
+ <p style="font-size: 0.8rem; color: var(--text-muted); margin-bottom: 1rem;">
357
+ <i class="fa-solid fa-circle-info"></i> Cliquez sur l'image pour choisir la cible.
358
+ </p>
359
+
360
+ <div class="control-group">
361
+ <label>
362
+ Tolérance (Sensibilité)
363
+ <span id="tolerance-val">40</span>
364
+ </label>
365
+ <input type="range" id="tolerance" min="5" max="150" value="40">
366
+ </div>
367
+
368
+ <div class="control-group">
369
+ <label>
370
+ Taille Min. Objet (px)
371
+ <span id="min-size-val">500</span>
372
+ </label>
373
+ <input type="range" id="min-size" min="100" max="5000" value="500">
374
+ </div>
375
+ </div>
376
+
377
+ <div class="panel-section">
378
+ <div class="panel-title">
379
+ Télémétrie
380
+ <i class="fa-solid fa-satellite-dish"></i>
381
+ </div>
382
+ <div class="data-grid">
383
+ <div class="data-item">
384
+ <div class="data-label">Position X</div>
385
+ <div class="data-value" id="pos-x">0</div>
386
+ </div>
387
+ <div class="data-item">
388
+ <div class="data-label">Position Y</div>
389
+ <div class="data-value" id="pos-y">0</div>
390
+ </div>
391
+ <div class="data-item">
392
+ <div class="data-label">Surface (px²)</div>
393
+ <div class="data-value" id="surface">0</div>
394
+ </div>
395
+ <div class="data-item">
396
+ <div class="data-label">FPS</div>
397
+ <div class="data-value" id="fps">0</div>
398
+ </div>
399
+ </div>
400
+ </div>
401
+
402
+ <div class="panel-section">
403
+ <div class="panel-title">Debug</div>
404
+ <label style="display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: 0.9rem;">
405
+ <input type="checkbox" id="show-mask"> Afficher le masque de couleur
406
+ </label>
407
+ </div>
408
+
409
+ </aside>
410
+ </main>
411
+
412
+ <div class="toast-container" id="toast-container"></div>
413
+
414
+ <script>
415
+ /**
416
+ * Logique de l'application de suivi d'objet
417
+ * Utilise l'API Canvas pour analyser les pixels de la vidéo en temps réel.
418
+ */
419
+
420
+ // Éléments du DOM
421
+ const video = document.getElementById('video');
422
+ const canvas = document.getElementById('canvas');
423
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
424
+ const viewport = document.getElementById('viewport');
425
+ const placeholder = document.getElementById('placeholder');
426
+
427
+ // Contrôles
428
+ const btnToggleCam = document.getElementById('btn-toggle-cam');
429
+ const toleranceInput = document.getElementById('tolerance');
430
+ const toleranceVal = document.getElementById('tolerance-val');
431
+ const minSizeInput = document.getElementById('min-size');
432
+ const minSizeVal = document.getElementById('min-size-val');
433
+ const colorPreview = document.getElementById('color-preview');
434
+ const showMaskCheckbox = document.getElementById('show-mask');
435
+
436
+ // Affichage données
437
+ const posXDisplay = document.getElementById('pos-x');
438
+ const posYDisplay = document.getElementById('pos-y');
439
+ const surfaceDisplay = document.getElementById('surface');
440
+ const fpsDisplay = document.getElementById('fps');
441
+
442
+ // État de l'application
443
+ let isStreaming = false;
444
+ let animationId = null;
445
+ let targetColor = null; // {r, g, b}
446
+ let lastFrameTime = 0;
447
+
448
+ // Configuration
449
+ let config = {
450
+ tolerance: 40,
451
+ minObjectSize: 500,
452
+ scanStep: 4 // Performance : vérifier 1 pixel sur 4
453
+ };
454
+
455
+ // Gestion des notifications (Toast)
456
+ function showToast(message, type = 'info') {
457
+ const container = document.getElementById('toast-container');
458
+ const toast = document.createElement('div');
459
+ toast.className = 'toast';
460
+
461
+ let icon = '<i class="fa-solid fa-info-circle"></i>';
462
+ if(type === 'success') icon = '<i class="fa-solid fa-check-circle"></i>';
463
+ if(type === 'error') icon = '<i class="fa-solid fa-exclamation-triangle"></i>';
464
+
465
+ toast.innerHTML = `${icon} <span>${message}</span>`;
466
+ container.appendChild(toast);
467
+
468
+ setTimeout(() => {
469
+ toast.style.opacity = '0';
470
+ toast.style.transform = 'translateY(20px)';
471
+ setTimeout(() => toast.remove(), 300);
472
+ }, 3000);
473
+ }
474
+
475
+ // Démarrage / Arrêt de la caméra
476
+ btnToggleCam.addEventListener('click', async () => {
477
+ if (isStreaming) {
478
+ stopCamera();
479
+ } else {
480
+ await startCamera();
481
+ }
482
+ });
483
+
484
+ async function startCamera() {
485
+ try {
486
+ const stream = await navigator.mediaDevices.getUserMedia({
487
+ video: {
488
+ width: { ideal: 640 },
489
+ height: { ideal: 480 },
490
+ facingMode: 'user'
491
+ },
492
+ audio: false
493
+ });
494
+
495
+ video.srcObject = stream;
496
+
497
+ video.onloadedmetadata = () => {
498
+ video.play();
499
+ canvas.width = video.videoWidth;
500
+ canvas.height = video.videoHeight;
501
+ isStreaming = true;
502
+ btnToggleCam.innerHTML = '<i class="fa-solid fa-stop"></i> Arrêter Caméra';
503
+ btnToggleCam.classList.replace('btn-primary', 'btn-danger');
504
+ placeholder.style.display = 'none';
505
+
506
+ showToast("Caméra active. Cliquez sur un objet pour le suivre.", "success");
507
+
508
+ // Boucle de traitement
509
+ requestAnimationFrame(processFrame);
510
+ };
511
+ } catch (err) {
512
+ console.error("Erreur caméra:", err);
513
+ showToast("Impossible d'accéder à la caméra.", "error");
514
+ }
515
+ }
516
+
517
+ function stopCamera() {
518
+ if (video.srcObject) {
519
+ video.srcObject.getTracks().forEach(track => track.stop());
520
+ video.srcObject = null;
521
+ }
522
+ isStreaming = false;
523
+ cancelAnimationFrame(animationId);
524
+
525
+ // Nettoyer canvas
526
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
527
+
528
+ btnToggleCam.innerHTML = '<i class="fa-solid fa-power-off"></i> Démarrer Caméra';
529
+ btnToggleCam.classList.replace('btn-danger', 'btn-primary');
530
+ placeholder.style.display = 'block';
531
+
532
+ // Reset données
533
+ posXDisplay.innerText = '0';
534
+ posYDisplay.innerText = '0';
535
+ surfaceDisplay.innerText = '0';
536
+ fpsDisplay.innerText = '0';
537
+ }
538
+
539
+ // Sélection de la couleur par clic
540
+ canvas.addEventListener('click', (e) => {
541
+ if (!isStreaming) return;
542
+
543
+ const rect = canvas.getBoundingClientRect();
544
+ // Calculer l'échelle car le canvas CSS peut être redimensionné
545
+ const scaleX = canvas.width / rect.width;
546
+ const scaleY = canvas.height / rect.height;
547
+
548
+ const x = (e.clientX - rect.left) * scaleX;
549
+ const y = (e.clientY - rect.top) * scaleY;
550
+
551
+ // Lire le pixel actuel
552
+ const pixel = ctx.getImageData(x, y, 1, 1).data;
553
+ targetColor = { r: pixel[0], g: pixel[1], b: pixel[2] };
554
+
555
+ // Mettre à jour l'UI
556
+ const rgbString = `rgb(${targetColor.r}, ${targetColor.g}, ${targetColor.b})`;
557
+ colorPreview.style.backgroundColor = rgbString;
558
+ colorPreview.innerText = `Cible: ${rgbString}`;
559
+
560
+ showToast("Couleur cible verrouillée !", "success");
561
+ });
562
+
563
+ // Mise à jour de la configuration
564
+ toleranceInput.addEventListener('input', (e) => {
565
+ config.tolerance = parseInt(e.target.value);
566
+ toleranceVal.innerText = config.tolerance;
567
+ });
568
+
569
+ minSizeInput.addEventListener('input', (e) => {
570
+ config.minObjectSize = parseInt(e.target.value);
571
+ minSizeVal.innerText = config.minObjectSize;
572
+ });
573
+
574
+ // Fonction principale de traitement d'image
575
+ function processFrame(timestamp) {
576
+ if (!isStreaming) return;
577
+
578
+ // Calcul FPS
579
+ const deltaTime = timestamp - lastFrameTime;
580
+ lastFrameTime = timestamp;
581
+ if (timestamp % 10 < 1) { // Mise à jour occasionnelle
582
+ fpsDisplay.innerText = Math.round(1000 / deltaTime) || 0;
583
+ }
584
+
585
+ // 1. Dessiner l'image vidéo sur le canvas
586
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
587
+
588
+ // Si une couleur cible est définie, on lance le tracking
589
+ if (targetColor) {
590
+ trackObject();
591
+ }
592
+
593
+ animationId = requestAnimationFrame(processFrame);
594
+ }
595
+
596
+ function trackObject() {
597
+ const width = canvas.width;
598
+ const height = canvas.height;
599
+
600
+ // Obtenir les données brutes de l'image
601
+ const frameData = ctx.getImageData(0, 0, width, height);
602
+ const data = frameData.data;
603
+ const length = data.length;
604
+
605
+ let minX = width, minY = height, maxX = 0, maxY = 0;
606
+ let pixelCount = 0;
607
+
608
+ const rT = targetColor.r;
609
+ const gT = targetColor.g;
610
+ const bT = targetColor.b;
611
+ const tol = config.tolerance;
612
+ const tolSq = tol * tol * 3; // Optimisation : comparer au carré
613
+
614
+ // Scan des pixels (avec step pour perf)
615
+ for (let i = 0; i < length; i += 4 * config.scanStep) {
616
+ const r = data[i];
617
+ const g = data[i + 1];
618
+ const b = data[i + 2];
619
+
620
+ // Calcul de distance de couleur (Euclidienne simplifiée)
621
+ // On utilise une approximation rapide pour la performance
622
+ const dist = (r - rT) ** 2 + (g - gT) ** 2 + (b - bT) ** 2;
623
+
624
+ if (dist < tolSq) {
625
+ // Pixel correspondant trouvé
626
+ const pixelIndex = i / 4;
627
+ const x = pixelIndex % width;
628
+ const y = Math.floor(pixelIndex / width);
629
+
630
+ if (x < minX) minX = x;
631
+ if (x > maxX) maxX = x;
632
+ if (y < minY) minY = y;
633
+ if (y > maxY) maxY = y;
634
+
635
+ pixelCount++;
636
+
637
+ // Optionnel : dessiner le masque (pour debug)
638
+ if (showMaskCheckbox.checked) {
639
+ data[i] = 0; // R
640
+ data[i + 1] = 255; // G
641
+ data[i + 2] = 0; // B
642
+ }
643
+ }
644
+ }
645
+
646
+ // Si on a activé le masque, on remet les pixels modifiés sur le canvas
647
+ if (showMaskCheckbox.checked) {
648
+ ctx.putImageData(frameData, 0, 0);
649
+ }
650
+
651
+ // Calcul des dimensions de l'objet détecté
652
+ const boxWidth = maxX - minX;
653
+ const boxHeight = maxY - minY;
654
+ const area = boxWidth * boxHeight;
655
+
656
+ // Filtrer le bruit (surface minimale)
657
+ if (area > config.minObjectSize) {
658
+ // Dessiner l'interface utilisateur (HUD)
659
+ drawHUD(minX, minY, boxWidth, boxHeight);
660
+
661
+ // Mettre à jour les données textuelles
662
+ posXDisplay.innerText = Math.round(minX + boxWidth / 2);
663
+ posYDisplay.innerText = Math.round(minY + boxHeight / 2);
664
+ surfaceDisplay.innerText = area;
665
+ } else {
666
+ // Si perdu
667
+ surfaceDisplay.innerText = "Recherche...";
668
+ posXDisplay.innerText = "-";
669
+ posYDisplay.innerText = "-";
670
+ }
671
+ }
672
+
673
+ function drawHUD(x, y, w, h) {
674
+ const centerX = x + w / 2;
675
+ const centerY = y + h / 2;
676
+
677
+ ctx.strokeStyle = '#00f2ff';
678
+ ctx.lineWidth = 2;
679
+ ctx.shadowBlur = 10;
680
+ ctx.shadowColor = '#00f2ff';
681
+
682
+ // Dessiner les coins (Style futuriste)
683
+ const lineLen = Math.min(w, h) * 0.2;
684
+
685
+ ctx.beginPath();
686
+ // Coin haut gauche
687
+ ctx.moveTo(x, y + lineLen);
688
+ ctx.lineTo(x, y);
689
+ ctx.lineTo(x + lineLen, y);
690
+ // Coin haut droite
691
+ ctx.moveTo(x + w - lineLen, y);
692
+ ctx.lineTo(x + w, y);
693
+ ctx.lineTo(x + w, y + lineLen);
694
+ // Coin bas droite
695
+ ctx.moveTo(x + w, y + h - lineLen);
696
+ ctx.lineTo(x + w, y + h);
697
+ ctx.lineTo(x + w - lineLen, y + h);
698
+ // Coin bas gauche
699
+ ctx.moveTo(x + lineLen, y + h);
700
+ ctx.lineTo(x, y + h);
701
+ ctx.lineTo(x, y + h - lineLen);
702
+ ctx.stroke();
703
+
704
+ // Lignes de guidage vers le centre
705
+ ctx.strokeStyle = 'rgba(0, 242, 255, 0.3)';
706
+ ctx.lineWidth = 1;
707
+ ctx.beginPath();
708
+ ctx.moveTo(centerX, 0);
709
+ ctx.lineTo(centerX, canvas.height);
710
+ ctx.moveTo(0, centerY);
711
+ ctx.lineTo(canvas.width, centerY);
712
+ ctx.stroke();
713
+
714
+ // Cercle central
715
+ ctx.beginPath();
716
+ ctx.arc(centerX, centerY, 5, 0, Math.PI * 2);
717
+ ctx.fillStyle = '#ff0055';
718
+ ctx.fill();
719
+
720
+ // Texte coordonnées
721
+ ctx.fillStyle = '#fff';
722
+ ctx.font = '12px JetBrains Mono';
723
+ ctx.fillText(`X:${Math.round(centerX)} Y:${Math.round(centerY)}`, x, y - 10);
724
+
725
+ // Reset shadow
726
+ ctx.shadowBlur = 0;
727
+ }
728
+
729
+ // Gestion du redimensionnement de la fenêtre
730
+ window.addEventListener('resize', () => {
731
+ if(isStreaming) {
732
+ // Le canvas garde sa résolution interne, CSS gère l'affichage
733
+ // Mais on peut vouloir ajuster si nécessaire
734
+ }
735
+ });
736
+
737
+ </script>
738
+ </body>
739
+ </html>