Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .electron-builder.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ const config = {
'dist/renderer/**',
'package.json',
],
/* 알림에 추가한 이미지 빌드 시 */
extraResources: [
{
from: 'src/main/assets/Symbol Logo.png',
to: 'Symbol Logo.png',
},
],
asar: true,
// 빌드 전에 필요한 파일들이 존재하는지 확인
beforeBuild: async (context) => {
Expand Down
60 changes: 53 additions & 7 deletions STABILIZATION_LOGIC.md
Original file line number Diff line number Diff line change
@@ -1,71 +1,87 @@
# 안정화 로직 케이스별 정리

## 개요

안정화 로직은 **시간 기반 주기 제어**와 **점수 기반 안정화 검사** 두 단계로 구성됩니다.

---

## 방지할 수 있는 상황 ✅

### 1. 노이즈로 인한 급격한 레벨 전환

**상황**: 카메라 노이즈, 측정 오차로 인해 점수가 급격히 변동
**예시**:
**예시**:

- L3(기린) 상태에서 일시적으로 점수가 1.3으로 올라가 L4(거북이)로 전환
- 실제 자세는 변하지 않았는데 측정 오차로 인한 변화

**방지 메커니즘**:

- **시간 기반**: 최소 50-200ms 주기로 검사하여 일시적 변화 무시
- **점수 기반**: 이전 점수들의 평균과 비교하여 급격한 변화 필터링
- **결과**: 안정적인 자세에서는 레벨이 자주 바뀌지 않음

---

### 2. 일시적인 자세 변화 (순간적인 움직임)

**상황**: 사용자가 잠깐 고개를 돌리거나 몸을 움직임
**예시**:

- 책상에서 일하다가 잠깐 옆을 보는 경우
- 재채기, 기침 등 일시적인 움직임

**방지 메커니즘**:

- **평균 기반 검사**: 최근 300ms 내 점수들의 평균과 비교
- **Threshold**: 0.6 또는 1.2 이내의 변화만 허용
- **결과**: 일시적 변화는 무시되고 안정적인 자세만 반영

---

### 3. 프레임 드롭/스파이크

**상황**: 카메라 프레임이 드롭되거나 특정 프레임에서 이상값 발생
**예시**:

- 프레임 레이트가 불안정한 경우
- 특정 프레임에서 포즈 인식 실패

**방지 메커니즘**:

- **버퍼 기반**: 최근 300ms 내 여러 프레임의 평균 사용
- **최소 버퍼**: 3개 이상의 데이터가 있어야 신뢰성 있는 판단
- **결과**: 개별 프레임의 이상값이 전체 판단에 큰 영향 없음

---

### 4. 카메라 움직임/조명 변화

**상황**: 카메라가 흔들리거나 조명이 변하는 경우
**예시**:

- 노트북을 옮기는 경우
- 조명이 켜지거나 꺼지는 경우

**방지 메커니즘**:

- **시간 지연**: 최소 주기(50-200ms) 동안 변화가 지속되어야 반영
- **평균 비교**: 단일 프레임의 급격한 변화는 무시
- **결과**: 일시적인 환경 변화는 필터링됨

---

### 5. 레벨 경계 근처에서의 진동

**상황**: 점수가 경계값 근처에서 왔다갔다 하는 경우
**예시**:

- 점수 1.1 ↔ 1.3 사이에서 L3 ↔ L4 전환 반복
- 실제로는 경계 근처에서 미세하게 변동

**방지 메커니즘**:

- **Threshold 검사**: 평균과의 차이가 threshold 이내여야 업데이트
- **시간 주기**: 최소 주기 동안 지속되어야 반영
- **결과**: 경계 근처에서 불필요한 전환 방지
Expand All @@ -75,12 +91,15 @@
## 불리한 상황 ❌

### 1. 실제로 빠르게 자세를 바꾸는 경우

**상황**: 사용자가 의도적으로 자세를 빠르게 교정
**예시**:

- 거북목 자세에서 갑자기 고개를 들고 올바른 자세로 교정
- 점수가 1.5 → 0.5로 빠르게 변화

**문제점**:

- **시간 지연**: 최소 50-200ms 주기로 인해 즉시 반영되지 않음
- **평균 기반**: 이전 점수들의 평균과 비교하여 급격한 변화가 억제될 수 있음
- **결과**: 실제 자세 개선이 화면에 반영되는데 지연 발생
Expand All @@ -90,12 +109,15 @@
---

### 2. 점진적이지만 지속적인 자세 악화

**상황**: 자세가 서서히 나빠지지만 각 프레임의 변화는 작음
**예시**:

- 점수가 0.3 → 0.5 → 0.7 → 0.9 → 1.1로 서서히 증가
- 각 변화는 threshold(0.6) 이내이지만 누적되면 레벨 전환

**문제점**:

- **Threshold 검사**: 각 프레임의 변화가 작아서 통과
- **평균 기반**: 최근 평균과 비교하므로 점진적 변화는 쉽게 통과
- **결과**: 실제로는 자세가 악화되었는데 레벨 전환이 지연될 수 있음
Expand All @@ -105,12 +127,15 @@
---

### 3. 경계 근처에서 오래 머무는 경우

**상황**: 점수가 경계값 근처에서 오래 유지되는 경우
**예시**:

- 점수 1.15 (L3)에서 1.25 (L4) 사이를 계속 왔다갔다
- 실제로는 L4에 가까운 자세인데 L3으로 표시

**문제점**:

- **Threshold**: 평균과의 차이가 작아서 업데이트는 되지만
- **시간 주기**: 주기마다 검사하므로 실제 레벨과 다를 수 있음
- **결과**: 경계 근처에서는 레벨 판정이 불안정할 수 있음
Expand All @@ -120,12 +145,15 @@
---

### 4. 초기 상태 (버퍼 부족)

**상황**: 앱 시작 직후 또는 reset() 후 버퍼에 데이터가 부족
**예시**:

- 앱 시작 후 처음 300ms 동안
- 캘리브레이션 후 처음 측정

**문제점**:

- **최소 버퍼**: 버퍼 < 3개일 때는 무조건 통과
- **평균 부정확**: 데이터가 적어서 평균이 부정확할 수 있음
- **결과**: 초기에는 안정화 효과가 약함
Expand All @@ -135,12 +163,15 @@
---

### 5. 매우 빠른 자세 변화 반복

**상황**: 사용자가 의도적으로 자세를 빠르게 바꾸는 경우
**예시**:

- 고개를 위아래로 빠르게 움직임
- 테스트 목적으로 자세를 의도적으로 변경

**문제점**:

- **시간 주기**: 최소 주기로 인해 모든 변화를 반영하지 못함
- **평균 기반**: 빠른 변화가 평균에 섞여서 정확도 저하
- **결과**: 실제 자세 변화를 제대로 추적하지 못할 수 있음
Expand All @@ -151,23 +182,25 @@

## 트레이드오프 요약

| 측면 | 장점 | 단점 |
|------|------|------|
| 측면 | 장점 | 단점 |
| ------------- | ------------------------ | -------------------------------- |
| **반응 속도** | 노이즈 필터링으로 안정적 | 실제 변화 반영에 지연 (50-200ms) |
| **정확도** | 일시적 변화 무시로 정확 | 점진적 변화는 잘 감지 |
| **안정성** | 급격한 전환 방지 | 경계 근처에서 약간 불안정 |
| **성능** | 불필요한 검사 방지 | 초기 상태에서 효과 약함 |
| **정확도** | 일시적 변화 무시로 정확 | 점진적 변화는 잘 감지 |
| **안정성** | 급격한 전환 방지 | 경계 근처에서 약간 불안정 |
| **성능** | 불필요한 검사 방지 | 초기 상태에서 효과 약함 |

---

## 권장 사용 시나리오

✅ **이 로직이 적합한 경우**:

- 일반적인 업무 환경 (안정적인 자세 유지)
- 장시간 모니터링 (노이즈 필터링 중요)
- 사용자 피드백을 안정적으로 제공해야 하는 경우

❌ **이 로직이 부적합한 경우**:

- 실시간 게임이나 빠른 반응이 필요한 경우
- 매우 빠른 자세 변화를 정확히 추적해야 하는 경우
- 초기 몇 초 동안의 정확도가 중요한 경우
Expand All @@ -179,23 +212,27 @@
매 프레임마다 현재 상황에 따라 업데이트 주기를 결정합니다.

### 케이스 1-1: 거북이/기린 경계 전환 (레벨 3 ↔ 4)

- **조건**: `lastStableState.cls === 3 && currentClassification.cls === 4` 또는 그 반대
- **주기**: **50ms**
- **설명**: 가장 중요한 경계(기린↔거북이) 전환 시 가장 빠르게 반응

### 케이스 1-2: 일반 레벨 변경 (경계 전환이 아닌 레벨 변경)

- **조건**: `levelChanged === true` && `isBoundaryCrossing === false`
- **예시**: L1→L2, L2→L3, L4→L5, L5→L6 등
- **주기**: **150ms**
- **설명**: 경계 전환이 아닌 다른 레벨 간 전환

### 케이스 1-3: 레벨 유지 (같은 레벨 내 점수 변화)

- **조건**: `levelChanged === false`
- **예시**: L3 내에서 점수만 변하는 경우
- **주기**: **200ms**
- **설명**: 같은 레벨 내에서는 가장 느리게 업데이트

### 주기 미경과 시 동작

- **조건**: `timeSinceLastUpdate < requiredInterval`
- **동작**: 안정화 검사 없이 `lastStableState` 즉시 반환
- **효과**: 불필요한 검사 방지 및 성능 최적화
Expand All @@ -207,11 +244,13 @@
주기가 경과했을 때만 안정화 검사를 수행합니다.

### 안정화 검사 파라미터

- **windowMs**: 300ms (버퍼에 유지할 시간)
- **threshold**: 0.6 (기본 임계값)
- **minBufferSize**: 3 (최소 버퍼 크기)

### 케이스 2-1: 거북이/기린 경계 전환 시

- **threshold**: **1.2** (기본값 0.6의 2배)
- **로직**:
1. 버퍼 크기 < 3이면 → **통과** (즉시 업데이트)
Expand All @@ -220,6 +259,7 @@
4. 그 외 → **실패** (이전 상태 유지)

### 케이스 2-2: 일반 상황 (경계 전환이 아닌 경우)

- **threshold**: **0.6** (기본값)
- **로직**:
1. 버퍼 크기 < 3이면 → **통과** (즉시 업데이트)
Expand All @@ -228,6 +268,7 @@
4. 그 외 → **실패** (이전 상태 유지)

### 안정화 검사 상세 로직

```
1. 버퍼에 현재 점수 추가 (매 프레임)
2. 300ms 이전 데이터 제거
Expand All @@ -245,21 +286,25 @@
## 3단계: 최종 결과 반환

### 케이스 3-1: 안정화 검사 통과

- **동작**: `lastStableState = currentClassification` 업데이트
- **반환**: 새로운 안정화된 상태

### 케이스 3-2: 안정화 검사 실패

- **동작**: `lastStableState` 유지 (변경 없음)
- **반환**: 이전 안정화된 상태

### 케이스 3-3: 초기 상태 (lastStableState === null)

- **반환**: 기본 "측정중" 상태

---

## 전체 플로우 예시

### 예시 1: 거북이 → 기린 전환 (L4 → L3)

```
1. 현재 점수: 1.0 (L3), 이전: 1.5 (L4)
2. 레벨 변경 감지 → isBoundaryCrossing = true
Expand All @@ -271,6 +316,7 @@
```

### 예시 2: 같은 레벨 내 점수 변화 (L3 내에서)

```
1. 현재 점수: 0.5 (L3), 이전: 0.3 (L3)
2. 레벨 변경 없음 → levelChanged = false
Expand All @@ -282,6 +328,7 @@
```

### 예시 3: 안정화 검사 실패

```
1. 현재 점수: 2.0, 이전 평균: 0.5
2. 차이: |2.0 - 0.5| = 1.5
Expand All @@ -299,4 +346,3 @@
3. **이중 안정화**: 시간 기반 + 점수 기반 안정화
4. **버퍼 기반 평균**: 최근 300ms 내 점수들의 평균과 비교
5. **현재 점수 제외**: 평균 계산 시 현재 점수는 제외하여 편향 방지

Binary file added src/main/assets/Symbol_Logo.png
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 파일 이름 띄어쓰기 없이 가는게 더 좋을거 같습니다

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/main/src/index.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 노티피케이션 따로 파일 분리 못하려나요?

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
closeWidgetWindow,
isWidgetWindowOpen,
} from '/@/widgetWindow';
import { setupNotificationHandlers } from '/@/notificationHandlers';

/**
* Setup IPC handlers for Electron-specific features
Expand Down Expand Up @@ -66,12 +67,23 @@ function setupAPIHandlers() {
throw error;
}
});

/* Notification 핸들러 설정 */
setupNotificationHandlers();
}
/* 위젯 상태 확인 요청 핸들러 */
ipcMain.handle('widget:isOpen', () => {
return isWidgetWindowOpen();
});

/**
* Set App User Model ID for Windows notifications
* mac은 필요 x
*/
if (process.platform === 'win32') {
app.setAppUserModelId('거부기린');
}

/**
* Prevent multiple instances
*/
Expand Down
Loading