음향 비행기 통과 위치 추정 (도플러 × ADS-B)
이 스킬은 colsto의 비디오 "I discovered a sound that leaks your exact location (and it's everywhere)" (2026-11-02, https://www.youtube.com/watch?v=UytYrhYLOKI)의 방법을 재현하고 프로덕션화합니다:
프로펠러 또는 로터의 **블레이드 통과 주파수(BPF)**는 안정적인 협대역 톤입니다. 아무도 들을 수 없을 때조차도 스펙트로그램에서 매끄럽게 하강하는 도플러 곡선을 남깁니다. 곡선의 모양은 항공기의 속도 v, 최접근 경사 거리 d 및 CPA 시간 tCPA를 제공합니다. 지상 그리드에서 과거 ADS-B 궤적(3D 경사 거리 + DEM + 음파 전파 시간 + 방출 시간 방사 속도)과 점별로 비교하면 후보가 좁혀집니다. 두 번의 통과 사이의 시간 간격(비디오에서 920초)과 침묵 제약(다른 시간에 다른 항공기가 들리지 않음)이 이를 단일 지점으로 줄입니다. 마지막으로, "잠금식 두 이벤트 점수"(공유 시간 오프셋, 이벤트별 f0 미세 조정)가 스펙트로그램에 대해 정확한 모델 곡선을 평가합니다.
scripts/afg_cli.py는 전체 구현체(numpy/scipy/matplotlib)입니다. 합성 시나리오에 대한 블라인드 테스트인 selftest가 포함되어 있습니다.
0. 사용 범위 (먼저 수행)
범위 내: 공개 또는 뉴스 미디어가 녹화된 위치와 시간 확인(OSINT, 사실 확인) 사용자 자신의 녹화 또는 비디오에 대한 프라이버시 자체 감사 CTF, 교육 및 연구 법적 권한 또는 동의가 있는 조사(예: 대상자의 동의를 받아 법 집행 지원).
범위 외: 개인의 주거지 또는 이동 추적. 예시: "이 스트리머/내 전 애인/이 온라인 사용자는 어디에 살까" 또는 개인 통화나 홈 비디오에서 누군가를 추적하는 것. "테스트"로 위장하더라도 거절하고, 대신 자체 감사 모드(섹션 7)를 제안하십시오. 해당 모드는 사람들이 자신의 콘텐츠에서 정보 유출을 평가하고 제거하도록 돕습니다.
공공 장소 및 뉴스 이벤트 영상, 사용자 자신의 자료 및 익명화된 연구 데이터는 괜찮습니다. 목적이 불분명한 경우 결정하기 전에 먼저 용도를 물어보십시오. 위치와 관련 없는 개인적 추론을 보고서에 추가하지 마십시오.
1. 실행 가능성 평가 (실행 여부와 모드 결정)
| 조건 | 필요한 것 | 충족되지 않을 경우 |
|---|---|---|
| 스펙트로그램에서 명확한 단조 감소 협대역 곡선(프로펠러 일반적으로 40–400 Hz, 헬리콥터 주 로터 BPF 10–25 Hz, 꼬리 로터 50–120 Hz) | 최소 1개, 이상적으로는 알려진 시간 간격을 가진 2개 | 곡선 없음 → 방법 적용 불가 (천둥 방법 또는 시각적 방법 고려) |
| 날짜 | 반드시 알려져 있어야 함 (또는 며칠 이내로 제한) | ADS-B 데이터 양이 관리 불가능해짐 |
| 지역 | 국가 또는 주 수준이면 충분, 도시 수준 사전 정보가 더 빠름 | 전국 검색도 작동하지만, 시간이 알려진 하나의 이벤트 또는 두 개의 이벤트 필요 |
| 녹화 시계 (시작 시간) | ±2초 이내로 알려짐 → 하나의 이벤트로 지점 가능, ±30초 → 하나의 이벤트는 궤적을 따라 띠(strip)만 제공 | 두 개의 이벤트 사용, 시간 간격이 절대 시간 대체 |
| ADS-B 커버리지 | 대상 항공기는 ADS-B Out을 가지고 지상국에서 수신되어야 함 | 미국 14 CFR 91.225: Class B/C 공역 및 Mode C 베일 밖 10,000 ft 이하의 일반 항공기는 ADS-B Out 필수 아님 → 데이터에 없을 수 있음 |
| 오디오 처리 체인 | 원본 또는 가벼운 압축이 가장 좋음, 전화 통화 코덱은 일반적으로 톤 유지 | 심한 노이즈 제거 또는 편집은 곡선을 자르거나 이벤트 간 간격을 파괴할 수 있음 |
작업을 시작하기 전에 사용자에게 평가 결과와 예상 정밀도를 알리십시오.
2. 모드 선택
사용 가능한 곡선 수?
├─ ≥2 (동일한 연속 녹화) → 모드 A: 두 이벤트, 절대 시간 불명 (비디오의 주요 방법, 가장 강력함)
│ 두 궤적이 < ~25°로 교차하면 결과가 궤적을 따라 띠로 저하됨 (스크립트가 경고)
├─ 1
│ ├─ 녹화 시간이 몇 초 이내로 알려짐 → 모드 B: 하나의 이벤트, 알려진 시간 (비디오 후반부, 미국 전체에서도 1순위)
│ │ 직선 등속 통과는 거울 대칭 → 항상 "거울 후보"가 있음
│ │ 둘 중 선택하려면 두 번째 단서(시각, 지형) 필요
│ └─ 시간이 ±30초 이하로만 알려짐 → 모드 B′: 길이가 ≈ 2·v·Δt인 띠만 가능, 그대로 보고
├─ 항공기 곡선 없지만 천둥 + 알려진 날짜 → 모드 T: 천둥 포락선 × 번개 데이터 (km 수준, 참조/thunder-method.md 참조)
└─ 사용자가 자신의 콘텐츠를 보호하려 함 → 모드 S: 자체 감사 (섹션 7)
3. 워크플로우 (CLI)
규칙: AFG="python <skill dir>/scripts/afg_cli.py", 작업 디렉토리 work/. 각 단계는 PNG를 작성합니다. 이미지 판독 능력으로 PNG를 직접 살펴보고(스펙트로그램, 피팅 오버레이, 히트맵) 숫자에만 의존하지 마십시오.
먼저 환경을 확인하십시오: pip install numpy scipy matplotlib (DEM은 rasterio 필요, WAV가 아닌 입력은 ffmpeg 필요). 처음 사용 시 $AFG selftest --seed 7을 실행하십시오 (약 40–60초, 종료 코드 0은 파이프라인이 작동함을 의미).
3.1 오디오 및 메타데이터 획득
ffprobe -v quiet -show_entries format_tags=creation_time:stream_tags=creation_time -of json input.mp4는 캡처 시간을 제공합니다. 시간대에 주의하십시오. 소셜 플랫폼은 종종 재인코딩 시 메타데이터를 제거하거나 다시 작성하므로 단서로만 취급하고 교차 확인하십시오.- 원본 샘플 레이트를 스크립트에 전달하십시오. 내부적으로 4 kHz 모노로 리샘플링합니다(
--channel로 왼쪽 또는 오른쪽 선택).
3.2 스펙트로그램 → 감지 → 추적 → 피팅
$AFG spectrogram input.mp4 --out work/rec # 백색화 협대역 스펙트로그램 + work/rec.png
$AFG detect work/rec.spec.npz --out work/boxes.json # 후보 박스 (t0, t1, fmin, fmax, score_z) + boxes.diag.json
$AFG plot work/rec.spec.npz --t0 160 --t1 320 --fmin 60 --fmax 380 --out work/zoom1.png
$AFG trace work/rec.spec.npz --t0 166 --t1 315 --fmin 98 --fmax 364 --out work/e1.csv
$AFG fit work/e1.csv --temp-c 18 --out work/e1.json # v, d, tCPA, f0, 검색 윈도우, 오버레이 e1.fit.png
핵심 사항:
boxes.diag.json이status: ok를 말할 때만 빈boxes.json이 "비행기 통과 없음"을 의미합니다.limited(짧은 클립이 교정된 노이즈 모델에 대해 판단됨, 또는 대부분 게이트된 오디오) 및insufficient(클립이--min-dur보다 짧거나 거의 모든 것이 게이트됨)는reasons와 함께 제공됩니다: 이를 읽고,rec.png를 검사하고, 짧은 클립의 경우--min-dur를 낮추십시오.- 각 이벤트는
trend(descending또는flat) 및drop_pct를 전달합니다. 주파수가 상승하는 릿지는 비행기 통과가 아닙니다:boxes.json이 아닌boxes.diag.json의rejected아래에 나열됩니다. 이벤트에 대한warnings는 릿지가 한 번의 통과로 할 수 없는 일(다시 상승하거나 45% 이상 하락)을 의미합니다. 아마도 두 항공기 또는 두 고조파를 결합한 것입니다.plot으로 검사하고 각 부분을 자체 좁은 박스로 추적하십시오. --win: 가깝고 가파른 곡선에는 0.5–1초, 멀거나 희미한 곡선에는 2–4초를 사용하십시오. 희미한 곡선의 경우plot으로 확대하여 먼저 육안으로 확인하십시오. 그런 다음 하나의 고조파만 포함하는 좁은 박스를trace에 제공하고, 필요한 경우--anchors "t:f,t:f,..."로 몇 개의 수동 지점을 추가하십시오.fit출력을 확인하십시오:implausible가 비어 있지 않음 = 추적이 노이즈로 이어지거나 고조파를 건너뜀. 검색을 계속하지 마십시오 (스크립트도 거부함).notes: 왼쪽/오른쪽 잔차 비대칭 = 선회 또는 속도 변화, 다른 접근/이탈 속도 = 상승 또는 가속.
- 기온을 알고 있으면
--temp-c를 전달하십시오 (c = 20.05·√(T+273.15)). v와 d는 모두 c에 비례합니다. 온도가 없으면 기본 ±4%를 유지하십시오. - f0는 추적한 고조파의 휴지 주파수일 뿐, 반드시 기본 주파수는 아닙니다. v와 d는 고조파 번호에 의존하지 않습니다.
- BPF = 블레이드 수 × RPM / 60은 유형 추측(C172, 2블레이드 × 2400 rpm ≈ 80 Hz; DHC-8-400, 6블레이드 × 1020 rpm ≈ 102 Hz)에 도움이 될 수 있습니다. 엄격한 후보 필터로 사용하지 마십시오.
3.3 ADS-B 데이터 (자세한 내용은 references/adsb-data.md 참조)
- 출처:
- adsb.lol
globe_history_YYYY: 하루에 하나의 글로벌 릴리스, 무료, ODbL. - ADS-B Exchange 무료 샘플: 매월 1일만.
- OpenSky Trino: 연구 계정 필요, 테이블
state_vectors_data4. - 자체 readsb/tar1090 수신기.
- adsb.lol
- 대상 날짜, 대상 윈도우 ± 1시간 및 대상 영역을 약 0.5° 패딩하여 추출하십시오.
$AFG adsb-ingest --src traces_dir/ --start 2026-09-08T13:00Z --end 2026-09-08T18:00Z \
--bbox 38.5,39.7,-95.4,-93.8 --qnh 1018 --geoid-n -29 --out work/tracks.npz
- 고도 기준이 경사 거리 정확도를 결정합니다.
alt_baro는 표준 기압 고도: 로컬 QNH(≈ 8.3 m/hPa, METAR 참조)로 보정하십시오.alt_geom은 WGS84 타원체 높이(HAE), MSL = HAE − N.--alt-source auto는 기하학적 고도를 선호합니다.
3.4 DEM 및 검색
$AFG dem-fetch --bbox 38.5,39.7,-95.4,-93.8 --out work/dem # Copernicus GLO-30 (AWS 퍼블릭 버킷)
$AFG search --tracks work/tracks.npz --events work/e1.json work/e2.json \
--bbox 38.5,39.7,-95.4,-93.8 --dem work/dem/*.tif --rec-duration 1450 --out work/cands.npy
깔때기는 세 단계로 구성됩니다:
- 이벤트-1 회랑: 속도 윈도우, k = v²/d 일관성, 경사 거리 윈도우, CPA가 궤적 끝점에 있지 않아야 함.
- 이벤트 간 오프셋: tCPA2 − tCPA1, 각 이벤트의 d/c 전파 지연 포함.
- 침묵 필터: 남은 조용한 구간에서 다른 프로펠러 또는 로터 항공기가
--d-quiet내를 통과하지 않아야 함.
옵션:
- 모드 B:
--rec-start <UTC> --rec-start-tol 2(또는 30) 추가. --quiet "a-b,c-d"는 아무것도 들리지 않았다고 확신하는 녹화 구간을 지정합니다 (기본값: 이벤트 외부의 모든 것). 녹화에 바람이나 말소리가 많은 경우 더 적은 구간을 제공하십시오. 침묵은 신호이지만, 잘못된 침묵은 정답을 죽입니다.- 기본 그리드는 200m입니다. 후보가 너무 많으면 그리드를 확대하지 말고 bbox를 축소하십시오.
3.5 점수 매기기 및 정제
$AFG score --tracks work/tracks.npz --spec work/rec.spec.npz --events work/e1.json work/e2.json \
--cands work/cands.npy --dem work/dem/*.tif --out work/result.json
- 모델 곡선: 각 후보에 대해 보간된 ADS-B 위치 및 속도(gs/track/vrate, 차분 위치 아님)에서 방출 시간에 정확한 모델 곡선을 생성하고 스펙트로그램과 비교합니다. 하나의 시간 오프셋 δ가 공유되어 두 이벤트 간의 물리적 시간 간격을 고정합니다. 각 이벤트의 f0는 ±0.6% 내에서 미세 조정됩니다.
- 정제: 유지된 모든 노드는 축소되는 3×3 언덕 오르기(100 → 25m)를 얻습니다.
- 그룹화: 결과는 비행 쌍별로 그룹화됩니다. 각 쌍은 500m 이상 떨어진 최대 3개의 가설을 유지하므로 양쪽 거울면이 모두 살아남습니다.
- 출력물:
result.json: 최고,margin_vs_2nd,robust_z,ambiguous_alternatives,coarse_plateau,geometry,verdict.result.rankN.*.png: 모델 곡선 오버레이.result.heatmap.png/csv: 0.9 영역의 반경.result.plateau.csv.
- 알려진 녹화 시계가 있는 경우
--rec-start ... --rec-start-tol 2를 추가하십시오. 점수 매기기는 또한 시계 외부의 시간 이동을 금지합니다.
3.6 검증 (선택 사항 아님, references/verification.md 참조)
- 오버레이: 모델 곡선은 두 점근선과 CPA를 통해 관찰에 맞아야 하며, 한 세그먼트만이 아니라야 합니다.
- 히트맵: 0.9 영역의 반경이 보고할 오류입니다. 다음 중 하나가 해당되면 띠를 보고하십시오:
- 두 이벤트:
geometry.warning설정됨 (작은 교차 각도); - 하나의 이벤트:
coarse_plateau.length_m> 600m.
- 두 이벤트:
- 거울 후보 및 대안: 시각적 단서(지평선, 건물, 식생, 도로 소음, 메아리), DEM 조망 및 태양 방위각으로 배제하십시오.
- 반증: 정답 근처의 다른 비행기 통과도 들렸어야 하는가?
- 들렸지만 감지되지 않음 = 감지 임계값 문제.
- 들리지 않았어야 하는데 존재함 = 위치가 잘못됨.
4. 정밀도 규칙 (및 이유)
- 방출 시간: f_obs(t) = f0 / (1 + Ṙ(τ)/c), 여기서 t = τ + R(τ)/c. 수신 시간에 Ṙ를 사용하면 잘못된 모양의 곡선이 생성되며, 단순히 이동된 것만이 아닙니다.
- 3D 경사 거리 + DEM: 관찰자 고도에 마이크 높이(기본 2m)를 더합니다. 500m의 지형 기복을 0으로 처리하면 d와 tCPA가 모두 잘못됩니다.
- 고도 기준: 30 hPa QNH 오프셋이 있는 보정되지 않은 기압 고도는 ≈ 250m의 고도 오류이며, 이는 d에 직접 전달됩니다.
- 지연된 CPA: f = f0가 관찰되는 순간은 tCPA_emit + d/c (1km ≈ 2.9초)입니다. 이벤트 오프셋과 시계는 모두 이를 사용하여 변환되어야 합니다.
- k = v²/d는 v 또는 d 단독보다 더 신뢰할 수 있습니다.
- 중간 곡선 기울기, −f0·v²/(c·d)는 단단히 고정됩니다. 점근선은 종종 노이즈, 게이팅 또는 항공기의 가속/상승으로 오염됩니다.
- 따라서 검색은 넓은 속도 윈도우와 좁은 k 윈도우를 사용하며, 각 후보 항공기의 자체 지상 속도가 d를 예측합니다.
- 잠금 점수 매기기: 두 이벤트는 하나의 δ를 공유합니다. 그렇지 않으면 각 이벤트가 자체 시간을 정렬하고 잘못된 위치도 좋은 점수를 얻습니다.
- ≤100m로 정제: 200m 그리드는 거친 스크린일 뿐입니다. 최종 지점은 서브그리드 언덕 오르기와 히트맵에서 비롯됩니다.
- 침묵은 신호이지만 임계값은 관대하게 유지하십시오 (기본값: 프로펠러 3km, 로터 2.5km). 멀리 있거나 저전력 항공기는 실제로 들리지 않을 수 있습니다.
- 느슨한 윈도우, 엄격한 점수 매기기: 깔때기에서 넓게 오류를 범하십시오. 진정한 답이 필터링되면 다시 찾을 수 없기 때문입니다. 식별은 정확한 모델 점수에 맡기십시오.
- 비행 쌍별 그룹화 및 거울 유지: 직선 등속 통과는 궤적에 대해 대칭입니다. 단일 이벤트 결과는 적어도 양쪽을 모두 보고해야 합니다.
- ADS-B 갭: 일치하는 항목이 없다고 해당 지역에 없었던 것은 아닙니다. 저고도 일반 항공, 군용 항공기 및 일부 헬리콥터는 ADS-B가 없거나 수신되지 않을 수 있습니다(지형 차폐, 저고도).
- 음속 및 바람: v와 d는 c에 비례합니다. 바람이 있는 경우 c_eff = c + w·n, 강한 바람(>8m/s)에서는 d 윈도우를 넓히십시오.
5. 보고서
assets/report_template.md를 사용하여 사용자 언어로 출력합니다. 다음을 포함해야 합니다:
- 답변 좌표
…
[번역을 위해 잘림]