Files
detectelectronpole/docs/plan.md
minsung 417f880a87 Setup RailPose3D harness (Planner/Generator/Evaluator)
Name the project RailPose3D and stand up a multi-agent harness
following the Anthropic harness-design blog principles
(decomposition, separation of concerns, file-based handoff,
sprint contracts, context-reset over compaction).

- CLAUDE.md / PLAN.md / PROGRESS.md as the file-based handoff
  surface; every agent must read PLAN+PROGRESS before acting.
- 7 sub-agents under .claude/agents/: plan-architect (Planner),
  pole-detector-builder, rail-detector-builder, triangulation-
  builder, data-pipeline-builder (Generators), module-evaluator
  (Evaluator), dataset-explorer (read-only helper).
- 6 skills under .claude/skills/: /start /sprint /eval /progress
  /handoff /contract.
- SessionStart and Stop hooks to inject the PLAN/PROGRESS
  briefing and remind about PROGRESS.md updates.
- docs/plan.md captures the user-approved detailed plan;
  docs/research.md is the prior tech survey.
- .gitignore excludes data/, .usage/, model checkpoints, and
  local Claude overrides.

Tracking: closes #1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:32:05 +09:00

15 KiB
Raw Blame History

드론 영상 기반 전철주·레일 검출 → 3D 좌표 추출 파이프라인 계획

Context (배경 및 문제 정의)

사용자 목표: 드론 정사영상(orthomosaic)과 드론 동영상/사진으로부터 전철주(catenary pole)레일(rail) 같은 가늘고 긴 구조물을 검출하고, 그 3D 좌표를 추출하는 것.

이미 갖추어진 것:

  • SfM/Photogrammetry 파이프라인 (COLMAP/Metashape)으로 카메라 포즈와 sparse/dense cloud 생성 가능
  • 라벨링 30장 (검증/시드용으로 활용)

현재 막혀있는 핵심 문제 4가지:

  1. 전철주 하단(base, 지면 접점) 좌표를 정확히 잡지 못함 — 3D 좌표화의 기준점인데, 카메라가 oblique(비스듬히)일 때 완금(crossarm, T자 가로팔)이 영상상 가장 아래쪽에 보이는 화소가 되어, bbox 기반 detector가 완금을 base로 오인.
  2. 라벨링 비용 — 30장만 보유. 일반적인 supervised 학습으론 불충분.
  3. SfM 메시 품질 — 가는 수직물·금속 레일은 텍스처가 부족해 dense reconstruction이 너덜너덜하게 깨짐. 메시에서 직접 객체 추출 불가.
  4. 레일은 bbox로 표현 불가 — 화면을 가로지르는 긴 곡선이므로 segmentation/polyline이 자연스러운 표현.

핵심 전략

"메시에서 객체를 추출하지 말고, 2D 영상에서 정확히 검출한 뒤 SfM 카메라 포즈로 삼각측량하여 3D 좌표를 만든다." SfM의 강점(카메라 포즈)만 쓰고 약점(가는 물체 dense recon)은 우회한다.

세 모듈로 나누어 구축:

  • (A) 전철주 base 검출 — 4-keypoint pose estimation으로 구조적으로 base/완금 구분
  • (B) 레일 polyline 검출 — Segmentation + skeletonization (사전학습된 레일 모델에서 transfer)
  • (C) 2D → 3D 통합 — 다중뷰 삼각측량 + DBSCAN 군집 + bundle adjustment

전체 파이프라인은 이미 운영 중인 SfM 출력에 detection 모듈만 끼워넣는 형태라 최소 침습.

모듈 A: 전철주 base 검출

핵심 통찰

완금이 base로 오인되는 문제는 통계적 문제가 아니라 기하학적 문제다. Bounding box의 "가장 아래쪽 픽셀"은 oblique 시점에서 완금일 수 있으므로, 모델이 학습량과 무관하게 못 푼다. → Pose/keypoint 모델로 "이 점은 base, 저 점은 완금 끝"을 의미적으로 구분해야 풀린다.

채택 모델: RTMPose-m (or YOLO11-pose) + 4-keypoint 스켈레톤

  • 스켈레톤: {base, top, left_crossarm_tip, right_crossarm_tip} (각 전철주당 4점)
  • 이유: 사람 포즈 추정에서 "왼발/오른발"을 헷갈리지 않게 만든 것과 정확히 같은 트릭. base 채널은 오직 지면 접점에서만 supervise 되므로 완금과 구조적으로 구분됨.
  • Repo: https://github.com/open-mmlab/mmpose (RTMPose), https://github.com/ultralytics/ultralytics (YOLO11-pose)
  • fallback: ViTPose-Base (frozen DINOv2 backbone) — 30장 같은 극소 데이터에서 더 강함

라벨 부족(30장) 대응 — 4단계 부트스트랩

  1. Day 13 (라벨 0개로 시작): Grounding-DINO 1.5 ("utility pole", "전봇대") → SAM 2.1 box prompt → 마스크의 PCA 주축의 아래쪽 끝 → base 후보. 30장에 대해 픽셀 오차 측정 = baseline.
  2. Day 45: MVP 출력 중 깨끗한 것을 RTMPose 학습용 부트스트랩 라벨로 사용.
  3. Week 2+: Albumentations + copy-paste augmentation (SAM2 마스크로 전철주를 잘라 다양한 배경에 붙이기, keypoint도 함께 변환). 510배 데이터 증강.
  4. Week 3+ (가장 큰 레버리지): SfM-consistent self-training 루프
    • 한 뷰의 base detection을 SfM 포즈로 다른 뷰에 reprojection → pseudo-label
    • reprojection error > 5px이면 outlier로 제거 (완금을 base로 잘못 잡으면 자동으로 걸러짐 — 다른 뷰의 정상 detection들과 3D상으로 일관되지 않으므로)
    • 참고: EpipolarPose (CVPR'19), Pixel-Perfect SfM (ICCV'21) https://github.com/cvg/pixel-perfect-sfm

하지 말아야 할 것

  • bbox 기반 YOLO/Faster-RCNN으로 base 추정 (위 기하 문제로 실패)
  • BlenderProc 합성 데이터에만 의존 (sim-to-real gap)
  • 30장으로 처음부터 큰 모델 full retrain

모듈 B: 레일 polyline 검출

채택 모델: SegFormer-B2 + 3단계 transfer learning + skeletonize

Polyline 추출 후처리

  1. 이진 마스크 (rail / not-rail)
  2. skimage.morphology.skeletonize → 1px 중심선
  3. Connected component split → 각 레일 분리 (instance ID)
  4. Ramer-Douglas-Peucker 단순화 (eps ~12px) → 정렬된 polyline
  5. 선택적 sub-pixel refinement: DeepLSD attraction field로 skeleton을 sub-pixel로 snap (https://github.com/cvg/DeepLSD)

MVP (week 1)

Grounded-SAM 2 (텍스트 프롬프트 "railway track", "steel rail") → 30장 수작업 보정 → SegFormer fine-tune → 나머지 자동 라벨 → 반복(active learning loop).

하지 말아야 할 것

  • LaneATT/CLRNet 등 레인 검출 모델 직접 적용 (전방 차량 시점 가정 — drone nadir에 부적합)
  • Sat2Graph/RoadTracer 그래프 모델 (이 규모/감독 형태 아님)
  • HAWP/LETR/M-LSD wireframe 검출기 (실내 Manhattan-world용)

모듈 C: 2D → 3D 통합

C-1. 전철주 (point 객체)

단계 도구 비고
다중뷰 cross-view association DBSCAN in world XY 핵심 트릭
Triangulation 초기화 pycolmap.triangulate_point https://github.com/colmap/pycolmap
Outlier 제거 RANSAC + Huber loss
Pose-fixed bundle adjustment pyceres https://github.com/cvg/pyceres

Cross-view association 핵심 (어려운 부분): 전철주는 약 50m 간격으로 모양이 똑같아서 DINOv2 같은 appearance descriptor로는 구분 불가. → 기하 기반 군집화:

  1. 각 뷰의 2D base detection을 ray로 back-project
  2. SfM dense cloud의 로컬 ground plane과 ray 교차 → 월드좌표 후보점
  3. DBSCAN(eps≈0.5m, min_samples=3) 으로 월드 XY 클러스터링 → 한 클러스터 = 한 물리적 전철주
  4. 클러스터 내 detection들로 pycolmap triangulate

C-2. 레일 (curve 객체)

단계 도구
지면 추출 PDAL CSF (Cloth Simulation Filter) https://pdal.io/en/latest/stages/filters.csf.html
Ray-ground 교차 Open3D RaycastingScene
곡선 fitting scipy.interpolate.splprep (3D B-spline)
NURBS 정밀화 geomdl https://github.com/orbingol/NURBS-Python
Bundle adjustment pyceres (control points 변수화)

파이프라인:

  1. SfM dense cloud → CSF로 ground 분류 → 로컬 표면 메시
  2. 각 뷰의 2D rail polyline을 35px 간격으로 샘플링
  3. 카메라 ray가 ground 표면과 교차하는 월드좌표 모음
  4. 모든 뷰의 월드좌표를 모아 호장(arc length)으로 정렬 → 3D B-spline fit
  5. (선택) pyceres로 control point를 변수로 두고 모든 뷰의 2D polyline에 대한 reprojection 오차 최소화

C-3. SfM dense step 교체 여부 판단

결론: 교체하지 않는다. Gaussian Splatting / NeRF는 photometric loss 기반이라 가는 물체 문제를 근본적으로 풀지 못함. 측정 정확도는 detection→triangulate가 더 강함. 단, 시각화 산출물용으로만 2DGS(https://github.com/hbb1/2d-gaussian-splatting) 옵션으로 추가 가능.

SfM에서 활용: 카메라 포즈 + intrinsics, sparse cloud (sanity check), ground 분류된 dense cloud만 사용. 메시는 버린다.

전체 파이프라인 다이어그램

드론 영상 (oblique + nadir)
        │
        ▼
[Stage 0] COLMAP / Metashape (기존)
   → 카메라 [R|t], K, dense cloud
        │
   ┌────┼────────────────────────┐
   ▼    ▼                        ▼
[Stage 1a]   [Stage 1b]              [Stage 1c]
PDAL CSF    Pole 4-kpt detection    Rail seg + polyline
ground       (RTMPose)               (SegFormer + skel)
        │              │                  │
        └──────┬───────┘                  │
               ▼                          │
[Stage 2] Pole association                │
  - ray → ground 교차                     │
  - DBSCAN in world XY                    │
        │                                 │
        ▼                                 │
[Stage 3] Pole triangulation              │
  - pycolmap + Huber + pyceres BA         │
  → 3D pole base point                    │
                                          ▼
                                 [Stage 4] Rail reconstruction
                                  - Open3D raycasting (per sample)
                                  - scipy splprep B-spline
                                  - pyceres spline BA
                                  → 3D rail centerline
        │                                 │
        └──────────────┬──────────────────┘
                       ▼
[Stage 5] Output: GeoJSON/Shapefile (EPSG:5186) + DXF (ezdxf)

권장 디렉터리 구조 (신규 생성할 파일들)

이미 존재하는 파일은 docs/research.mdREADME.md 뿐. 새로 만들 핵심 파일:

detectelectronpole/
├── docs/
│   ├── research.md                  # 기존
│   └── plan.md                      # 이 계획의 한국어 사본
├── data/
│   ├── images/                      # 드론 원본
│   ├── sfm/                         # COLMAP DB / Metashape export
│   └── labels/
│       └── poles_4kpt.json          # COCO-keypoints 포맷, 4-keypoint 스키마
├── configs/
│   ├── rtmpose_pole_4kpt.py         # MMPose 설정
│   └── segformer_rail.yaml          # SegFormer fine-tune 설정
├── src/
│   ├── detection/
│   │   ├── mvp_groundingdino_sam2.py    # zero-label MVP (week 1)
│   │   ├── pole_keypoint.py             # RTMPose 추론 wrapper
│   │   ├── rail_segment.py              # SegFormer 추론 + skeletonize
│   │   └── augment_copy_paste.py        # 키포인트 보존 copy-paste
│   ├── triangulation/
│   │   ├── sfm_io.py                # pycolmap / Metashape XML 로더
│   │   ├── ground.py                # PDAL CSF + Open3D RaycastingScene
│   │   ├── poles.py                 # DBSCAN association + pycolmap + pyceres
│   │   └── rails.py                 # raycasting + splprep + pyceres
│   ├── self_training/
│   │   └── sfm_self_training.py     # multi-view pseudo-label loop
│   └── export/
│       └── geojson_dxf.py           # 산출물 export
└── pipeline/
    └── run_full.py                  # end-to-end orchestrator

단계별 마일스톤

Phase 1 (Week 1) — Zero-label MVP

  • mvp_groundingdino_sam2.py: Grounding-DINO 1.5 + SAM 2.1로 30장 이미지에서 pole mask + base 후보 추출
  • PCA 주축 lower endpoint를 30장의 GT와 비교해 픽셀 오차 baseline 측정
  • Grounded-SAM 2로 같은 방법으로 rail 마스크 baseline 측정
  • 목적: 라벨 0개로 어디까지 가는지 정량화. 실패 모드 카탈로그 작성.

Phase 2 (Week 23) — Detection 학습

  • 4-keypoint COCO-keypoints 포맷으로 30장 재라벨링 (point 4개 클릭이라 비용 낮음)
  • copy-paste augmentation으로 ~300장으로 증강
  • RTMPose-m fine-tune (frozen backbone)
  • SegFormer-B2를 RailSem19 → UAV-RSOD → 30장 3-stage transfer
  • 두 모델 평가 (PCK, mIoU)

Phase 3 (Week 4) — 2D → 3D 통합

  • sfm_io.py로 Metashape/COLMAP 출력 로드
  • ground.py로 PDAL CSF 지면 추출 + Open3D RaycastingScene 구축
  • poles.py: DBSCAN association + pycolmap triangulate + pyceres BA → 3D pole points
  • rails.py: ray-ground 교차 + B-spline fit → 3D rail polylines
  • EPSG:5186 (한국 중부원점) GeoJSON export

Phase 4 (Week 5+) — Self-training 루프

  • sfm_self_training.py: 한 라운드 = 검출 → 삼각측량 → reprojection → pseudo-label → fine-tune
  • reprojection error 임계값 5px로 outlier 자동 제거
  • 라운드별 mAP/PCK 추적
  • 수렴까지 반복 (보통 35 라운드)

검증 방법 (Verification)

  1. 모듈 A 단독: 30장 GT 대비 base keypoint PCK@5px 측정. MVP baseline → RTMPose baseline → self-training 라운드별 비교.
  2. 모듈 B 단독: 30장 GT 대비 rail mask mIoU 및 polyline Hausdorff distance 측정.
  3. 모듈 C 단독: Phase 1의 raw 검출 결과를 triangulation/poles.py에 넣어 3D pole 좌표를 GeoJSON으로 export. 측량 기준점(GCP) 또는 KORAIL 인벤토리와 비교 (없으면 두 측량 비행을 따로 돌려 일관성 검증).
  4. End-to-end: 작은 trial section (예: 100m 구간, ~3 전철주 + 2 레일)에서 전체 파이프라인 1회 실행. 산출물 시각화: QGIS에서 GeoJSON + 정사영상 오버레이.
  5. 회귀 테스트: pipeline/run_full.py --section trial_100m 단일 명령으로 재현 가능해야 함.

위험 요소 및 대응

위험 대응
전철주 base가 ballast/식생에 가려짐 self-training 루프가 다른 뷰의 가시 base와 일관성 검증 → 평균 offset 보정
SfM ground cloud 밀도 부족 (<50 pts/m²) 비행 고도/오버랩 재조정, 또는 SuGaR/2DGS로 보강 (시각화 한정)
한국 catenary 마스트 종류 다양 (단주 vs 문형) 4-keypoint 스킴을 pole_type 클래스 분기 (단일 마스트 / 문형 가구)로 확장
좌표계 (local vs EPSG:5186) GCP 3점 이상으로 7-parameter Helmert 변환
라벨 시드(30장)가 한 카메라 각도/조명 편향 첫 self-training 라운드에서 다양성 강제 (multi-view 클러스터당 최소 1장 샘플링)

핵심 참조 (전부 April 2026 기준 상용 가능)

  • Pole detection: MMPose RTMPose, SAM 2.1, Grounding-DINO 1.5/1.6, Pixel-Perfect SfM
  • Rail detection: SegFormer, RailSem19, UAV-RSOD, NL-LinkNet-SSR, DeepLSD
  • 3D 통합: pycolmap, pyceres, PDAL, Open3D, scipy.interpolate, geomdl, ezdxf