diff --git a/public/app.js b/public/app.js
index c902466..273e8b0 100644
--- a/public/app.js
+++ b/public/app.js
@@ -270,7 +270,10 @@ document.addEventListener('DOMContentLoaded', () => {
// ========== dashboard.html 스크립트 추가 시작 ==========
const uri = "ws://10.10.11.246:8765"; // 필요하면 여기만 수정
- const imgEl = document.getElementById("frame");
+
+ // [수정] imgEl -> canvasEl로 변경
+ const canvasEl = document.getElementById("frame");
+
const statusEl = document.getElementById("status");
const frameInfoEl = document.getElementById("frame-info");
const detListEl = document.getElementById("det-list");
@@ -281,6 +284,12 @@ document.addEventListener('DOMContentLoaded', () => {
const modelDisplayEl = document.getElementById("current-model-display");
const fpsDisplayEl = document.getElementById("fps-display");
+ // [추가] 캔버스 2D 컨텍스트
+ let ctx = null;
+ if (canvasEl) {
+ ctx = canvasEl.getContext('2d');
+ }
+
// [추가] 클래스별 색상 팔레트 (원하는 색상으로 수정 가능)
const CLASS_COLORS = [
'#FF3838', // 0: Red
@@ -299,7 +308,8 @@ document.addEventListener('DOMContentLoaded', () => {
let lastFrameMeta = null;
- if (imgEl && statusEl && frameInfoEl && detListEl && bboxContainerEl && modelContainerEl) {
+ // [수정] canvasEl 및 ctx 존재 여부 확인
+ if (canvasEl && ctx && statusEl && frameInfoEl && detListEl && bboxContainerEl && modelContainerEl) {
// 모델 변경 이벤트 리스너
modelContainerEl.addEventListener('change', (event) => {
@@ -335,11 +345,12 @@ document.addEventListener('DOMContentLoaded', () => {
// 박스 컨테이너 위치/크기 조절 함수
function updateBboxContainerPosition() {
- if (!imgEl || !bboxContainerEl) return;
- const top = imgEl.offsetTop;
- const left = imgEl.offsetLeft;
- const width = imgEl.offsetWidth;
- const height = imgEl.offsetHeight;
+ // [수정] imgEl -> canvasEl
+ if (!canvasEl || !bboxContainerEl) return;
+ const top = canvasEl.offsetTop;
+ const left = canvasEl.offsetLeft;
+ const width = canvasEl.offsetWidth;
+ const height = canvasEl.offsetHeight;
bboxContainerEl.style.top = `${top}px`;
bboxContainerEl.style.left = `${left}px`;
bboxContainerEl.style.width = `${width}px`;
@@ -382,7 +393,8 @@ document.addEventListener('DOMContentLoaded', () => {
lines.push(`DET ch=${meta.ch} seq=${meta.seq} ts=${meta.ts_us} cnt=${items.length}`);
bboxContainerEl.innerHTML = "";
- if (!imgEl || !lastFrameMeta) {
+ // [수정] imgEl -> canvasEl
+ if (!canvasEl || !lastFrameMeta) {
// [수정됨] x1,y1,x2,y2 형식에 맞게 텍스트 로그 수정
items.forEach((it, i) => {
// x1, y1, x2, y2 -> x, y, w, h
@@ -402,8 +414,9 @@ document.addEventListener('DOMContentLoaded', () => {
}
updateBboxContainerPosition();
- const imgWidth = imgEl.clientWidth;
- const imgHeight = imgEl.clientHeight;
+ // [수정] imgEl -> canvasEl
+ const imgWidth = canvasEl.clientWidth;
+ const imgHeight = canvasEl.clientHeight;
const frameWidth = lastFrameMeta.w;
const frameHeight = lastFrameMeta.h;
@@ -511,26 +524,68 @@ document.addEventListener('DOMContentLoaded', () => {
}
return;
}
+
+ // [수정] ArrayBuffer 처리: img.src 대신 createImageBitmap 및 canvas.drawImage 사용
if (data instanceof ArrayBuffer && lastFrameMeta) {
+
+ // ========== [수정됨] FPS 계산 로직 (이곳으로 이동) ==========
+ // WebSocket에서 ArrayBuffer(프레임)를 수신한 시점을 기준으로 FPS 계산
+ frameCount++;
+ const now = performance.now();
+ const delta = now - lastFpsCheckTime;
+
+ if (delta >= 1000 && fpsDisplayEl) { // 1초마다 업데이트
+ const fps = (frameCount * 1000) / delta;
+ fpsDisplayEl.textContent = `${Math.round(fps)} FPS`;
+ frameCount = 0;
+ lastFpsCheckTime = now;
+ }
+ // ========== FPS 계산 로직 끝 ==========
+
+
const blob = new Blob([data], {type: "image/jpeg"});
- const url = URL.createObjectURL(blob);
- imgEl.onload = () => {
- URL.revokeObjectURL(url);
- updateBboxContainerPosition();
-
- // [추가] FPS 계산
- frameCount++;
- const now = performance.now();
- const delta = now - lastFpsCheckTime;
-
- if (delta >= 1000 && fpsDisplayEl) { // 1초마다 업데이트
- const fps = (frameCount * 1000) / delta;
- fpsDisplayEl.textContent = `${Math.round(fps)} FPS`;
- frameCount = 0;
- lastFpsCheckTime = now;
- }
- };
- imgEl.src = url;
+
+ // createImageBitmap으로 비동기 디코딩 및 렌더링
+ createImageBitmap(blob)
+ .then(imageBitmap => {
+ // 캔버스 크기를 CSS에 정의된 표시 크기로 맞춥니다.
+ // (object-fit: contain을 시뮬레이션하기 위해 매번 필요)
+ canvasEl.width = canvasEl.clientWidth;
+ canvasEl.height = canvasEl.clientHeight;
+
+ const canvasWidth = canvasEl.width;
+ const canvasHeight = canvasEl.height;
+ const frameWidth = imageBitmap.width; // 실제 이미지 비트맵의 너비
+ const frameHeight = imageBitmap.height; // 실제 이미지 비트맵의 높이
+
+ // 'object-fit: contain' 로직
+ const widthRatio = canvasWidth / frameWidth;
+ const heightRatio = canvasHeight / frameHeight;
+ const ratio = Math.min(widthRatio, heightRatio);
+
+ const videoDisplayWidth = frameWidth * ratio;
+ const videoDisplayHeight = frameHeight * ratio;
+ const offsetX = (canvasWidth - videoDisplayWidth) / 2;
+ const offsetY = (canvasHeight - videoDisplayHeight) / 2;
+
+ // 캔버스를 지웁니다 (CSS의 background-color가 비친다)
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+
+ // 계산된 위치에 이미지를 그립니다.
+ ctx.drawImage(imageBitmap, offsetX, offsetY, videoDisplayWidth, videoDisplayHeight);
+
+ // 비트맵 메모리 해제
+ imageBitmap.close();
+
+ // 프레임이 그려진 *이후*에 바운딩 박스 컨테이너 위치 업데이트
+ updateBboxContainerPosition();
+
+ // [수정됨] FPS 계산 로직이 위로 이동했으므로 여기서는 제거됩니다.
+
+ })
+ .catch(e => {
+ console.error("createImageBitmap error:", e);
+ });
}
};
}
@@ -550,5 +605,4 @@ document.addEventListener('DOMContentLoaded', () => {
// ========== 페이지 로드 시 모델 목록 즉시 로드 ==========
loadModelList();
-
});
\ No newline at end of file
diff --git a/public/dashboard.html b/public/dashboard.html
index bd139f4..4069564 100644
--- a/public/dashboard.html
+++ b/public/dashboard.html
@@ -34,7 +34,7 @@
-
+