|
|
|
|
@ -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';
|
|
|
|
|
|