초안 — 네가 고쳐야 할 곳
이 문서는
gpr-viewer/README.md,gpr-synth/README.md, 실제 파일 수를 근거로 내가 초안을 쓴 것이다.
결과항목은 비워 뒀다. 측정한 숫자가 로컬에 없어서 지어낼 수 없다.
학습 결과(precision·recall·F1)를 찾으면 채우고, 이 상자는 지운다.
도로 지하 GPR 탐사 자료에서 공동(싱크홀 전조)을 자동으로 찾는 시스템. 장비 원본 파일을 직접 읽는 뷰어와, 그 위에 얹은 검출 계층으로 이루어진다.
왜
도로 밑 공동은 GPR로 탐사한다. 그런데 판독은 사람이 눈으로 한다. 레이더그램에서 쌍곡선 모양을 찾는 일이라 느리고, 판독자마다 결과가 다르다. 탐사 한 건이 수천 트레이스라 놓치기도 쉽다.
자동화하려면 두 가지가 필요했는데 둘 다 없었다.
- 장비가 뱉는 원본 파일을 읽는 방법 — 규격서가 공개돼 있지 않다
- 학습용 라벨 데이터 — 공개 데이터셋은 우리 장비 포맷과 맞지 않는다
어떻게
장비에 묶이지 않는 구조
원본 .t3r(ISUNG)을 읽어 정규 큐브(ntrace × nchannel × nsample) 하나로 바꾼다. 그 뒤의 모든 코드는 장비를 모른다.
막힌 데 — 장비마다 포맷이 다르고, 규격서가 없다. 장비를 바꾸면 코드를 다시 쓰게 된다.
뚫은 방법 — 어댑터를 readers/ 한 곳에 몰고, 그 바깥은 전부 정규 좌표계로만 말하게 했다. 라벨도 화면 좌표가 아니라 정규 좌표로 저장한다. 그래서 화면 설정(게인·배경제거)을 바꿔도 라벨이 어긋나지 않고, 나중에 다른 장비를 붙여도 검출·라벨·평가 코드는 그대로다. 반대 방향(정규 큐브 → 타사 포맷)도 되므로 변환 허브로도 쓴다.
학습 데이터를 물리로 만들어냈다
막힌 데 — 라벨 붙은 GPR 데이터가 세상에 없다. 사람이 붙이려면 몇 년이 걸린다.
뚫은 방법 — 전자기 시뮬레이션(gprMax)으로 지하를 만들고 레이더그램을 뽑았다. 뭘 어디에 묻었는지 내가 아니까 라벨이 공짜로 나온다.
여기서 중요한 결정이 둘 있었다.
- 배경은 진짜를 쓴다. 순수 합성은 노이즈 질감이 실물과 달라 학습이 헛돈다. 실제
.t3r배경 위에 합성 타겟을 얹었다. - 규격을 지어내지 않고 실측했다.
dt 0.156ns · 채널간격 0.075m · 트레이스간격 0.053718m · 24채널 · 256샘플 · 심도 2.0m— 전부 실제 장비 파일에서 뽑은 값이다.
생성한 서베이 5,000건 → 종단 타일 55,883장
클래스 cavity · pipe · manhole · box · patch
정상땅 타일 22,353장 (39%) — 아무것도 없는 땅도 배워야 한다
면 종단 · 횡단 · 평단 각각 독립 데이터셋
숫자를 셀 때 타일이 아니라 서베이를 센다. 한 서베이를 잘게 자르면 타일은 얼마든지 늘어나지만, 그것들은 서로 독립이 아니다. 학습·검증을 나눌 때도 타일이 아니라 서베이 단위로 갈라야 같은 도로가 양쪽에 섞이지 않는다.
학습과 현장이 같은 그림을 보게 강제했다
막힌 데 — 영상 기반 검출이 조용히 무너지는 자리는 대부분 여기다. 학습할 때 본 그림과 현장에서 판독하는 그림이 다르다. 게인을 조금 올리거나 타일 폭을 바꾸면 모델이 보는 세계가 달라지는데, 이건 정확도 지표에 안 잡힌다. 학습은 잘 됐는데 현장에서만 못 맞춘다.
뚫은 방법 — 규율이 아니라 구조로 막았다. 렌더 파이프라인(보정 → 타일링 → 정규화)을 학습용 export 와 추론이 같은 함수를 호출하게 묶었다. 어드민에서 게인·배경제거를 바꾸면 내보내는 데이터셋과 검출이 동시에 바뀐다. 둘을 따로 조절할 방법이 아예 없다.
문서에 “같은 설정을 쓰세요”라고 적는 것과, 다르게 쓸 수 없게 만드는 것은 다르다.
합성과 실물의 거리를 숫자로 좁혔다
막힌 데 — 합성 데이터가 실물과 얼마나 닮았는지를 눈으로 판단하고 있었다. “비슷해 보인다”로는 렌더 손잡이(dewow·배경제거·게인·TV게인·제로타임·타일폭)를 어디로 돌려야 할지 정할 수 없다.
뚫은 방법 — 자연 이미지로 학습된 DINOv2 임베딩에 두 집합을 넣고, 평균 임베딩 사이의 유클리드 거리를 차원 수의 제곱근으로 나눈 값을 격차로 정의했다. 비교 대상에 따라 스케일이 변하지 않아 어떤 두 쌍이든 같은 자로 잴 수 있다.
탐색은 격자 96조합(dewow·배경제거 각 2 × 표시범위 6 × 게인 4)으로 거칠게 훑은 뒤, 최선 주변만 표시범위 ×÷√2 · 게인 ±1 로 두 라운드 좁힌다. 유전 알고리즘을 쓰지 않은 이유는 결정적이어야 하기 때문이다. 같은 입력이면 같은 답이 나와야 어제 결과와 오늘 결과를 비교할 수 있다.
부수적으로 얻은 것도 있다. 우리 검출기(RF-DETR)의 백본 특징으로도 같은 측정을 해봤는데 특징이 붕괴해서 격차가 뭉개지고 표본 노이즈에 순위가 뒤집혔다. 그래서 신뢰하지 않기로 하고 문서에 남겼다. 쓸 수 없다는 것도 결과다.
데이터가 맞는지 기계로 보증한다
막힌 데 — 타일 수만 장을 내보내놓고 그게 라벨과 정말 맞는지 어떻게 아나. 만든 코드로 검사하면 자기 채점이다. 같은 오해를 두 번 하면 통과한다.
뚫은 방법 — 검사기를 학습 코드를 재사용하지 않고 처음부터 다시 구현했다. 타일 계산과 좌표 역산을 독립적으로 짜서, 원래 코드가 틀렸으면 두 숫자가 어긋나 드러나게 했다. 통과하면 종료 코드 0, 하나라도 실패하면 1 — 자동화에 물릴 수 있다.
한계도 같이 적어뒀다. 양쪽이 타일링 규약 자체를 똑같이 오해하는 경우는 못 잡는다. 그건 사람이 미리보기를 눈으로 봐야 한다.
이 검사에서 실제로 잡아낸 것이 하나 있다. 타일이 겹치게 잘리면서 물체의 꼬리만 남은 조각이 생기는데, 그게 “이건 배경이다”로 학습되면 미탐을 주입한다. 판정 기준을 물체의 몇 퍼센트가 남았는지(비율)가 아니라 몇 트레이스나 보이는지(절대 폭) 로 바꿨다. 비율로 재면 작은 공동과 큰 맨홀에 같은 픽셀량이 정반대로 판정된다. 쌍곡선이 쌍곡선으로 보이려면 필요한 최소 폭이 있고, 그건 물체 크기와 무관하다.
아직 못 한 것
- cavity·pipe 는 견고하다. 규격·필터·배경 질감이 실물과 같다.
- manhole·box·patch 는 미검증 추정이다. 실제 예시로 검증하기 전에는 쓰지 않는 게 안전하다.
- 평단·횡단 타겟은 근사다. 종단이 정밀하다.
- 면별 독립 모델은 있지만 본격적인 3D 융합은 아직 계획 단계다. 지금은 각 면의 검출을 따로 낸다. 종·횡·평 결과를 IoU로 맞물려 하나의 3차원 물체로 묶는 것은 로드맵에 있다.
- 완전한 3D(3D FDTD 합성 + 3D CNN)는 보류했다. 비용 대비 이득이 아직 없다고 판단했다. 정확도는 데이터·융합·검수 쪽에서 더 싸게 나온다.
결과
채울 것
evaluation.py가 held-out 검증 분할에서 precision·recall·F1 을 낸다.
그 숫자를 여기 적는다. 없는 숫자를 적지 않는다.
숫자가 생기면 부품을 하나씩 켜며 비교하는 그림을 여기 붙인다.
만든 것
- 뷰어 — 종단·횡단·평단 실시간 렌더, 트레이스 A-scan 과 스펙트럼, 화면 보정(dewow·배경제거·게인·제로타임)
- 라벨 도구 — 정규 좌표계 기반 주석 저장
- 검출기 — ONNX 로 서비스. 검출기는 한 인터페이스 뒤에 등록되어 YOLO 와 RF-DETR 을 바꿔 끼울 수 있고, 로컬 렌더 서버와 원격 GPU 워커 중 살아 있는 쪽으로 넘어간다. 학습은 외부 GPU, 서비스 이미지에는 torch 가 없다 — 그래서 이미지가 가볍고 배포가 싸다
- 합성기 — 대시보드에서 개수·길이·음성비·클래스·면을 고르면 데이터셋이 나온다
- 도메인갭 검사기 — 렌더 설정을 자동 탐색해 합성과 실물의 거리를 좁힌다. 실시간 점수판과 HTML 대시보드
- 데이터셋 검사기 — 내보낸 학습셋이 라벨과 맞는지 독립 구현으로 확인한다
- 판독 루프 — 모델이 그린 예측 박스를 판독자가 승인하면 그대로 라벨이 된다. 되돌리기 포함
프런트엔드는 바닐라 JS 다. 빌드 단계가 없다. 패널은 자료가 지원하는 것만 골라 동적으로 만든다.