Adds STATUS.md as the handoff document: benchmark numbers, the bare-earth
metrics that actually matter for this project, the Korean-data domain gap that
retraining will not fix, and what to do on the 24 GB machine.
The memory problem was in how a prediction's colours were turned back into
class indices. Every consumer built an (N, 13, 3) float64 temporary:
d = ((rgb[:, None, :] - COLOR_MAP[None, :, :]) ** 2).sum(axis=2)
That is ~250 MB of intermediates per 800k-point tile, several live at once, and
a full 4.7M-point block pushes it into gigabytes. main.py writes exact palette
entries, so an exact hash lookup resolves nearly every point with no large
temporary; only leftovers fall back to a chunked distance search. Peak RSS on a
470k-point tile drops to 61 MB. Extracted to sumparts_palette.py and shared by
coarse_eval.py and split_by_class.py.
Also from this round:
- patch_cm_mutation.sh: ConfusionMatrix.update() rewrote the caller's pred
tensor in place, folding every ignore_index point into class num_classes-1.
test() saves its visualization from that same tensor afterwards, so an
unlabelled tile came out 100% wall and the model looked degenerate when it
was not.
- patch_class_mask.sh: SUMPARTS_MASK_CLASSES drops known-absent classes from
the argmax. Measured on Seosan and it does not help - the runner-up for
"water" is "wall", not "terrain" - but the experiment is worth keeping.
- split_by_class.py now writes .ply alongside .obj. A vertex-only OBJ has zero
faces and most viewers render nothing, which is why the first export looked
broken.
- verify_outputs.sh reads exported files back with a parser, so "here are your
files" can be checked rather than asserted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8.8 KiB
현재 상태 — 2026-08-21
3060 머신에서 여기까지 왔다. 이 문서는 3090으로 옮겨서 이어가기 위한 인수인계다.
목표: 드론 사진측량 메시에서 구조물을 제거한 지형(bare earth) 추출. 부수적으로 건물·수목·차량 분리.
한 줄 요약
SUM Parts 벤치마크는 재현했다. 지면 precision 95.24%로 목표 용도에 충분하다. 문제는 벤치마크가 아니라 한국 데이터에서의 도메인 갭이다.
1. 완료된 것
| 항목 | 결과 |
|---|---|
| WSL2 환경 (CUDA 11.8 + torch 2.0.1 + 확장 5종) | ✅ |
| SUM Parts 데이터 (train 24 / val 8 / test 8) | ✅ |
| PointNet 100 epoch | mIoU 17.19 (논문 15.1 — 재현 확인) |
| PointVector 100 epoch | mIoU 51.83 / OA 80.41 |
| 서산 OBJ → PLY 변환 | ✅ 타일 1장 |
| 서산 추론 | ✅ 13클래스 예측 |
| 클래스별 분리 출력 | ✅ D:\AI_Test\sum-part\ |
2. SUM 벤치마크 성적 (PointVector, voxel_max 24000)
4클래스 통합
| 클래스 | IoU | precision | recall |
|---|---|---|---|
| ground | 60.69% | 95.24% | 62.59% |
| vegetation | 90.70% | 95.11% | 95.14% |
| building | 86.10% | 88.01% | 97.54% |
| vehicle | 51.05% | 78.81% | 59.17% |
| mIoU 72.14% / OA 89.72% |
bare earth (지면 vs 나머지) ← 실제 목표 지표
지면 정확히 유지 523,983
비지면 섞여 들어옴 26,217 ← 지형을 오염시킴
지면 놓침 268,755 ← 구멍, 보간 가능
precision 95.24%
recall 66.10%
IoU 63.98%
F1 78.04%
오염원 분해
| 출처 | 비중 |
|---|---|
| facade_surface | 49.90% ← 절반이 건물 외벽 |
| high_vegetation | 17.42% |
| water | 12.04% |
| car | 7.83% |
| wall (옹벽) | 7.54% |
| roof_surface | 5.05% |
건물 외벽 하단 — 지면과 만나는 경계에서 새는 것으로 보인다. 후처리로 상당 부분 잡을 수 있다: 지면 예측 중 수직으로 튀는 포인트 제거.
precision 95.24%는 목표 용도(구조물 제거 지형도)에 이미 쓸 만하다. recall 66.10%로 지면 34%를 놓치지만, 구멍은 보간으로 메운다.
3. ★ 미해결 — 한국 데이터 도메인 갭
서산 타일 1장 추론 결과:
| 클래스 | 비율 | 판정 |
|---|---|---|
| terrain | 24.29% | 타당 |
| unclassified | 21.70% | ❌ 모델이 판단 못 함 |
| water | 21.11% | ❌ 물 없다고 확인됨 |
| high_vegetation | 7.85% | 타당 |
| wall | 6.48% | ? |
| roof_surface | 6.11% | ? |
| boat | 5.07% | ❌ 배 있을 리 없다 |
| facade_surface | 0.10% | ❌ 지붕 6%인데 벽 0.1%는 모순 |
신뢰 불가 47.9%.
원인
닫힌 집합 분류기다. 13개 중 반드시 하나를 고르고 "모르겠음"이 없다. 학습 어휘에 없는 것을 만나면 특징 공간에서 가장 가까운 것으로 간다.
헬싱키에서 배운 water = 어둡고 평평하고 균질하고 수평. 서산 포장면이 그 설명에 맞는다.
boat는 물 위에 있는 것이라 따라온다. 일관성 있는 착각이다.
비율 매칭은 아니다 — SUM val의 water는 11.17%인데 서산 예측은 21.11%로 2배다.
시도했고 실패한 것: 클래스 마스킹
water·boat 로짓을 -inf로 죽여 2순위로 넘기는 실험.
wall +63,783 (6.48% → 20.05%) ← 절반 이상이 여기로
unclassified +33,867
terrain +13,656 ← 11%만 회복
실패. 2순위가 terrain이 아니라 wall이었다.
모델이 그 영역을 수직 구조물로 읽고 있다는 뜻이고, 도메인 갭이 얕지 않다는 증거다.
마스킹은 되돌렸다 (SUMPARTS_MASK_CLASSES 환경변수로 언제든 다시 켤 수 있다).
4. 발견한 업스트림 버그 4건 (전부 패치됨, 멱등, 원본 보존)
| # | 증상 | 원인 | 패치 |
|---|---|---|---|
| A | bincount ... non-negative |
블라인드 test셋 라벨이 전부 -1 |
patch_unlabeled_test.sh |
| B | UnboundLocalError: 'epoch' |
mode=val이 학습 루프 변수 참조 |
patch_val_mode.sh |
| C | np.long / collections.Iterable |
numpy 1.24 / python 3.10에서 제거·이동 | patch_numpy_aliases.sh |
| D | 예측이 전부 한 클래스로 나옴 | ConfusionMatrix.update()가 입력을 제자리 변조 |
patch_cm_mutation.sh |
D가 가장 위험했다. ignore_index에 해당하는 포인트의 예측을
num_classes-1(=wall)로 덮어쓰는데, main.py가 그 다음에 시각화를 저장한다.
라벨이 없는 우리 타일은 전 포인트가 ignore_index라 100% wall로 저장됐다.
모델이 퇴화한 것처럼 보였지만 멀쩡했다.
5. 메모리 문제 (3090에서 겪은 것)
팔레트 색상 → 클래스 복원에서 (N, 13, 3) 배열을 통째로 만들고 있었다.
d = ((rgb[:, None, :] - COLOR_MAP[None, :, :]) ** 2).sum(axis=2)
80만 포인트면 임시 배열 하나가 250 MB이고 여러 개가 동시에 존재한다. 전체 블록(470만 포인트)이면 GB 단위로 뛴다.
수정 완료 — sumparts_palette.py로 분리하고 해시 조회 + 청크 폴백으로 바꿨다.
개선 후 peak RSS: 61 MB (470k 포인트 처리)
coarse_eval.py, split_by_class.py가 이 모듈을 쓴다.
6. 실측 성능표 (RTX 3060 12GB)
| 모델 | voxel_max | peak VRAM | s/iter | 100 epoch | 논문 mIoU |
|---|---|---|---|---|---|
| pointnet | 64000 | 6.01 G | 0.291 | 2.9h | 15.1% |
| pointnet++msg | 64000 | 4.16 G | 0.675 | 6.8h | 33.1% |
| pointvector-xl | 24000 | 6.46 G | 0.402 | 4.1h | 70.0% |
| pointvector-xl | 64000 | 16.49 G | 13.113 | ❌ 12GB 불가 | |
| pointnext-xl | 32000 | 8.03 G | 0.635 | 6.4h | 65.3% |
| pointnext-xl | 64000 | 15.47 G | 46.980 | ❌ 12GB 불가 |
WSL2에서 VRAM 초과는 OOM을 내지 않는다. 호스트 RAM으로 흘려서 조용히 완주한다 — 25~100배 느리게. peak VRAM과 **전력(W)**으로 판정할 것. 사용률 100%인데 전력이 낮으면 연산이 아니라 PCIe 전송 대기다.
7. 3090에서 할 일
우선순위 1 — 논문 설정 재학습
voxel_max 64000은 16.5 GB가 필요해 12 GB 카드에서 불가능했다. 24 GB면 된다.
기대 효과:
| 현재 (24000) | 재학습 (64000) 기대 | |
|---|---|---|
| SUM terrain IoU | 60.69% | ~85% |
| 지면 precision | 95.24% | ~96% |
| 지면 recall | 66.10% | ~85% |
| 서산 물 오분류 21% | — | 그대로 |
주의: voxel_max는 중립적 손잡이가 아니다. 64000으로 학습하면
추론도 64000 청크로 해야 성능이 나온다 → 서산 추론도 3090에서 돌려야 한다.
우선순위 2 — 서산 예측 육안 확인
D:\AI_Test\sum-part\unseen\*.ply에 42.82%가 들어 있다.
이게 뭔지 모르면 나머지가 전부 추측이다.
우선순위 3 — 도메인 갭 대응
재학습으로 안 고쳐진다. 선택지:
- SAM 3.1 자동 라벨링 + 파인튜닝 — 드론 원본 8,120장 + 카메라 포즈(PPK) 보유. SAM 3의 텍스트 프롬프트로 명명된 마스크 → 포즈로 3D 투영 → 면 투표. 사람 몫은 전수 라벨링이 아니라 1~2타일 검증이다.
- 후처리 — 지면 예측 중 수직 이상치 제거로 건물 외벽 오염(49.9%) 감소.
8. 산출물 위치
D:\AI_Test\sum-part\
├── ground\ 114,159 pts (24.29%)
├── building\ 93,076 pts (19.80%)
├── tree\ 36,876 pts ( 7.85%)
├── vehicle\ 24,631 pts ( 5.24%)
└── unseen\ 201,258 pts (42.82%) ← unclassified + water
각 폴더에 .ply와 .obj가 있다. .ply를 열어라 —
OBJ는 면이 없어서 대부분의 뷰어가 아무것도 안 보여준다.
색상은 클래스 색이 아니라 드론 사진 텍스처라, 그 클래스로 분류된 게 실제로 뭔지 보인다.
D:\MYCLAUDE_PROJECT\sum-parts-test\output\
├── seosan_pred_viewer.ply 클래스 색
└── seosan_rgb_viewer.ply 사진 색
9. 제약
- test 세트는 블라인드다. 라벨이 전부
-1이라 로컬 채점 불가. 논문 수치와 직접 대조하려면 예측을 저자(gaoweixiaocuhk@gmail.com)에게 보내야 한다. - 어노테이션 도구는 비공개다. 저자가 유료 서비스로 판매 중.
- 학습 가중치도 비공개다. 직접 학습이 유일한 경로.
- 라이선스: 데이터 CC BY-NC 4.0, 코드 GPL-3.0. 상업 이용은 저자 허락 필요.
10. 문서
| 문서 | 용도 |
|---|---|
| SETUP.md | 1단계 — 환경 구축 (GPU 불필요, ~80분) |
| TRAIN.md | 2단계 — 학습 (GPU 필요) |
| docs/pipeline.html | 전체 6단계 공정 정의 |
| docs/SUM-Parts-검토노트.md | 트러블슈팅 상세, 데이터 스키마 실측 |