Repository navigation
feat(analysis): 결과화면 볼 궤적 오버레이 (M3 조기착수) - #41
Merged
Merged
Conversation
CustomPaint가 자동으로 클리핑한다는 오해 소지 있는 표현 수정 — 현재 안전한 건 컨테이너 밖 좌표가 실제로도 화면 밖 영역이라 우연히 문제없는 것뿐, 명시적 클리핑 보장이 아님을 명확히 함.
[analysis_state_machine]
- 실기기 증상1: 손/어프로치 구간까지 궤적선이 그려짐
→ release phase(릴리즈 직후 4프레임 전환 창)는 공이 아직 레인 평면 밖(손 안)일 수 있어
homography 투영이 왜곡됨. release phase의 궤적 누적을 제거하고 flight phase부터만
누적. flight에서도 yM>=0(파울라인 이후) 가드를 belt-and-suspenders로 추가.
- 실기기 증상2: 브레이크포인트 구간에서 궤적이 끊기고 speed 측정도 anchorMismatch로 실패
→ YOLO가 훅 브레이크포인트(보통 6~12m)에서 일시적으로 검출을 놓치면 5프레임 연속
미검출만으로 곧장 impact로 오판정, impactFrame이 너무 이르게 확정되어 다운스트림
핀-루미넌스 신호와 어긋남. null-count impact 폴백에 레인 깊이 게이트(18.29m의 약
75%인 14.0m 이상)를 추가 — 브레이크포인트에서는 오탐하지 않고, 회복 못 하면
impactFrame이 null로 남아 정직하게 실패한다.
[test]
- release phase 궤적 누적 안 됨 / flight phase yM<0 필터링 / 브레이크포인트 구간
미검출 오탐 안 함 / 핀 근처 미검출은 정상 impact 전이, 4개 회귀 테스트 추가.
[pin_impact_detector_service] - 실기기 로그에서 release+minTravelFrames 지점(검색 시작지점 자체)에서 곧바로 오탐(핀 충돌 프레임: 56)되던 증상. 원인은 루프 첫 비교만 release 프레임과 20+ 프레임 격차로 비교하고 이후는 직전 프레임(1프레임 격차)과 비교하는 불일치 — 첫 비교가 카메라 미동/조명 드리프트 누적만으로 임계값을 넘겨 오탐. - 씨드를 release가 아닌 searchStart 프레임으로 바꿔 release~searchStart 구간의 자연드리프트를 비교 대상에서 제외, 이후 전 구간을 일관된 1프레임 격차로 비교하도록 수정. - 실기기 재현 회귀 테스트 1건 추가(기존 4건 영향 없음, 수기 트레이스로 확인 후 실행 검증). [analysis_pipeline] - 손구간(backswing/approach) 렌더링 의혹(미확인 사용자 리포트)을 다음 실기기 테스트에서 확인하기 위한 궤적 진단로그 추가. 동작 변경 없음.
실기기 재현: 검색구간 전체(56~149)를 훑어도 핀임팩트가 아예 미감지됨(오탐 아님). 핀존비율(상단 20%)이 실제 촬영각도랑 안 맞는지, 임계값(15%/30)이 너무 빡빡한지 구분하기 위한 진단. 동작 변경 없음, 로그만 추가.
궤적 오버레이 데이터(레인 y범위)는 정상인데 실기기 화면에는 여전히 손 구간에 선이 그려진다는 리포트 — TrajectoryOverlay가 BoxFit.cover 매핑에 쓰는 VideoPlayerController.value.size가 분석용 ffmpeg 추출 프레임(480x853 portrait, 호모그래피 좌표계 기준)과 가로세로 비율이 일치하는지 확인하기 위한 진단. 동작 변경 없음, 로그만 추가.
[원인] - 오버레이가 정적이라 영상 시작(백스윙 중)부터 전체 궤적이 그려져 "손 구간에 선이 그려진다"로 보임 — 궤적 데이터 자체는 정상이었음 (실기기 3회 연속 레인 y범위 정상 확인, 비디오 크기 가설도 반증) [변경] - TrajectoryFramePoint 도입: 궤적 점에 분석 프레임 번호 보존 - TrajectoryOverlay: 재생 위치에 동기화해 공이 지나간 지점까지만 선을 그리는 점진 렌더링 — 레인 위에 페인트되는 느낌, 루프 재시작 시 자연 리셋 - VideoPlayerValue.position이 ~500ms 간격으로만 갱신되므로 Ticker로 vsync마다 위치 보간 추정(0.25x에서 선 끊김 방지) - 비디오 크기 진단 debugPrint 제거(가설 반증 완료)
[원인] - 핀존이 프레임 상단 20% x 전폭 하드코딩 — 레인 전체가 잡히는 실기기 구도(480x853)에서 핀은 존의 극히 일부라, 핀이 전부 넘어져도 존 전체 대비 변화율 2~3%에 그쳐 임계값 15%를 구조적으로 못 넘음 (실측: 탐색구간 최대 9.4%, 실제 충돌 프레임에서도 미달) [변경] - computePinZone: 핀덱 라인(y=18.29m)과 1.29m 앞 지점을 laneToFrame으로 투영해 핀 실제 위치 주변의 타이트한 존 산출 (원근 스케일 기반으로 위 1.5유닛/아래 0.5유닛 확장, 퇴화 시 null → legacy 존 폴백) - 호모그래피 존 경로는 임계값 10% (신호 대 잡음 개선 반영), legacy 경로는 15% 및 기존 동작 완전 유지 - 감지/미감지 debugPrint에 존 경로·좌표 진단 정보 추가
[궤적 렌더] - 균일 두께 폴리라인 → 레인 평면에 투영된 리본(공 폭 ±0.11m)으로 교체. 가까우면 넓고 멀면 좁은 원근이 생겨 화면 위 선이 아니라 레인 위에 그려진 느낌을 만든다 [궤적 정제 (trajectory_refiner)] - 선두 트림: 릴리즈 직후 손 근처 공(레인 평면 밖)의 왜곡 투영 포인트를 y 속도 일관성([0.08, 0.8] m/frame) 기준으로 제거 — 다운스윙 구간부터 선이 생기던 문제의 원인 - 단조 필터(y 역행 오검출 드롭) + xM 3점 이동평균(좌우 지터 완화) [핀 충돌 탐색] - 탐색 시작을 릴리즈+20프레임 대신 추적된 공이 16m 도달한 프레임으로 게이트 — 릴리즈 직후 볼러 팔로스루가 핀존에 걸려 나던 오탐(실측: 프레임 57에서 18.7% 오탐, 실제 충돌 ~115) 원천 차단. 이 오탐이 FSM 임팩트와 불일치를 만들어 속도 측정까지 막고 있었다
[궤적 렌더] - 정제된 궤적을 3차 최소제곱 x(y) 곡선으로 피팅 후 0.25m 균일 리샘플 — 검출 지터로 각진 폴리라인 대신 매끈한 곡선(직진+훅). 프로 샷트레이서 방식 - 리본을 꼬리→머리 불투명도 램프(코멧) + 공 위치 글로우 헤드로 렌더 [핀존] - 존 수직 크기를 레인 바닥 원근(낮은 카메라 각에서 극도로 압축, 실측 17px)이 아니라 압축 없는 레인 폭 기준 스케일로 유도 — 서 있는 핀(수직 물체)을 실제로 담는 존이 됨 (~110px). 직전 실측에서 최대 변화율 지점이 실제 충돌(111)로 이동했으므로 존 크기만 남은 상태였다
[궤적 정제] - 트림 전 xM/yM 3점 중앙값 프리필터 추가 — 반사광 등 고립 스파이크가 선두 트림의 속도 유효성 검사를 계속 실패시켜 궤적 앞 절반을 잘라먹던 문제(실측: 선이 9.24m부터 시작) 해소. 손구간 왜곡 (연속 드리프트)은 중앙값을 통과하므로 트림이 여전히 잘라낸다 [핀 충돌] - 호모그래피 존 경로를 임계값 첫-돌파(10%)에서 탐색창 내 최대 변화율 프레임 채택(바닥값 6%)으로 변경 — 탐색이 이미 공의 16m 근접 이후로 게이트돼 창이 충돌 근처로 좁고, 실측 2연속 런에서 최대 변화율 지점이 실제 충돌 옆(111, 107)인데 크기(9.4%)만 문턱 아래라 미감지되던 문제 해소. legacy 경로는 기존 동작 유지
정제 단계가 초반 구간의 지속적 검출 노이즈를 잘라내면(실측: 8m대 시작) 선 앞 절반이 비어 보인다. 릴리즈 직후 2~8m는 스키드 구간이라 물리적으로 직선에 가까우므로, 피팅된 곡선 시작점의 기울기를 원시 궤적 시작 y(최소 2.5m)까지 그대로 늘려 릴리즈 직후부터 선이 보이게 한다. 3차식 외삽은 꼬리가 요동쳐 선형만 사용. xM은 레인 폭, frame은 0 이상으로 클램프.
[원인] - 기존 구현은 release+8~24 프레임 구간의 원시 검출 좌표로 프레임간 이동거리 중앙값을 산출 — 이 구간(레인 2~8m)은 검출 y 지터가 ±0.5m/frame에 달해 거리 절댓값이 부풀려지는(±노이즈가 전부 +로 합산) 계통적 과대추정으로 50km/h 상한을 넘겨 outOfRange로 실패. 게다가 이 경로에 로그가 없어 무음 실패였다(실기기: 궤적·핀 감지 성공인데 구속 측정불가, 로그 없음) [변경] - 정제 궤적(중앙값+트림+단조+스무딩 완료)에 y = a + b·frame 최소제곱 회귀 → b×fps가 속도. 지터가 양방향으로 상쇄되어 강건 - 신뢰도 = 회귀 R² × impact 신뢰도 페널티 × release confidence - 모든 실패 경로에 수치 포함 debugPrint — 무음 실패 제거 - 원시 detections/homography 의존 제거 (파이프라인 시그니처 정리)
실사용에서 far 코너를 핀덱(핀 발밑)이 아닌 브레이크포인트/오일라인 부근에 두는 캘리브레이션 오류 발생 — 코너를 어디에 놓아야 하는지 시각 가이드가 전혀 없었다. 이 스큐가 구속 저평가(15.8km/h)와 궤적 시작/끝 오프셋(손~BP 구간)의 근원. - 코너 4점으로 호모그래피를 실시간 산출해 에로우(4.57m)· 레인지파인더(12.19m) 기준선을 프레임에 투영 — 드래그 중 실제 레인 마킹과 겹치도록 맞추면 캘리브레이션 검증 완료 - 파울라인/핀덱 라인은 라벨만 표시(코너 외곽선과 중복 회피) - 배너 문구에 기준 명시: 가까운 두 점=파울라인 양끝, 먼 두 점= 핀이 서 있는 레인 끝 바닥(핀 몸통 아님) - 퇴화 사각형(드래그 중 교차 등)은 그리드만 생략, 크래시 없음
호모그래피는 레인 평면 전제인데 공 중심(바닥에서 반지름 ~11cm 위)을 투영해, 평면 밖 점이 실제보다 앞으로 찍히는 계통 오차가 있었다. 낮은 카메라 각에서 오버슛이 거리에 비례해 커진다 — 실측 물증: 궤적 y가 핀덱(18.29m)을 넘는 18.65m까지 관측, 회귀 구속(13.7km/h)과 릴리즈→임팩트 앵커 교차검증(~26km/h)의 2배 불일치. - BallDetection.contactPoint(bbox 바닥 중점 = 공-레인 접점, 평면 위의 점이라 바이어스 0) 추가, 공 위치→레인 매핑 전 지점 교체 - refined 궤적 (frame:y) 진단 덤프 추가 — y(t) 형태로 잔존 왜곡 판독용 - 실기기 재현 회귀 테스트가 접점 투영만으로 outOfRange 실패에서 속도 산출 성공으로 전환됨 (계통 오차 실재의 테스트 증거)
320 입력에서 far-lane 공이 3~5px로 뭉개져 생기는 localization 지터 (정제 드롭률 58%, 회귀 R² 0.67 실측)를 640 입력 + s급 모델로 개선 시도. 기존 yolov8n(@320, 1클래스 전용학습)은 롤백용으로 애셋 유지 — 상수 5개 되돌리면 복귀. - COCO 80클래스 출력(1,84,8400) 파싱: sports ball 클래스 행만 사용, 1클래스(5차원) 레이아웃도 같은 경로에서 지원 - init 시 출력 shape 진단 로그 — 레이아웃 가정 실기기 검증용 - 주의: 범용 COCO 모델이라 전용학습 nano 대비 recall이 후퇴할 수 있음 — 다음 실기기 로그(raw 검출 수·드롭률·R²)로 비교 판정
신형 litert 변환기 산출물은 torch 채널-우선(NCHW [1,3,640,640]) 입력을 요구하는데 입력 빌더가 구형 export(NHWC) 고정이라 CONV_2D가 준비 단계에서 실패, 전 프레임 검출 0건 (실기기 재현: input_channel % filter_input_channel != 0). 로컬 인터프리터로 텐서 스펙 확인 후 채널-우선 경로 추가 — 롤백용 NHWC 경로도 상수 분기로 유지. init 진단 로그에 입력 shape/레이아웃 추가.
yolo11s 교체 후 검출 0건 원인 규명 과정: - 로컬 파이썬 추론으로 모델 자체는 정상 확인(bus.jpg에서 person 0.93, 좌표 정규화·sigmoid 적용 확인) → 문제는 앱 쪽 전처리 - 기존 스쿼시 리사이즈는 480x853 포트레이트를 640x640으로 뭉개 공을 타원으로 왜곡 — ultralytics 학습 전처리(letterbox)와 불일치 변경: - letterbox(비율 유지 + 회색 114 패딩) 전처리 + 검출 좌표 역매핑 (computeLetterbox/unletterbox 순수 함수, 단위테스트 포함) - 전 프레임 최고 신뢰도 진단 로그 — 검출 0건이 추론 붕괴(점수 ~0)인지 임계값 근접 미달(전처리/클래스 문제)인지 다음 로그에서 즉시 판별
새 검출 모델로 데이터 품질이 확보된(raw=refined 37, 드롭 0%) 후 남은 이음새 3개: [속도] - impact low 시 하드 거부(anchorMismatch)를 신뢰도 페널티(x0.7)로 완화 — 현재 속도는 정제 궤적 회귀라 임팩트 프레임을 계산에 안 쓰는데, 옛 앵커 산식 시절 게이트가 남아 완벽한 회귀 데이터를 통째로 거부 하고 있었다 [핀 충돌] - argmax 바닥값 6% → 4%: 3연속 런에서 위치는 항상 실제 충돌 프레임 (107/118/118)으로 정확, 크기만 5.9~10.2% 변동 — 5.9% 실측이 0.1%p 차로 잘리는 것 방지 [궤적] - 끝 연장(extendCurveEnd): 원거리(17m+)에서 공이 화면상 수 픽셀이라 검출이 끊겨 선이 핀 직전에 멈춤 — 마지막 추적점이 14m 이상이면 핀덱(18.29m)까지 끝 기울기로 연장. 시작 연장과 대칭
파이프라인이 산출하는 구속 신뢰도(회귀 R² × 임팩트 신호 × 릴리즈 신호)를 화면에 노출 — 측정값을 얼마나 믿어도 되는지 사용자가 즉시 판단할 수 있게 한다. 0.75 이상 "신뢰도 높음" / 0.5 이상 "보통" / 미만 "참고용". _StatRow의 기존 badge 슬롯 활용(미사용 warning 해소).
[auth] - Info.plist에 GIDClientID 추가 — 로컬 빌드는 CI와 달리 dart_defines.json 주입이 없어 GOOGLE_IOS_CLIENT_ID가 빈 값이 되고, google_sign_in 7.x가 Info.plist 폴백(GIDClientID)도 못 찾아 "No active configuration" 에러로 Google 로그인 자체가 불가했다. iOS client id는 이미 같은 파일의 URL scheme에 노출된 값(시크릿 아님) [core] - 앱 업데이트 확인을 debug 빌드에서 스킵 — 스토어 미등록 번들 id 조회 에러 로그만 만들고 안내 의미도 없음
[핀 폭발 감지 재설계] - 실기기 프레임 실물 판독: 기존 방식(직전 프레임 diff argmax, 이후 첫-돌파+지속성)이 공의 존 통과(117~119)를 실제 핀 폭발(~128)로 오인 — 공 접근은 씨드 대비 diff에 프레임당 +2%p 완만한 램프를, 폭발은 뚜렷한 급증(+4.9%p) 후 34% 영구 고착을 만든다(실영상 계측) - 새 판정: (1) 창 끝 5프레임 중앙값 >= 15% (영구 변화 게이트 — 공 통과/스파이크는 원상복구되어 탈락) (2) 시점 = 씨드 대비 diff의 최대 단일 프레임 증가(Δd argmax). 실영상에서 128 정확 적중 검증 - legacy(존 미지정) 경로는 기존 동작 유지 [앵커 기반 구속] - 중·원거리 호모그래피 스케일 왜곡(광각 렌즈 + 원거리 원근 민감도) 으로 회귀 구속이 과대(측정 속도가 레인 따라 32→40으로 가속하는 물리적 모순 실측). 릴리즈 직후 y(에로우로 검증되는 근거리)와 핀덱 물리 상수 18.29m, 시간 = 릴리즈→핀 폭발로 산출하는 앵커를 primary로 승격 — 회귀는 교차검증(15% 초과 불일치 시 신뢰도 차감) - FSM-핀 일치 허용창 3/12프레임으로 완화 (FSM은 왜곡 영향으로 이르게 잡히는 실측 반영)
[진단] - TrajectoryOverlay에 1초 스로틀 debugPrint(kDebugMode 전용): 재생 위치·추정 위치·현재 프레임·공개 포인트 수 — 리본이 영상보다 ~12프레임 뒤처지는 증상의 원인(플레이어 위치 보고 지연 vs pts 오프셋 vs 매핑 버그) 판별용 [캘리브레이션 가이드] - far 코너 안내를 "핀 발밑" → "구석 핀(7·10번) 머리 높이"로 변경. 실측 경험칙: 발밑 기준은 광각 렌즈의 중·원거리 압축으로 구속 과대 (37.8km/h), 머리 높이 기준은 현실값(28.7km/h)에 근접
수동 캘리브레이션 y 스케일이 시도마다 흔들려(같은 영상이 최대 y 17.5m 또는 11.5m로 분석됨) 절대 y 기준 게이트가 통째로 죽는 문제: FSM 임팩트(18.29 도달/14m 소실)가 없으면 핀 폭발 감지·속도 산출이 아예 실행되지 않았다(실측: 11.5m 스케일에서 측정불가). - 조기 반환을 release만 필수로 완화 — FSM 임팩트 없어도 핀 폭발 감지 실행, 발견 시 medium confidence 단독 신호로 속도 진행 (폭발은 픽셀 증거라 스케일 오류와 무관한 시간축 제공) - 핀 탐색 시작 = 궤적 소실 프레임(절대 y 16m 스캔 폐기). BP 등 중간 유실 오탐은 폭발 감지의 영구변화 게이트가 방어 - ImpactDetectorService.detect: homographyImpactFrame nullable, 4분기(둘 다/핀만/FSM만/없음) 명시화, 없음이면 null 반환 - 효과: 스케일 오차의 속도 영향이 릴리즈 y 항목(±7% 수준)으로 제한
[핀 폭발 탐색] - 탐색 시작 = 궤적 소실 프레임 + (핀덱 도착 예상 프레임 ÷ 2). 소실 직후를 씨드로 쓰면 공이 핀존에 진입하는 순간의 날카로운 Δ가 폭발보다 먼저 argmax에 잡히는 오탐이 3회 연속 실측(115/115/117 vs 실제 ~128). 도착 예상은 남은 거리와 마지막 구간 기울기를 같은 캘리브레이션 자로 재므로 스케일 왜곡이 비율에서 약분됨 — 절대 y 기준의 취약점 없음. 절반만 더하는 건 외삽 오차로 폭발을 지나치지 않기 위한 보수적 마진 [진단] - 무보정 앵커 구속 로그 추가(릴리즈 y=2.5m 상수 가정) — 캘리브레이션 의존을 완전히 제거한 변형. 여러 캘리브레이션 시도에서 이 값이 안정적이면 "구속은 무보정, 궤적만 보정" 단순화의 실측 근거
[시간축 계측 — 동작 불변, 로그만] - 소스 메타(ffprobe: duration/avg·r_frame_rate/nb_frames/실효fps)와 추출 프레임 연속 중복 카운트 로그. 실측 구속(37.7)과 사용자 실제 구속(~25)의 차이(×1.22)가 원본 실효 fps(~24.6, dup=27 정황)의 30fps 재라벨 왜곡과 크기가 일치 — 다음 실행 로그로 확정/기각 [핀 폭발 판정 v3 — 램프-종료 판별] - v2(Δd argmax)는 공 진입 Δ가 폭발 Δ보다 날카로워 진입 순간(117)을 잡는 오탐 재현. 물리 구분: 진입 후엔 diff가 계속 상승(램프), 폭발 후엔 정체(플래토) — 실측: 진입 Δ13%+이후 +14%p vs 폭발 Δ36%+이후 −1.2%p. Δd≥3%p 후보 중 이후 8프레임 순증가 ≤4%p인 최초 후보 채택, 창 끝 후보는 lookahead 면제, 전부 램프면 argmax 폴백. 영구변화 게이트 유지, 성공 로그에 존 LTRB 병기 [분석 캡] - 150→210프레임(7초): 실측 트림 영상 7.1초 — 5초 캡이면 느린 공의 핀 임팩트가 분석 창 밖으로 잘린다
재설계 문서(2026-07-12 rethink §4C) Phase 1. 수동 캘리브레이션의 픽셀→미터 스케일이 시도마다 ±30% 요동해(실측: 같은 영상이 최대 y 11.5~17.5m) 구속 절대값의 근거로 쓸 수 없음이 결론. - 구속 = 물리 상수 18.29m ÷ (핀 폭발 시각 − 파울라인 통과 시각). 파울라인 통과 = 회귀 y=a+b·f의 y=0 근(−a/b) — 스케일 오류 s가 a,b에 같은 비율로 곱해져 근에서 약분됨: 수학적으로 스케일 불변 (스케일 ×0.6 입력에서 구속 동일함을 테스트로 박제) - 유효성 가드: 전진(b>0), 통과가 폭발보다 앞, 릴리즈 근방 (−45~+15프레임) — 실패 시 회귀 폴백 - 기존 앵커(첫 샘플 y 기반)·무보정 진단 로그는 이벤트-시간으로 대체(제거) — 이벤트-시간이 무보정의 완성형 - 캘리브레이션 화면에 "구속 측정에는 영향 없음" 명시 — 궤적 시각화 전용으로 공식 강등
[존 독립화 — Phase 3] - 핀 행(흰 핀들이 검은 피트 배경 앞에 늘어선 띠)을 이미지에서 직접 탐지(pin_row_detector): 행별 밝은 픽셀 run 3개 이상 + 비율 조건을 만족하는 최하단 밴드. 핀 폭발 감지 존의 마지막 캘리브레이션 의존 제거 — 존 소스 3단 체인(자동탐지 → 호모그래피 → legacy)과 소스 진단 로그([PinZone]) 추가 - 릴리즈 시점 프레임 사용(핀 온전, 공은 근거리) [엔트리 앵글] - 피팅 곡선 끝 기울기에서 핀덱 진입각(도) 산출(entryAngleDeg), 결과 화면에 보조 지표로 표시. 캘리브레이션 좌표 기반이라 구속과 달리 참고치 성격(doc 명시)
시뮬 캐시의 실영상 2편으로 초기 휴리스틱을 오프라인 재현한 결과, run 3개+ 조건만으로는 밝은 multi-run 행이 12개 밴드(조명·기계·레인 반사·마킹)나 나와 최하단 규칙이 레인 반사(프레임 ~69% 지점)를 잡았다. 결정적 판별자를 계측으로 확정: - 핀 행의 run 사이 틈 = 피트(검정, p25 25~60) / 반사·마킹의 틈 = 밝은 나무색(p25 104~177) → 행 자격에 "틈 p25 < 60" 추가 - 밴드 병합 2행 허용 + 최소 6행, 최하단 선택 → 실영상 2편 모두 핀 밴드(0.33~0.38) 정확 격리 (기계 유사 밴드 0.30~0.31 배제) - 높이 하한(1.5%)은 최소 행수 규칙으로 대체 같은 캐시로 시간축도 최종 판결: 카톡 원본 212프레임/7.04s = 정확히 30fps 균일, 갭·중복 0 — dup=27은 ffmpeg 통계 노이즈였고 fps 재라벨 없음(sampleFps 교정 불필요, 이벤트-시간 값이 물리적 진실).
픽셀에서 깊이를 재던 방식을 버리고, 거리를 아는 랜드마크 2개를 볼이 "언제" 지났는지로 잰다. 등속 운동에서 투영 x = xv + K/(i−a) 는 레인 거리 u가 프레임 번호에 선형인 것과 동치이므로, 랜드마크의 픽셀 깊이는 필요 없고 통과 프레임만 있으면 된다. 소실점 근처에서 1px이 수십 cm가 되는 원근 압축(기존 방식의 지배적 오차원)이 계산에서 빠진다. 랜드마크: 바깥 조준 화살표 2개(board 5·35, 둘 다 12ft)가 만드는 iso-u 선과, 헤드핀 접촉(핀 이동 개시 프레임). [lane_landmark_speed.dart] - LaneLandmarkLine + landmarkCrossingFrame: 외적 부호 변화 선형보간으로 통과 프레임을 소수점까지. 외적만 쓰므로 화면 종횡비에 불변 — 정규화 좌표를 그대로 넣어도 된다 - estimateLandmarkSpeedKmh: Δu ÷ Δt. 호모그래피·유저탭 불필요 - arrowLineFromDetections: 셰브론에서 픽셀 최장 쌍 = 같은 거리(12ft)인 양 끝. 원근이 진행방향을 압축해 항상 성립(실측 204px vs 101px). 일직선이면 null로 레인 이음매 오탐 배제 [lane_landmark_speed_test.dart] - 실영상 계측치(1920×1080/29.98fps) 고정. 에로우 통과 f48.3, 핀 접촉 f126 → 20.1 km/h - 조건수 회귀 테스트: 에로우 선 ±15px → 1km/h 미만, 충돌 프레임 ±4 → 1.1km/h 미만. 대조군인 에로우 단독 호모그래피는 ±1px에 29~98 km/h였다
궤적 연장과 리본 폭이 캘리브레이션에 물려 있던 것을 끊는다. 관측 구간 선은 원래도 정확했다 — lane = frameToLane(픽셀)이고 laneToFrame이 진짜 역행렬이라 왕복이 상쇄되기 때문. 문제는 레인 좌표를 실제로 믿고 쓰는 세 곳이었다: - extendCurveEnd(y=18.29): 목표가 레인 스케일에 물려 있어 스케일이 흔들리면 연장을 안 하거나 핀 한참 너머까지 늘린다. 게다가 직선 연장이라 실제 쌍곡선 궤적과 끝에서 벌어진다 - extendCurveStart(2.5): 동일 - 리본 폭 ±0.11m 환산: 뚱뚱하거나 실낱같이 나온다 [projective_track_model.dart] - 등속 + 원근이면 1/(ny − nyv)가 프레임 번호에 선형. 미지수가 소실점 하나뿐이라 1차원 탐색(기하 격자 + 삼분 탐색) + 매 후보 선형 최소제곱으로 적합. 잔차는 reciprocal이 아니라 ny 공간에서 평가 - 가로는 nx = p + q·ny — 레인 위 직선은 화면에서도 직선이라 훅이 끝난 후반부에 적합해 연장에 사용 - 리본 폭은 widthN = s·(ny − nyv) 원점 통과 1모수 모델. 겉보기 크기도 1/(u+c)에 비례 - buildProjectiveRibbon: 관측 프레임은 검출값 그대로(정확히 공 위에 얹힘), 모델은 관측 밖 구간에만 [analysis_pipeline.dart] - collectPixelTrack: 정제 궤적이 채택한 프레임의 픽셀 관측만 수집 - buildPixelRibbon: 실측 핀 충돌 프레임까지 연장. 적합 실패 시 null → 기존 레인 좌표 리본으로 폴백 - 최종 AnalysisData가 픽셀 리본 우선 사용, 소스를 로그로 남김 [테스트 17개] - 실측 궤적 적합 시 소실점이 파이썬 투영적합값과 일치, rms < 화면의 0.2% - 뒤 2점을 빼고 적합해도 직선 외삽보다 예측이 정확 - 직선 외삽은 핀덱 부근에서 소실점을 넘어 발산(물리적 불가) — 회귀 고정 - 폭 모델이 실측 비율(f63 51px → f93 32px) 재현 - 관측 프레임 리본 중심이 검출값과 1e-9 이내 일치
테플은 릴리즈 빌드라 debugPrint를 볼 수 없다. 폴백으로 떨어지면 결과가 예전과 똑같이 나와 "아무것도 안 바뀐 것"과 구분이 안 되므로, 궤적·구속을 어느 경로로 계산했는지 화면에 노출한다. [arrow_detector.dart] - 국소 배경(적분영상 박스 평균) 대비 어두움으로 조준 화살표 검출. 전역 임계값은 조명 기울기에 무너져 쓸 수 없다 - 절대 픽셀 크기를 박지 않는다 — 카메라 거리가 달라지면 화살표 크기가 변하므로, 후보 중 서로 크기가 비슷한 무리를 고른 뒤 모양으로 검증 - isChevron: 양 끝을 잇는 선에 대한 수직 편차가 한쪽 방향이고 가운데가 최대인 단봉인지 확인. 일직선(레인 이음매)·지그재그를 배제 - 장변 960px로 축소 후 처리 — 해상도 독립 [analysis_pipeline.dart] - estimateLandmarkSpeed: 릴리즈 프레임에서 화살표 → iso-u 선 → 볼 통과 프레임 ~ 핀 충돌 프레임의 시간으로 구속. 호모그래피 미사용 - adoptSpeed: 랜드마크 값이 10~50km/h면 채택, 아니면 기존 코어 폴백. 랜드마크 쪽 조건수가 두 자릿수 좋다(에로우 선 ±15px → ±0.4km/h) - buildPixelRibbon이 적합 rms를 함께 반환(중복 적합 제거) [analysis_data.dart] - TrajectorySource / SpeedSource enum + 진단 필드 4개. 채택 여부와 무관하게 두 코어의 값을 모두 기록해 나란히 비교 가능 [analysis_result_page.dart] - _QaDiagnosticsBadge: 좌상단에 "궤적 픽셀투영 · N단면 · rms N px", "구속 랜드마크 20.1 · 기존 33.2". 폴백이면 경고색 - 임시 위젯 — 정식 릴리즈 전 진단 필드와 함께 제거 [테스트 18개] - 합성 프레임에서 화살표 7개 검출(레인 가로/세로 양쪽, 1920×1080 축소 포함) - 검출 결과 + 실측 볼 궤적 end-to-end → 20.1km/h 재현 - 일직선·지그재그·개수 범위 밖·탐색영역 밖 전부 빈 리스트 - adoptSpeed 채택 규칙 4종
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AnalysisStateMachine이 이미 release~flight 단계에서 누적하던 궤적 데이터(그동안 버려지고 있었음)를 파이프라인에서homography.laneToFrame()으로 프레임좌표 변환해 노출computeCoverRect(BoxFit.cover 대응) +TrajectoryOverlay위젯 — 기존computeContainRect(BoxFit.contain, 레인확인화면용)와 별개, 서로 안 건드림진행 경위
원래 M3(궤적)는 M1/M2 검증 후 마지막 단계였으나 우선순위 변경으로 조기 착수. 브레인스토밍 스킬로 스펙 §11 작성 → writing-plans로 3태스크 플랜 → subagent-driven-development로 태스크별 구현+컨트롤러 직접 재검증 → 최종 전체 브랜치 리뷰(opus, Critical/Important 0건).
Test plan
flutter test test/features/analysis/ test/golden_reference/— 64/64 통과flutter analyze— 0 errors (기존 baseline 10개만)