diff --git a/public/app.js b/public/app.js index 31845bb..b20be28 100644 --- a/public/app.js +++ b/public/app.js @@ -310,8 +310,74 @@ document.addEventListener('DOMContentLoaded', () => { } // ================================================= - // 4. 비디오 & 웹소켓 (좌표 그리기 로직 추가됨) + // 4. 비디오 & 웹소켓 (라벨 매핑 및 컬러 로직 추가) // ================================================= + + // [설정] Tag/Class 매핑 테이블 + const LABEL_MAP = { + 1: { + tagName: "객체 탐지/ 분류", + classes: { + 0: "person", + 1: "car", + 2: "van", + 3: "truck", + 4: "bus", + 5: "motor" + } + }, + 2: { + tagName: "화재 인식", + classes: { + 0: "flame", + 1: "smoke" + } + }, + 3: { + tagName: "얼굴 인식", + classes: { + 0: "face" + } + }, + 4: { + tagName: "차량번호판 및 차종 인식", + classes: { + 0: "License plate" + } + } + }; + + // [설정] 박스 색상 생성 함수 + function getBoxColor(tag, cls) { + // Tag 2: 화재 관련 (긴급한 색상) + if (tag === 2) { + if (cls === 0) return '#FF0000'; // Flame: 빨강 + if (cls === 1) return '#808080'; // Smoke: 회색 + } + + // Tag 1: 객체 탐지 (다양한 색상) + if (tag === 1) { + const colors = [ + '#00FF00', // Person: 라임 그린 + '#00FFFF', // Car: 시안 + '#FFA500', // Van: 오렌지 + '#FF69B4', // Truck: 핫핑크 + '#9370DB', // Bus: 미디엄 퍼플 + '#FFD700' // Motor: 골드 + ]; + return colors[cls % colors.length] || '#FFFFFF'; + } + + // Tag 3: 얼굴 + if (tag === 3) return '#1E90FF'; // 도저 블루 + + // Tag 4: 번호판 + if (tag === 4) return '#32CD32'; // 라임 그린 + + // 기타 기본값 + return '#FF0000'; + } + const uri = "ws://10.10.11.246:8765"; const canvasEl = document.getElementById("frame"); const statusEl = document.getElementById("status"); @@ -327,7 +393,6 @@ document.addEventListener('DOMContentLoaded', () => { let lastFpsCheckTime = performance.now(); let lastFrameMeta = null; - // [신규] 캔버스 영상의 배율(scale)과 여백(offset)을 저장하여 좌표 변환에 사용 let viewConfig = { r: 1, dx: 0, dy: 0 }; function connect() { @@ -350,13 +415,12 @@ document.addEventListener('DOMContentLoaded', () => { // 1. 텍스트 데이터(JSON) 처리 if (typeof data === "string") { - console.log("-----" + data); + console.log("-----" + data); // 필요시 주석 해제 try { const meta = JSON.parse(data); if (meta.type === "frame") { lastFrameMeta = meta; document.getElementById("frame-info").textContent = ` | FRAME ${meta.w}x${meta.h}`; - // 탐지 정보가 오면 박스를 그림 showDetections(meta); } } catch(e){ console.error(e); } @@ -373,25 +437,21 @@ document.addEventListener('DOMContentLoaded', () => { const blob = new Blob([data], {type: "image/jpeg"}); createImageBitmap(blob).then(bmp => { - // 캔버스 크기에 맞춰 영상 비율(레터박스) 계산 canvasEl.width = canvasEl.clientWidth; canvasEl.height = canvasEl.clientHeight; - // r: 축소/확대 비율, dx/dy: 중앙 정렬을 위한 여백 const r = Math.min(canvasEl.width / bmp.width, canvasEl.height / bmp.height); const dw = bmp.width * r; const dh = bmp.height * r; const dx = (canvasEl.width - dw) / 2; const dy = (canvasEl.height - dh) / 2; - // 좌표 변환을 위해 현재 뷰 설정 전역 변수에 저장 viewConfig = { r, dx, dy }; ctx.clearRect(0, 0, canvasEl.width, canvasEl.height); ctx.drawImage(bmp, dx, dy, dw, dh); bmp.close(); - // 창 크기 변화 시 박스 컨테이너 위치도 동기화 updateBboxContainerPosition(); }); } @@ -400,12 +460,12 @@ document.addEventListener('DOMContentLoaded', () => { /** * [수정됨] 탐지 정보를 받아 화면에 Bounding Box를 그리는 함수 + * - 매핑 테이블을 사용하여 Tag/Class 이름을 표시 + * - Class별 색상 구분 적용 */ function showDetections(meta) { - // 기존 박스들 초기화 bboxContainerEl.innerHTML = ""; - // 메타 정보 표시 준비 const ch = meta.ch !== undefined ? meta.ch : '-'; const seq = meta.seq !== undefined ? meta.seq : '-'; const ts = meta.ts_us !== undefined ? meta.ts_us : '-'; @@ -416,59 +476,79 @@ document.addEventListener('DOMContentLoaded', () => { if (items.length === 0) { outputLines.push("No detections"); } else { - // 각 탐지 객체에 대해 처리 items.forEach((it, i) => { - // 원본 영상 기준 좌표 const x1 = it.x1 || 0; const y1 = it.y1 || 0; const x2 = it.x2 || 0; const y2 = it.y2 || 0; - const cls = it.cls !== undefined ? it.cls : '?'; - const tag = it.tag !== undefined ? it.tag : ''; - // 우측 정보창에 텍스트 로그 추가 - outputLines.push(`#${i} | Class:${cls} | Tag:${tag} | Box:[${x1.toFixed(1)}, ${y1.toFixed(1)}, ${x2.toFixed(1)}, ${y2.toFixed(1)}]`); + // 데이터에서 tag와 cls 추출 + const tagId = it.tag !== undefined ? it.tag : -1; + const clsId = it.cls !== undefined ? it.cls : -1; + const tId = it.tid !== undefined ? it.tid : -1; + + // 1. 이름 매핑 Lookup + let displayTagName = `Tag ${tagId}`; + let displayClassName = `Cls ${clsId}`; + + if (LABEL_MAP[tagId]) { + displayTagName = LABEL_MAP[tagId].tagName; + if (LABEL_MAP[tagId].classes[clsId]) { + displayClassName = LABEL_MAP[tagId].classes[clsId]; + } + } + + // 2. 색상 결정 + const boxColor = getBoxColor(tagId, clsId); - // 좌표 변환: 원본 좌표 -> 캔버스 화면 좌표 - // 공식: 화면좌표 = 여백 + (원본좌표 * 배율) + // 로그창 출력 (원본 데이터 유지) + outputLines.push(`#${i} | ${displayClassName} (${clsId}) | ${displayTagName} (${tagId}) | Box:[${x1.toFixed(0)},${y1.toFixed(0)}]`); + + // 좌표 변환 const { r, dx, dy } = viewConfig; const screenX = dx + (x1 * r); const screenY = dy + (y1 * r); const screenW = (x2 - x1) * r; const screenH = (y2 - y1) * r; - // 박스 DOM 요소 생성 + // 박스 DOM 생성 const boxDiv = document.createElement('div'); - boxDiv.className = 'bbox'; // style.css에 정의된 붉은 박스 스타일 사용 + boxDiv.className = 'bbox'; + + // [스타일 적용] 동적 색상 및 위치 boxDiv.style.left = `${screenX}px`; boxDiv.style.top = `${screenY}px`; boxDiv.style.width = `${screenW}px`; boxDiv.style.height = `${screenH}px`; + boxDiv.style.borderColor = boxColor; // 테두리 색상 변경 - // 박스 위 라벨 (선택 사항) + // 라벨 생성 const label = document.createElement('div'); label.style.position = 'absolute'; - label.style.top = '-16px'; - label.style.left = '0'; - label.style.backgroundColor = 'red'; - label.style.color = 'white'; - label.style.fontSize = '10px'; - label.style.padding = '1px 3px'; + label.style.top = '-20px'; // 박스 바로 위 + label.style.left = '-2px'; // 테두리 두께 고려 정렬 + label.style.backgroundColor = boxColor; // 배경색을 박스색과 동일하게 + label.style.color = 'white'; // 글자는 흰색 + label.style.fontSize = '12px'; + label.style.fontWeight = 'bold'; + label.style.padding = '2px 6px'; + label.style.borderRadius = '3px'; label.style.whiteSpace = 'nowrap'; - label.textContent = `C:${cls} T:${tag}`; + label.style.textShadow = '0px 0px 2px #000'; // 가독성을 위한 그림자 + + // [요청 포맷 적용] Class 명 : Tag id + label.textContent = tId === -1 || tId === 65535 ? `${displayClassName}` : `${displayClassName} : ${tId}`; boxDiv.appendChild(label); bboxContainerEl.appendChild(boxDiv); }); } - // 우측 정보창 업데이트 detListEl.textContent = outputLines.join("\n"); } function updateBboxContainerPosition() { if (!canvasEl || !bboxContainerEl) return; - // 캔버스의 실제 위치와 크기에 박스 컨테이너를 일치시킴 bboxContainerEl.style.top = canvasEl.offsetTop + 'px'; bboxContainerEl.style.left = canvasEl.offsetLeft + 'px'; bboxContainerEl.style.width = canvasEl.offsetWidth + 'px'; diff --git a/public/dashboard.html b/public/dashboard.html index 91018a7..c1a49ff 100644 --- a/public/dashboard.html +++ b/public/dashboard.html @@ -149,4 +149,6 @@ - \ No newline at end of file + + + \ No newline at end of file diff --git a/public/style.css b/public/style.css index 752a550..011f527 100644 --- a/public/style.css +++ b/public/style.css @@ -346,4 +346,6 @@ main { color: #bbb; text-decoration: none; cursor: pointer; -} \ No newline at end of file +} + +/*2025.11.25 16:16*/ \ No newline at end of file