mr4 commited on
Commit
e66436f
Β·
verified Β·
1 Parent(s): bdb29a5

Upload 2 files

Browse files
Files changed (2) hide show
  1. Yolo.js +11 -0
  2. index.html +220 -8
Yolo.js CHANGED
@@ -140,6 +140,17 @@ export async function loadModel(modelPath) {
140
  executionProviders: [provider],
141
  });
142
  console.log(`[ONNX] Using execution provider: ${provider}`);
 
 
 
 
 
 
 
 
 
 
 
143
  return session;
144
  } catch {
145
  // provider khΓ΄ng khαΊ£ dα»₯ng, thα»­ tiαΊΏp
 
140
  executionProviders: [provider],
141
  });
142
  console.log(`[ONNX] Using execution provider: ${provider}`);
143
+ console.log(`[ONNX] Model: ${modelPath}`);
144
+
145
+ // Ghi chú về FP16 performance
146
+ if (modelPath.includes('-fp16')) {
147
+ console.warn(
148
+ '[ONNX] FP16 models may be slower than FP32 on web browsers.\n' +
149
+ 'Reason: WebGPU/WebGL FP16 support is limited, WASM converts FP16β†’FP32.\n' +
150
+ 'Use FP32 for best performance, FP16 only for size reduction.'
151
+ );
152
+ }
153
+
154
  return session;
155
  } catch {
156
  // provider khΓ΄ng khαΊ£ dα»₯ng, thα»­ tiαΊΏp
index.html CHANGED
@@ -171,7 +171,8 @@
171
  opacity: 1;
172
  }
173
 
174
- #file-input {
 
175
  display: none;
176
  }
177
 
@@ -199,6 +200,8 @@
199
  /* Webcam */
200
  #webcam-panel { display: none; flex-direction: column; align-items: center; gap: 10px; width: 100%; }
201
  #webcam-panel.active { display: flex; }
 
 
202
  #image-panel { display: flex; flex-direction: column; align-items: center; gap: 10px; }
203
  #image-panel.hidden { display: none; }
204
 
@@ -210,6 +213,38 @@
210
  display: none;
211
  }
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  .webcam-controls {
214
  display: flex;
215
  gap: 10px;
@@ -429,6 +464,7 @@
429
  <div class="source-tabs">
430
  <button class="tab-btn active" id="tab-image">πŸ–Ό αΊ’nh</button>
431
  <button class="tab-btn" id="tab-webcam">πŸ“· Webcam</button>
 
432
  </div>
433
 
434
  <!-- Image panel -->
@@ -451,6 +487,28 @@
451
  <button class="btn-danger" id="webcam-stop-btn" disabled>β–  Dα»«ng</button>
452
  </div>
453
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  </div>
455
 
456
  <div class="canvas-area">
@@ -678,14 +736,17 @@
678
  // ── Source Tabs ───────────────────────────────────────────────────────────
679
  document.getElementById('tab-image').addEventListener('click', () => switchTab('image'));
680
  document.getElementById('tab-webcam').addEventListener('click', () => switchTab('webcam'));
 
681
 
682
  function switchTab(tab) {
683
- const isImage = tab === 'image';
684
- document.getElementById('tab-image').classList.toggle('active', isImage);
685
- document.getElementById('tab-webcam').classList.toggle('active', !isImage);
686
- document.getElementById('image-panel').classList.toggle('hidden', !isImage);
687
- document.getElementById('webcam-panel').classList.toggle('active', !isImage);
688
- if (isImage) stopWebcam();
 
 
689
  }
690
 
691
  // ── Webcam ────────────────────────────────────────────────────────────────
@@ -784,6 +845,157 @@
784
  }
785
  }
786
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
787
  // ── Magnifier ─────────────────────────────────────────────────────────────
788
  (function initMagnifier() {
789
  const magnifier = document.getElementById('magnifier');
@@ -813,7 +1025,7 @@
813
  applyLensSize(lensSize);
814
  });
815
 
816
- ['original-canvas', 'result-canvas', 'webcam-video'].forEach(id => {
817
  const el = document.getElementById(id);
818
  el.addEventListener('mouseenter', () => { magnifier.style.display = 'block'; el.style.cursor = 'crosshair'; });
819
  el.addEventListener('mouseleave', () => { magnifier.style.display = 'none'; el.style.cursor = ''; });
 
171
  opacity: 1;
172
  }
173
 
174
+ #file-input,
175
+ #video-file-input {
176
  display: none;
177
  }
178
 
 
200
  /* Webcam */
201
  #webcam-panel { display: none; flex-direction: column; align-items: center; gap: 10px; width: 100%; }
202
  #webcam-panel.active { display: flex; }
203
+ #video-panel { display: none; flex-direction: column; align-items: center; gap: 10px; width: 100%; }
204
+ #video-panel.active { display: flex; }
205
  #image-panel { display: flex; flex-direction: column; align-items: center; gap: 10px; }
206
  #image-panel.hidden { display: none; }
207
 
 
213
  display: none;
214
  }
215
 
216
+ #video-player {
217
+ max-width: 100%;
218
+ border-radius: 8px;
219
+ border: 1px solid #e0e0e0;
220
+ background: #111;
221
+ display: none;
222
+ }
223
+
224
+ .video-progress-wrap {
225
+ width: 100%;
226
+ max-width: 640px;
227
+ display: flex;
228
+ flex-direction: column;
229
+ gap: 4px;
230
+ }
231
+
232
+ #video-progress {
233
+ width: 100%;
234
+ accent-color: #1565c0;
235
+ cursor: pointer;
236
+ }
237
+
238
+ #video-progress:disabled { opacity: 0.4; cursor: default; }
239
+
240
+ .video-time {
241
+ display: flex;
242
+ justify-content: space-between;
243
+ font-size: 0.82rem;
244
+ color: #888;
245
+ padding: 0 2px;
246
+ }
247
+
248
  .webcam-controls {
249
  display: flex;
250
  gap: 10px;
 
464
  <div class="source-tabs">
465
  <button class="tab-btn active" id="tab-image">πŸ–Ό αΊ’nh</button>
466
  <button class="tab-btn" id="tab-webcam">πŸ“· Webcam</button>
467
+ <button class="tab-btn" id="tab-video">🎬 Video</button>
468
  </div>
469
 
470
  <!-- Image panel -->
 
487
  <button class="btn-danger" id="webcam-stop-btn" disabled>β–  Dα»«ng</button>
488
  </div>
489
  </div>
490
+
491
+ <!-- Video panel -->
492
+ <div id="video-panel">
493
+ <label class="file-label" for="video-file-input">
494
+ πŸ“ Chọn video (MP4, WebM, MOV)
495
+ </label>
496
+ <input type="file" id="video-file-input" accept="video/mp4,video/webm,video/quicktime" />
497
+ <video id="video-player" playsinline muted width="640" height="360"></video>
498
+ <div class="video-progress-wrap">
499
+ <input type="range" id="video-progress" min="0" max="100" step="0.1" value="0" disabled />
500
+ <div class="video-time">
501
+ <span id="video-current-time">0:00</span>
502
+ <span id="video-duration">0:00</span>
503
+ </div>
504
+ </div>
505
+ <div class="webcam-controls">
506
+ <button class="btn-secondary" id="video-play-btn" disabled>β–Ά Play</button>
507
+ <button class="btn-secondary" id="video-detect-btn" disabled>🎯 BαΊ―t Δ‘αΊ§u nhαΊ­n diện</button>
508
+ <button class="btn-secondary" id="video-capture-btn" disabled>πŸ“‹ Capture β†’ Clipboard</button>
509
+ <button class="btn-danger" id="video-stop-btn" disabled>β–  Reset</button>
510
+ </div>
511
+ </div>
512
  </div>
513
 
514
  <div class="canvas-area">
 
736
  // ── Source Tabs ───────────────────────────────────────────────────────────
737
  document.getElementById('tab-image').addEventListener('click', () => switchTab('image'));
738
  document.getElementById('tab-webcam').addEventListener('click', () => switchTab('webcam'));
739
+ document.getElementById('tab-video').addEventListener('click', () => switchTab('video'));
740
 
741
  function switchTab(tab) {
742
+ ['image', 'webcam', 'video'].forEach(t => {
743
+ document.getElementById(`tab-${t}`).classList.toggle('active', t === tab);
744
+ });
745
+ document.getElementById('image-panel').classList.toggle('hidden', tab !== 'image');
746
+ document.getElementById('webcam-panel').classList.toggle('active', tab === 'webcam');
747
+ document.getElementById('video-panel').classList.toggle('active', tab === 'video');
748
+ if (tab !== 'webcam') stopWebcam();
749
+ if (tab !== 'video') stopVideo();
750
  }
751
 
752
  // ── Webcam ────────────────────────────────────────────────────────────────
 
845
  }
846
  }
847
 
848
+ // ── Video ─────────────────────────────────────────────────────────────────
849
+ let videoObjectUrl = null, videoDetecting = false, videoRafId = null;
850
+ let videoFpsCount = 0, videoFpsLastTime = 0, videoCurrentFps = 0;
851
+
852
+ const videoPlayer = document.getElementById('video-player');
853
+ const videoProgress = document.getElementById('video-progress');
854
+ const videoCurrentEl = document.getElementById('video-current-time');
855
+ const videoDurationEl = document.getElementById('video-duration');
856
+ const videoPlayBtn = document.getElementById('video-play-btn');
857
+ const videoDetectBtn = document.getElementById('video-detect-btn');
858
+ const videoCaptureBtn = document.getElementById('video-capture-btn');
859
+ const videoStopBtn = document.getElementById('video-stop-btn');
860
+
861
+ function formatTime(s) {
862
+ const m = Math.floor(s / 60);
863
+ return `${m}:${String(Math.floor(s % 60)).padStart(2, '0')}`;
864
+ }
865
+
866
+ document.getElementById('video-file-input').addEventListener('change', function (e) {
867
+ const file = e.target.files[0];
868
+ if (!file) return;
869
+ stopVideo();
870
+ if (videoObjectUrl) URL.revokeObjectURL(videoObjectUrl);
871
+ videoObjectUrl = URL.createObjectURL(file);
872
+ videoPlayer.src = videoObjectUrl;
873
+ videoPlayer.style.display = 'block';
874
+ videoPlayer.load();
875
+ });
876
+
877
+ videoPlayer.addEventListener('loadedmetadata', () => {
878
+ videoProgress.max = videoPlayer.duration;
879
+ videoProgress.value = 0;
880
+ videoProgress.disabled = false;
881
+ videoDurationEl.textContent = formatTime(videoPlayer.duration);
882
+ videoCurrentEl.textContent = '0:00';
883
+ videoPlayBtn.disabled = false;
884
+ videoDetectBtn.disabled = false;
885
+ videoStopBtn.disabled = false;
886
+ setStatus('ready', 'Video Δ‘Γ£ tαΊ£i β€” nhαΊ₯n Play hoαΊ·c BαΊ―t Δ‘αΊ§u nhαΊ­n diện');
887
+ });
888
+
889
+ videoPlayer.addEventListener('timeupdate', () => {
890
+ if (!videoPlayer.seeking) {
891
+ videoProgress.value = videoPlayer.currentTime;
892
+ videoCurrentEl.textContent = formatTime(videoPlayer.currentTime);
893
+ }
894
+ });
895
+
896
+ videoPlayer.addEventListener('ended', () => {
897
+ videoPlayBtn.textContent = 'β–Ά Play';
898
+ if (videoDetecting) stopVideoDetection();
899
+ });
900
+
901
+ videoProgress.addEventListener('input', () => {
902
+ videoPlayer.currentTime = parseFloat(videoProgress.value);
903
+ });
904
+
905
+ videoPlayBtn.addEventListener('click', () => {
906
+ if (videoPlayer.paused) {
907
+ videoPlayer.play();
908
+ videoPlayBtn.textContent = '⏸ Pause';
909
+ } else {
910
+ videoPlayer.pause();
911
+ videoPlayBtn.textContent = 'β–Ά Play';
912
+ }
913
+ });
914
+
915
+ videoDetectBtn.addEventListener('click', () => {
916
+ if (videoDetecting) {
917
+ stopVideoDetection();
918
+ } else {
919
+ if (!detector) { setStatus('error', 'ChΖ°a tαΊ£i model'); return; }
920
+ videoDetecting = true;
921
+ videoFpsCount = 0; videoFpsLastTime = performance.now();
922
+ videoDetectBtn.textContent = '⏹ Dα»«ng nhαΊ­n diện';
923
+ videoCaptureBtn.disabled = false;
924
+ if (videoPlayer.paused) videoPlayer.play();
925
+ videoPlayBtn.textContent = '⏸ Pause';
926
+ videoDetectLoop();
927
+ }
928
+ });
929
+
930
+ async function videoDetectLoop() {
931
+ if (!videoDetecting || videoPlayer.paused || videoPlayer.ended) {
932
+ if (videoPlayer.ended) stopVideoDetection();
933
+ return;
934
+ }
935
+ const t0 = performance.now();
936
+ const origCanvas = document.getElementById('original-canvas');
937
+ origCanvas.width = videoPlayer.videoWidth || 640;
938
+ origCanvas.height = videoPlayer.videoHeight || 360;
939
+ origCanvas.getContext('2d').drawImage(videoPlayer, 0, 0);
940
+
941
+ const pre = preprocessFromCanvas(origCanvas);
942
+ const detections = await runDetection(detector, currentModelEntry, pre);
943
+ const elapsed = performance.now() - t0;
944
+
945
+ const resultCanvas = document.getElementById('result-canvas');
946
+ resultCanvas.width = origCanvas.width; resultCanvas.height = origCanvas.height;
947
+ const ctx = resultCanvas.getContext('2d');
948
+ ctx.drawImage(origCanvas, 0, 0);
949
+ drawDetectionsOnCtx(ctx, detections, classes.length);
950
+ renderTable(detections);
951
+
952
+ videoFpsCount++;
953
+ const now = performance.now();
954
+ if (now - videoFpsLastTime >= 500) {
955
+ videoCurrentFps = videoFpsCount / ((now - videoFpsLastTime) / 1000);
956
+ videoFpsCount = 0; videoFpsLastTime = now;
957
+ }
958
+ showTiming(elapsed, videoCurrentFps);
959
+
960
+ videoRafId = requestAnimationFrame(videoDetectLoop);
961
+ }
962
+
963
+ function stopVideoDetection() {
964
+ videoDetecting = false;
965
+ if (videoRafId) cancelAnimationFrame(videoRafId);
966
+ videoDetectBtn.textContent = '🎯 BαΊ―t Δ‘αΊ§u nhαΊ­n diện';
967
+ videoCaptureBtn.disabled = true;
968
+ }
969
+
970
+ function stopVideo() {
971
+ stopVideoDetection();
972
+ videoPlayer.pause();
973
+ videoPlayer.src = '';
974
+ videoPlayer.style.display = 'none';
975
+ videoProgress.value = 0;
976
+ videoProgress.disabled = true;
977
+ videoCurrentEl.textContent = '0:00';
978
+ videoDurationEl.textContent = '0:00';
979
+ videoPlayBtn.textContent = 'β–Ά Play';
980
+ videoPlayBtn.disabled = true;
981
+ videoDetectBtn.disabled = true;
982
+ videoCaptureBtn.disabled = true;
983
+ videoStopBtn.disabled = true;
984
+ }
985
+
986
+ videoCaptureBtn.addEventListener('click', async () => {
987
+ const resultCanvas = document.getElementById('result-canvas');
988
+ try {
989
+ const blob = await new Promise(res => resultCanvas.toBlob(res, 'image/png'));
990
+ await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
991
+ setStatus('ready', 'βœ… Đã copy frame vΓ o clipboard');
992
+ } catch (err) {
993
+ setStatus('error', `KhΓ΄ng thể copy: ${err.message}`);
994
+ }
995
+ });
996
+
997
+ videoStopBtn.addEventListener('click', stopVideo);
998
+
999
  // ── Magnifier ─────────────────────────────────────────────────────────────
1000
  (function initMagnifier() {
1001
  const magnifier = document.getElementById('magnifier');
 
1025
  applyLensSize(lensSize);
1026
  });
1027
 
1028
+ ['original-canvas', 'result-canvas', 'webcam-video', 'video-player'].forEach(id => {
1029
  const el = document.getElementById(id);
1030
  el.addEventListener('mouseenter', () => { magnifier.style.display = 'block'; el.style.cursor = 'crosshair'; });
1031
  el.addEventListener('mouseleave', () => { magnifier.style.display = 'none'; el.style.cursor = ''; });