1) 구성요소 & 사용 모델
A. 시선추적(Gaze) 모듈
- 기본선택: Mediapipe FaceMesh + Iris
- 이유: 추가 학습 없이 즉시 사용, CPU에서도 실시간(또는 준실시간) 가능, 구현 난이도/의존성 낮음.
- 역할: 눈 윤곽·홍채 랜드마크 검출 → 홍채 평면 법선(or 안구중심–홍채중심 벡터)로 시선벡터 d\mathbf{d}d 계산.
- 보정: EMA(Exponential Moving Average)로 방향/중심점 스무딩.
- 대안·확장:
- RT-GENE (eye-only): 근접 눈 영상에서의 강건한 gaze 회귀(추후 정밀도 향상을 위한 후보).
- Gaze360: 얼굴/머리자세 변화에 강건한 3D gaze(추후 교체·앙상블 후보).
- EllSeg/YOLO-seg 기반: 동공/홍채 세그멘테이션 후 기하 모델 정밀화(추후 고정밀 파이프라인 후보).
B. 월드 깊이(Depth) 모듈
- 기본선택: Depth-Anything v2 (metric 옵션) + Video-Depth-Anything
- 이유: 단안 입력으로도 높은 범용성/성능, v2는 metric 보정 경향이 좋아서 스케일·시프트 교정(a, b)만 잡으면 준-절대 깊이 구현 용이. Video 버전은 시간적 일관성과 스트리밍에 적합.
- 역할: 월드카메라 프레임마다 깊이맵 DpredD_{\text{pred}}Dpred 추정.
- 보정: Dcorr=a⋅Dpred+bD_{\text{corr}} = a \cdot D_{\text{pred}} + bDcorr=a⋅Dpred+b (환경·카메라별 스케일/시프트 보정).
2) 모델 선정 이유(현실 제약 기반)
- 하드웨어 제약: 로컬 Windows + RTX 3050 Ti(4GB VRAM) / 때로는 CPU only → 무거운 네트워크 및 다중 모델 병렬 구동이 어려움.
- 개발 속도: “작동하는 프로토타입”을 빠르게 만들고 점진 개선 → 미디어파이프 + Depth-Anything v2 조합이 최단경로.
- 운영 성숙도: Video-Depth-Anything은 프레임간 안정성·깜빡임 억제에 도움 → UI/UX에 큰 이점.
- 확장성: 추후 RT-GENE/Gaze360로 gaze만 교체해도 아래 **융합부(카메라 기하/깊이 교차)**는 그대로 재사용 가능.
3) 두 모델을 융합한 목적
- 실시간 3D 응시점(POG) 추정
- 시선벡터만 있으면 “방향”뿐이라 **깊이(거리)**가 없음.
- 깊이맵과 교차시키면 픽셀 좌표 & 3D 좌표의 응시점을 즉시 얻을 수 있음.
- 단안 환경에서의 비용 최소화
- 별도의 ToF/스테레오 없이 단일 월드카메라로 “응시 지점의 대략적 절대 거리”를 추정.
- 제품화 관점의 견고한 파이프라인
- 시선 모델과 깊이 모델을 모듈화하여 상호 교체/업그레이드 용이.
- 프레임워크/장비 변화에도 카메라-기하·융합 로직은 재사용.
4) 구현 방법(절차, 수식, 코드 구조)
A. 카메라 보정(필수 선행 단계)
- 내부파라미터(인트린직): 월드카메라의 fx,fy,cx,cyf_x, f_y, c_x, c_yfx,fy,cx,cy 확보.
- 외부파라미터(익스트린직): eye→world 변환 {R,t}\{R, t\}{R,t} 추정.
- 방법: 두 체커보드(앞뒤 배치) 시나리오에서 양 카메라(eye/world) 동시 관측 → PnP + 상대 자세로 R,tR, tR,t 계산.
- 스크립트: estimate_extrinsic_two_boards.py (rows/cols/square, 보드 간 거리(thickness) 인자 포함).
- (선택) Kappa 보정: Optical↔Visual axis 상수 오프셋을 사용자별 캘리브레이션으로 보정.
B. 시선벡터 계산(Mediapipe 기반)
- 입력: Eye 카메라 프레임(근접 눈 영상).
- 처리: FaceMesh/Iris → 눈 윤곽·홍채 중심·평면 추정.
- 시선벡터(eye좌표):
- 홍채 평면 법선 혹은 (안구중심→홍채중심) 벡터를 deye\mathbf{d}_{eye}deye로 채택.
- 안정화: deye\mathbf{d}_{eye}deye와 기준점(예: 홍채중심) EMA 스무딩.
- 좌표계 변환:oworld=R⋅oeye+t,dworld=R⋅deye\mathbf{o}_{world} = R\cdot \mathbf{o}_{eye} + t,\quad \mathbf{d}_{world} = R\cdot \mathbf{d}_{eye}oworld=R⋅oeye+t,dworld=R⋅deye여기서 o\mathbf{o}o는 ray origin(eye 카메라 좌표계에서의 기준점, 보통 카메라 중심 또는 홍채 중심 근방).
C. 깊이맵 예측(Depth-Anything v2 / Video-Depth-Anything)
- 입력: 월드카메라 프레임 I\mathcal{I}I.
- 출력: Dpred(u,v)D_{\text{pred}}(u,v)Dpred(u,v) — 상대 혹은 준-메트릭 깊이.
- 보정: 사전 캘리브레이션/측정으로 a,ba,ba,b 산출 →Dcorr(u,v)=a⋅Dpred(u,v)+bD_{\text{corr}}(u,v) = a\cdot D_{\text{pred}}(u,v) + bDcorr(u,v)=a⋅Dpred(u,v)+b(신뢰 샘플: 바닥적평면/표준 거리 타겟/레이저 거리계 등)
D. “시선 ray × 깊이맵” 교차 추정
- 픽셀↔광선 변환(월드카메라 인트린직):
- 한 픽셀 (u,v)(u,v)(u,v)의 광선 방향 r(u,v)=K−1[u,v,1]⊤\mathbf{r}(u,v) = K^{-1}[u,v,1]^\topr(u,v)=K−1[u,v,1]⊤.
- 샘플링식 교차(가장 실용적):
- 시선 ray: P(t)=oworld+tdworld, t>0\mathbf{P}(t) = \mathbf{o}_{world} + t\mathbf{d}_{world},\; t>0P(t)=oworld+tdworld,t>0
- 작은 step Δt\Delta tΔt로 진행하며, 각 P(t)\mathbf{P}(t)P(t)를 월드카메라로 투영 → 픽셀 (ut,vt)(u_t, v_t)(ut,vt)
- 깊이맵의 zmap=Dcorr(ut,vt)\,z_{map} = D_{\text{corr}}(u_t, v_t)zmap=Dcorr(ut,vt)와 zray=(P(t))Z\,z_{ray} = (\mathbf{P}(t))_Zzray=(P(t))Z 비교
- ∣zmap−zray∣|z_{map} - z_{ray}|∣zmap−zray∣가 임계값 이하가 되는 지점(또는 최소가 되는 지점)을 응시점으로 선택.
- (안정화) 근방 윈도우 최소화, 2~3프레임 EMA.
- 대안(폐형식 근사): 평면 가정(바닥·모니터) 시 평면-직선 교차로 즉시 t\*t^\*t\* 계산 → 픽셀/3D 좌표 산출.
E. 출력 & 시각화
- 픽셀 좌표: (u\*,v\*)(u^\*, v^\*)(u\*,v\*) — 월드 프레임 상 점.
- 3D 좌표: P(t\*)\mathbf{P}(t^\*)P(t\*) — 월드 좌표계의 응시점.
- UI: 월드 프레임에 점/트레일 표시, FPS/신뢰도(거리오차) 오버레이.
F. 코드 구조(요지)
gaze_depth_fusion_live.py
├─ capture/
│ ├─ eye_camera.py (OpenCV/AV 백엔드, MJPEG 설정)
│ └─ world_camera.py
├─ gaze/
│ ├─ mediapipe_iris.py (랜드마크 → d_eye, o_eye, EMA)
│ └─ rtgene_wrapper.py (옵션)
├─ depth/
│ ├─ depth_anything_v2_stream.py (Video-Depth-Anything 래퍼)
│ └─ calibrate_scale_shift.py (a,b 추정 유틸)
├─ calib/
│ ├─ intrinsics.json (fx,fy,cx,cy)
│ └─ extrinsic_eye_to_world.json (R,t)
├─ fuse/
│ └─ ray_depth_intersect.py (샘플링 교차/평면교차)
└─ ui/
└─ overlay.py (응시점/정보 렌더링)
5) 성능·운영 팁 (우리 환경 기준)
- 프레임레이트:
- Eye 캡처: 320×240@30fps (MJPEG/dshow)
- World 캡처: 512×384 ~ 640×480 (Video-Depth-Anything 대상 해상도 내)
- 지연 저감:
- RTSP/네트워크 캡처 시 OpenCV FFMPEG 옵션(버퍼 축소, TCP/UDP 선택)
- Depth 추론 스레드와 Gaze 추론 스레드 분리, 메인 루프는 최신 프레임 기준 폴링
- 정확도:
- (필수) a,ba,ba,b 보정은 동일 장면·조명에서 최소 2~3개 기준 거리로 추정
- (권장) 모니터/바닥 평면 방정식 사전추정 시, 샘플링 대신 직교 교차 적용으로 안정화
- (추가) 사용자별 kappa 캘리브레이션(5점) 적용 시 화면 오차 유의미하게 감소
6) 한계 & 다음 단계
- Mediapipe 한계: 클로즈업·저조도·부분가림에 취약 → RT-GENE/Gaze360로 gaze 백본 교체 검토.
- Depth 스케일 안정성: 환경 바뀌면 a,ba,ba,b 재보정 필요 → **온라인 보정(바닥 평면/인식 타겟)**로 자동화.
- 연산 여유: RTX 3050 Ti(4GB)에서 Video-Depth-Anything 스트리밍은 해상도·FPS 트레이드오프 필수 → ONNX/TensorRT 경량화 고려.
- 정합 신뢰도: ray×depth 일치도(예: ∣zmap−zray∣|z_{map}-z_{ray}|∣zmap−zray∣) 기반 confidence score 산출해 UI/후처리 반영.