Skip to content

feat(analysis): M1 온디바이스 속도 전용 분석 파이프라인#38

Merged
devpark435 merged 24 commits into
mainfrom
worktree-idempotent-crunching-beacon
Jul 8, 2026
Merged

feat(analysis): M1 온디바이스 속도 전용 분석 파이프라인#38
devpark435 merged 24 commits into
mainfrom
worktree-idempotent-crunching-beacon

Conversation

@devpark435

Copy link
Copy Markdown
Owner

Summary

  • 볼링공 트래킹 재설계 M1: YOLOv8n 검출 → 호모그래피(NCC drift 보정) → FSM 기반 release/impact 교차검증(핀임팩트 휘도신호 복원 포함) → confidence-gated 속도 추정
  • 실패 시 정직하게 "측정불가" + 그룹화된 한국어 안내 노출(내부 enum 미노출)
  • 캘리브레이션 화면(4점 탭) + 저장 시 자동 기본값 지정 + 진입점(트림 화면 AppBar) 신규 추가
  • confidence/실패사유 영속화 필드 추가(AnalysisResultEntity/Model)
  • 미사용 Gemini 분석 서비스, 죽은 CV-fallback 경로(video_analysis_service.dart, BallTracker), 고아 라우트 정리
  • 스펙: docs/superpowers/specs/2026-07-07-ball-tracking-redesign-design.md, 플랜: docs/superpowers/plans/2026-07-07-ball-tracking-m1-speed-pipeline.md (둘 다 gitignore 대상이라 이 diff엔 안 잡힘 — 별도 공유 필요)

진행 중 발견/수정된 이슈

  • 개발 중 main의 기존 analysis 피처가 브랜치와 별개로 진화해있어 몇 차례 충돌(BallTracker 삭제, ReleaseDetector API 불일치)을 그때그때 핫픽스
  • 최종 전체 리뷰에서 발견한 이슈 전부 수정: 캘리브레이션 drift-check가 자기 자신과 비교해 항상 무효화되던 버그, confidence 계산에 release/drift 반영 누락, 빈 프레임 경로의 실패사유 미설정, 죽은 코드 3건

액션 필요 (배포 전 필수)

Supabase ball_analysis 테이블에 컬럼 추가 SQL을 대시보드에서 직접 실행해야 함(에이전트가 대신 실행하지 않음):

ALTER TABLE ball_analysis
  ADD COLUMN IF NOT EXISTS speed_confidence double precision,
  ADD COLUMN IF NOT EXISTS speed_failure_reason text;

이거 없이 분석 결과 저장 시 insert 실패함.

Test plan

  • flutter test test/features/analysis/ test/golden_reference/ — 55/55 통과 (골든 회귀는 fixture 없어 정상 skip)
  • flutter analyze — 0 errors (기존 baseline 경고 11개만)
  • Supabase 마이그레이션 SQL 실행 (위 참고)
  • 실기기 수동 QA (캘리브레이션→분석→결과 화면 골든패스)

devpark435 added 24 commits July 7, 2026 18:28
main의 기존 CV fallback 경로(analysis_trim_page.dart)가 여전히
BallTracker.calcSpeedKmh를 참조 — Task 13에서 해당 경로를
새 파이프라인으로 교체하기 전까지는 유지 필요.
findReleaseFrame(int?) → findRelease(ReleaseResult) API 변경 대응.
Task 13에서 전체 파이프라인 교체 전까지 임시 어댑터.
[캘리브레이션]
- CalibrationViewModel/Providers/Overlay/Page 신규 추가
- 저장 시 setDefault 자동 호출 — 안 하면 트림 화면이 영원히 기본
  프로파일을 못 찾아 캘리브레이션 기능 전체가 죽은 코드가 됨
- 트림 화면 AppBar에 캘리브레이션 진입점 추가(기존엔 도달 경로 없었음)

[분석 파이프라인 배선]
- 트림 화면 분석 버튼을 Gemini/로컬 폴백 체인에서
  AnalysisPipeline(drift-check 포함) 호출로 교체
- 결과 화면이 참조하던 구버전 AnalysisData(video_analysis_service)를
  신규 도메인 AnalysisData로 교체, RPM 표시 제거(M1 스코프 아님),
  실패 사유 배너(speedFailureUserMessage) 추가
- 라우터의 잔존 구버전 AnalysisData 참조도 함께 교정(도달 불가능한
  라우트지만 타입 불일치로 analyze 에러 유발)
트림 화면이 AnalysisPipeline 배선으로 교체되며 GeminiAnalysisService를
더 이상 참조하지 않음. gemini_analysis_service_test.dart는 순수 로직을
자체 복제해 테스트하므로 서비스 삭제와 무관하게 유지.
[analysis_pipeline]
- drift-check가 currentFrame과 referenceFrame으로 같은 프레임(frames.first)을
  넘겨 항상 ok를 반환하던 버그 수정. 캘리브레이션 시점 레퍼런스 이미지
  (profile.referenceImagePath)를 디코드해 실제 referenceFrame으로 사용.
  레퍼런스 파일을 읽거나 디코드할 수 없으면 안전하게 recalibrationRequired 처리
- drift가 autoCorrected일 때 speedConfidence에 소폭 페널티(0.9x) 반영 (스펙 요구사항)
- 프레임 추출 결과가 비어있는 경로에서 speedFailure가 null로 남아 결과 화면에
  빈 안내 문구가 뜨던 문제 수정 (lowConfidence로 설정)
- 더 이상 쓰이지 않는 fpsHint 파라미터 제거, 호출부(analysis_trim_page,
  골든 회귀 테스트) 동기화

[speed_estimator_service]
- release.confidence가 최종 speed confidence 계산에 반영되지 않던 문제 수정

[analysis_state_machine]
- impact 판정 lane-y 임계값을 다른 곳과 동일한 실제 레인 길이(18.29m)로 통일
- video_analysis_service.dart: lib/ 어디서도 참조되지 않는 구(舊) CV-fallback
  파일. 새 도메인 엔티티와 이름이 겹치는 AnalysisData 클래스를 정의해 혼란만
  유발하고 있어 삭제
- ball_detection_service.dart의 BallTracker: Task 13에서 analysis_trim_page가
  재작성되며 마지막 참조가 사라진 죽은 코드 삭제 (BallDetection/
  BallDetectionService는 유지)
- app_router.dart: 아무 곳에서도 navigate하지 않는 고아 라우트 '/analysis/result'
  제거. extra를 Map<String, dynamic>으로 무가드 캐스팅하던 코드라 실제로
  호출됐다면 런타임에 터졌을 코드였음
[calibration_drift_checker]
- 레퍼런스(고해상도 갤러리 사진)와 현재 프레임(ffmpeg 480px 추출) 해상도가
  달라 정규화 좌표가 서로 다른 픽셀 스케일로 해석되던 문제 수정 — 비교 전
  레퍼런스를 현재 프레임 크기로 리사이즈
- 탐색 반경(12px) 특성상 driftScoreNormalized만으로는 재캘리브레이션
  임계값(대각선 5%)에 도달할 수 없어 게이트가 사실상 no-op이던 문제 수정 —
  NCC 최저 기준 미달 또는 탐색창 경계에서 최적 매칭이 나온 포인트를
  "신뢰 불가"로 집계해 2개 이상이면 오프셋 크기와 무관하게 재캘리브레이션
  요구로 판단

[analysis_pipeline]
- release 미검출을 impactNotFound로 오분류하던 실패 사유 분리
  (release 미검출 → releaseNotFound, impact만 없으면 impactNotFound)
[calibration_drift_checker_test]
- 기존 200x200 정사각 테스트는 우연히 임계값이 도달 가능해 통과하던
  케이스 — 레퍼런스 3배 고해상도(1440x810) vs 현재 프레임(480x270) 조건의
  테스트 추가: (1) 동일 장면·다른 해상도는 리사이즈 후 ok로 판정,
  (2) 현재 프레임에 마커가 전혀 없으면 NCC 최저 기준 미달로
  재캘리브레이션 요구

[gemini_analysis_service_test]
- 삭제된 서비스(gemini_analysis_service)를 검증하던 테스트 제거 —
  파일 내부에 재정의한 헬퍼 함수만 검증하는 자기순환 테스트였음
BoxFit.contain 렌더링 시 레터박스 영역을 고려하지 않고 탭 좌표를
컨테이너 전체 크기로 정규화하던 문제 수정. 저장되는 nx/ny가 이미지
상대 좌표가 아니라 컨테이너 상대 좌표였고, 이 값이 분석 파이프라인의
이미지-정규화 좌표(BallDetection.cx/cy)와 같은 공간으로 취급되어
호모그래피가 체계적으로 틀어지고 있었다.

- computeContainRect(순수 함수)로 실제 이미지 표시 영역을 계산해
  레터박스 탭은 무시하고, 탭 좌표를 이미지 상대로 변환
- 마커도 동일 변환의 역으로 정확한 위치에 렌더링
- 캘리브레이션 스텝 안내를 한국어 완전 문장으로, 마커에 순번 표시
[calibration]
- 프로파일 없이 분석 시작 시 SnackBar 대신 안내+캘리브레이션 진입
  다이얼로그로 교체 (analysis_trim_page)

[result]
- 구속 측정불가 시 "다시 촬영하기"를 primary CTA로 추가, 저장하기는
  secondary로 강등 (analysis_result_page)

[camera]
- 좌상단 닫기 버튼, 녹화 중 mm:ss 타이머 추가
- 원본 예외 노출하던 에러 메시지를 사용자 카피로 교체, 예외는 debugPrint로만
  (analysis_camera_page, analysis_trim_page)

[rpm 잔존 카피 제거]
- 로딩 위젯의 "회전수 측정 중" 스텝 제거
- 선택 화면 팁 문구를 구속 측정 중심으로 교체
- 히스토리 카드 "RPM 미측정" 행 제거
- 상세 화면 RPM 통계 제거, 구속 단독 중앙 정렬

[theme]
- 선택 화면 안내 박스의 하드코딩 white-alpha 배경/보더를
  AppColors 팔레트 토큰으로 교체 (라이트 테마 대응)
대형 PR(수십 커밋)에서 15턴 내에 리뷰를 못 끝내고
error_max_turns로 실패 (run 28901873998).
M1 파이프라인 실기기 검증을 위해 진입점 복원.
테플 내부테스터 배포는 애플 베타심사 안 거치므로 리스크 없음.
정식 App Store 제출 전에는 재검토 필요 — 원래 비활성화 사유(앱스토어 이슈)
자체가 해소된 건 아님.
@devpark435 devpark435 merged commit 744d411 into main Jul 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant