Select AI Model to server.

main
dongjin kim 7 months ago
parent 6607a45931
commit ac157c2b6b

@ -277,6 +277,10 @@ document.addEventListener('DOMContentLoaded', () => {
const bboxContainerEl = document.getElementById("bbox-container");
const modelContainerEl = document.getElementById("current-model-container");
// [추가] 오버레이 표시용 요소
const modelDisplayEl = document.getElementById("current-model-display");
const fpsDisplayEl = document.getElementById("fps-display");
// [추가] 클래스별 색상 팔레트 (원하는 색상으로 수정 가능)
const CLASS_COLORS = [
'#FF3838', // 0: Red
@ -289,6 +293,10 @@ document.addEventListener('DOMContentLoaded', () => {
'#FFFFFF' // 7: White
];
// [추가] FPS 계산용 변수
let frameCount = 0;
let lastFpsCheckTime = performance.now();
let lastFrameMeta = null;
if (imgEl && statusEl && frameInfoEl && detListEl && bboxContainerEl && modelContainerEl) {
@ -296,6 +304,9 @@ document.addEventListener('DOMContentLoaded', () => {
// 모델 변경 이벤트 리스너
modelContainerEl.addEventListener('change', (event) => {
if (event.target && event.target.name === 'current-model') {
// [추가] 모델 이름 즉시 업데이트
updateModelDisplay();
const selectedModel = event.target.value;
console.log(`Model changed to: ${selectedModel}`);
@ -345,6 +356,25 @@ document.addEventListener('DOMContentLoaded', () => {
` | FRAME ch=${meta.ch} ts=${meta.ts_us} w=${meta.w} h=${meta.h}`;
}
// [추가] 선택된 AI 모델 이름을 상단 우측에 표시하는 함수
function updateModelDisplay() {
if (!modelContainerEl || !modelDisplayEl) return;
// 현재 선택된 라디오 버튼을 찾습니다.
const checkedRadio = modelContainerEl.querySelector('input[name="current-model"]:checked');
if (checkedRadio) {
// 해당 라디오 버튼의 ID로 label을 찾습니다.
const label = modelContainerEl.querySelector(`label[for="${checkedRadio.id}"]`);
if (label) {
modelDisplayEl.textContent = label.textContent; // "군중 위험 인식"
} else {
modelDisplayEl.textContent = checkedRadio.value; // "CROWD" (대체)
}
} else {
modelDisplayEl.textContent = "모델 선택 안됨";
}
}
// Detections 표시 함수 (수정됨)
function showDetections(meta) {
const items = meta.items || [];
@ -487,12 +517,27 @@ document.addEventListener('DOMContentLoaded', () => {
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;
}
};
}
// [추가] 초기 모델 이름 설정
updateModelDisplay();
// WebSocket 연결 시작
connect();

@ -30,6 +30,10 @@
<span id="status" class="status">연결 시도 중...</span>
<span id="frame-info"></span>
</div>
<span id="current-model-display" class="video-overlay top-right"></span>
<span id="fps-display" class="video-overlay bottom-right"></span>
<img id="frame" alt="video frame">
<div id="bbox-container"></div>
</div>
@ -54,13 +58,13 @@
<input type="radio" id="model-lpr" name="current-model" value="LPR">
<label for="model-lpr" class="segmented-control-button">차량 번호판 인식</label>
<input type="radio" id="model-deid" name="current-model" value="DEID">
<label for="model-deid" class="segmented-control-button">얼굴 비식별화</label>
<input type="radio" id="model-deid" name="current-model" value="FACEATTR">
<label for="model-deid" class="segmented-control-button">얼굴/ 인상착의 인식</label>
<input type="radio" id="model-viptrack" name="current-model" value="VIPTRACK">
<label for="model-viptrack" class="segmented-control-button">관심인물추적</label>
</div>
</div>
</div>
</div>
</div>
@ -79,7 +83,8 @@
<tr>
<th>번호</th>
<th>Al Model 역할</th>
<th>파일명</th> <th>현재 버전</th>
<th>파일명</th>
<th>현재 버전</th>
<th>삭제</th>
</tr>
<tbody>
@ -101,7 +106,8 @@
<button class="btn-delete">삭제</button>
</td>
</tr>
<tr data-role="ABNORM"> <td>3</td>
<tr data-role="ABNORM">
<td>3</td>
<td>이상행동(쓰러짐, 폭행) 감지</td>
<td class="model-filename">-</td>
<td class="model-version">v1.0</td>

@ -553,4 +553,42 @@ main {
justify-content: center; /* 수평 중앙 정렬 */
align-items: center; /* 수직 중앙 정렬 */
gap: 20px; /* 자식 요소(이미지) 사이의 간격 */
}
/* ====================================================== */
/* [수정] 비디오 오버레이 (FPS, 모델명) 스타일 */
/* ====================================================== */
.video-overlay {
position: absolute;
z-index: 10; /* #info와 동일한 레벨 */
background-color: rgba(0, 0, 0, 0.5); /* 반투명 검은 배경 */
color: #ffffff; /* 흰색 텍스트 */
padding: 4px 8px;
border-radius: 4px;
font-size: 14px; /* 기본 폰트 크기 */
font-weight: bold;
pointer-events: none; /* 클릭 이벤트 방지 */
white-space: nowrap; /* 줄바꿈 방지 */
/* [추가] 요청사항: 가독성을 위한 그림자 */
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8);
}
.video-overlay.top-right {
top: 100px;
right: 10px;
/* [추가] 요청사항: AI 모델명 3배 크게 (14px * 3) */
font-size: 60px;
}
.video-overlay.bottom-right {
/* [수정] 요청사항: 10px -> 30px (조금 더 위로) */
bottom: 70px;
right: 10px;
/* [추가] 요청사항: FPS 표시 2배 크게 (14px * 2) */
font-size: 28px;
/* [추가] 요청사항: 폰트 색상 노란색 */
color: #FFEE00;
}

@ -58,9 +58,9 @@ app.get('/dashboard', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'dashboard.html'));
});
// 6. 모델 변경 API 엔드포인트
// 6. 모델 변경 API 엔드포인트 (*** 수정된 부분 ***)
app.post('/set-model', (req, res) => {
const { model } = req.body; // app.js에서 보낸 { model: "..." }
const { model } = req.body; // app.js에서 보낸 { model: "OBJDET" } 등
if (!model) {
return res.status(400).json({ status: 'error', message: '모델 값이 없습니다.' });
@ -68,40 +68,42 @@ app.post('/set-model', (req, res) => {
console.log(`[서버] 모델 변경 명령 수신: ${model}`);
// --- 요청하신 파이썬 스크립트 실행 ---
const pythonCommand = 'python3';
const scriptPath = '/mnt/user_data/ctrl_cli.py';
const combinedArg = `"ON ${model}"`;
const args = [scriptPath, combinedArg];
// --- 요청하신 쉘 스크립트 실행으로 변경 ---
const scriptCommand = '/mnt/user_data/feat_control/feat_on.sh';
const args = [model]; // OBJDET, ABNORM, CROWD 등
console.log(`[서버] 실행: ${pythonCommand} ${args.join(' ')}`);
console.log(`[서버] 실행: ${scriptCommand} ${args.join(' ')}`);
const py = spawn(pythonCommand, args);
// 스크립트 파일에 실행 권한(chmod +x)이 있다고 가정합니다.
const scriptProcess = spawn(scriptCommand, args);
let stdoutData = '';
let stderrData = '';
py.stdout.on('data', (data) => {
console.log(`Python stdout: ${data}`);
scriptProcess.stdout.on('data', (data) => {
console.log(`Script stdout: ${data}`);
stdoutData += data.toString();
});
py.stderr.on('data', (data) => {
console.error(`Python stderr: ${data}`);
scriptProcess.stderr.on('data', (data) => {
console.error(`Script stderr: ${data}`);
stderrData += data.toString();
});
py.on('close', (code) => {
console.log(`Python process exited with code ${code}`);
scriptProcess.on('close', (code) => {
console.log(`Script process exited with code ${code}`);
if (code === 0) {
res.json({ status: 'success', message: `모델이 ${model}(으)로 변경됨`, output: stdoutData });
} else {
res.status(500).json({ status: 'error', message: '파이썬 스크립트 실행 실패', error: stderrData });
res.status(500).json({ status: 'error', message: ' 스크립트 실행 실패', error: stderrData });
}
});
py.on('error', (err) => {
console.error('[서버] 파이썬 프로세스 시작 실패:', err);
scriptProcess.on('error', (err) => {
console.error('[서버] 쉘 프로세스 시작 실패:', err);
// "ENOENT" 오류는
// 1) 스크립트 경로가 잘못되었거나,
// 2) 스크립트 파일에 실행 권한이 없을 때 자주 발생합니다.
res.status(500).json({ status: 'error', message: '프로세스 시작 실패', error: err.message });
});
// --- 스크립트 실행 끝 ---
@ -244,4 +246,4 @@ app.post('/delete-model', (req, res) => {
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
//```
//
Loading…
Cancel
Save