Reproduces the SUM Parts (CVPR 2025) face-labeling benchmark on a single consumer GPU, then applies it to drone-photogrammetry road survey meshes. Verified on RTX 3060 12GB / WSL2 Ubuntu 22.04 / CUDA 11.8 / torch 2.0.1: - CUDA extensions build (pointnet2_batch, pointops, chamfer_dist, emd, subsampling) - PointNet 100 epochs reaches mIoU 17.19, matching the paper's reported 15.1 - OBJ -> PLY conversion round-trips through the model and yields per-point predictions Four upstream source patches, all idempotent, originals preserved: - numpy aliases removed in 1.24 (np.long etc.) and collections ABCs moved in python 3.10 - the blind test split ships label = -1, which crashed ConfusionMatrix - mode=val referenced `epoch` before assignment Documents the traps that cost the most time, including VRAM overflow silently falling back to host RAM on WSL2 (25-100x slowdown, no OOM) and the colour scale mismatch between r/g/b float32 and red/green/blue uint8. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
647 lines
25 KiB
HTML
647 lines
25 KiB
HTML
<title>서산 명천 메시 분할 파이프라인</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans+KR:wght@300;400;500;600;700&display=swap">
|
||
|
||
<style>
|
||
/* Palette derives from the dataset's own semantic class colours
|
||
(terrain / high_vegetation / water), pulled down in saturation so they
|
||
read as document colour rather than legend swatches. Neutrals carry a
|
||
slight green bias to sit with them. */
|
||
:root {
|
||
--ground: #F6F7F4;
|
||
--surface: #FFFFFF;
|
||
--surface-sunk: #EDEFEA;
|
||
--line: #D8DCD3;
|
||
--line-soft: #E6E9E1;
|
||
--ink: #1A1F1B;
|
||
--ink-soft: #4B534C;
|
||
--ink-faint: #7C857D;
|
||
|
||
--veg: #2F6F4E; /* high_vegetation */
|
||
--terr: #8A5A2B; /* terrain */
|
||
--water: #2A6E7A; /* water */
|
||
--signal: #B4482D; /* traps, hard stops */
|
||
--veg-wash: #E7F0EA;
|
||
--signal-wash: #F8E9E4;
|
||
--water-wash: #E4EEF0;
|
||
|
||
--shadow: 0 1px 2px rgba(26,31,27,.05), 0 8px 24px -12px rgba(26,31,27,.18);
|
||
|
||
--step-0: .75rem;
|
||
--step-1: .8125rem;
|
||
--step-2: .9375rem;
|
||
--step-3: 1.125rem;
|
||
--step-4: 1.5rem;
|
||
--step-5: 2.25rem;
|
||
--step-6: 3rem;
|
||
}
|
||
|
||
@media (prefers-color-scheme: dark) {
|
||
:root:not([data-theme="light"]) {
|
||
--ground: #12150F;
|
||
--surface: #191D16;
|
||
--surface-sunk: #10130E;
|
||
--line: #2C3229;
|
||
--line-soft: #232821;
|
||
--ink: #E9ECE6;
|
||
--ink-soft: #AEB6AC;
|
||
--ink-faint: #7B8479;
|
||
|
||
--veg: #6FBF8E;
|
||
--terr: #C79A63;
|
||
--water: #6BB6C2;
|
||
--signal: #E08A6B;
|
||
--veg-wash: #1B2A20;
|
||
--signal-wash: #2E1D17;
|
||
--water-wash: #172629;
|
||
|
||
--shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px -12px rgba(0,0,0,.7);
|
||
}
|
||
}
|
||
|
||
:root[data-theme="dark"] {
|
||
--ground: #12150F;
|
||
--surface: #191D16;
|
||
--surface-sunk: #10130E;
|
||
--line: #2C3229;
|
||
--line-soft: #232821;
|
||
--ink: #E9ECE6;
|
||
--ink-soft: #AEB6AC;
|
||
--ink-faint: #7B8479;
|
||
|
||
--veg: #6FBF8E;
|
||
--terr: #C79A63;
|
||
--water: #6BB6C2;
|
||
--signal: #E08A6B;
|
||
--veg-wash: #1B2A20;
|
||
--signal-wash: #2E1D17;
|
||
--water-wash: #172629;
|
||
|
||
--shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px -12px rgba(0,0,0,.7);
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
|
||
body {
|
||
margin: 0;
|
||
background: var(--ground);
|
||
color: var(--ink);
|
||
font-family: "IBM Plex Sans KR", system-ui, -apple-system, sans-serif;
|
||
font-size: var(--step-2);
|
||
line-height: 1.65;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
.wrap {
|
||
max-width: 60rem;
|
||
margin: 0 auto;
|
||
padding: 3.5rem 1.5rem 6rem;
|
||
}
|
||
|
||
code, .mono { font-family: "IBM Plex Mono", ui-monospace, monospace; }
|
||
|
||
/* ---------- masthead ---------- */
|
||
|
||
.masthead { display: flex; flex-direction: column; gap: 1.25rem; margin-bottom: 3rem; }
|
||
|
||
.eyebrow {
|
||
font-family: "IBM Plex Mono", monospace;
|
||
font-size: var(--step-0);
|
||
letter-spacing: .14em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-faint);
|
||
}
|
||
|
||
h1 {
|
||
margin: 0;
|
||
font-size: clamp(2rem, 5.5vw, var(--step-6));
|
||
font-weight: 600;
|
||
letter-spacing: -.025em;
|
||
line-height: 1.1;
|
||
text-wrap: balance;
|
||
}
|
||
|
||
.standfirst {
|
||
margin: 0;
|
||
max-width: 46ch;
|
||
font-size: var(--step-3);
|
||
font-weight: 300;
|
||
color: var(--ink-soft);
|
||
text-wrap: pretty;
|
||
}
|
||
|
||
.facts {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
|
||
gap: 1px;
|
||
background: var(--line);
|
||
border: 1px solid var(--line);
|
||
border-radius: 3px;
|
||
overflow: hidden;
|
||
margin-top: .75rem;
|
||
}
|
||
.fact { background: var(--surface); padding: .875rem 1rem; }
|
||
.fact dt {
|
||
font-family: "IBM Plex Mono", monospace;
|
||
font-size: var(--step-0);
|
||
letter-spacing: .08em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-faint);
|
||
margin: 0 0 .25rem;
|
||
}
|
||
.fact dd {
|
||
margin: 0;
|
||
font-size: var(--step-2);
|
||
font-weight: 500;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/* ---------- section headings ---------- */
|
||
|
||
h2 {
|
||
margin: 3.5rem 0 1.25rem;
|
||
font-size: var(--step-4);
|
||
font-weight: 600;
|
||
letter-spacing: -.015em;
|
||
padding-bottom: .5rem;
|
||
border-bottom: 1px solid var(--line);
|
||
}
|
||
|
||
h3 { margin: 2rem 0 .625rem; font-size: var(--step-3); font-weight: 600; }
|
||
|
||
p { margin: 0 0 1rem; max-width: 68ch; }
|
||
|
||
/* ---------- the spine ---------- */
|
||
|
||
.spine { display: flex; flex-direction: column; gap: 1rem; }
|
||
|
||
.phase {
|
||
position: relative;
|
||
background: var(--surface);
|
||
border: 1px solid var(--line);
|
||
border-left: 3px solid var(--accent, var(--veg));
|
||
border-radius: 3px;
|
||
padding: 1.375rem 1.5rem 1.5rem;
|
||
box-shadow: var(--shadow);
|
||
}
|
||
.phase[data-tone="setup"] { --accent: var(--water); }
|
||
.phase[data-tone="train"] { --accent: var(--veg); }
|
||
.phase[data-tone="apply"] { --accent: var(--terr); }
|
||
|
||
.phase-head {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: .75rem;
|
||
flex-wrap: wrap;
|
||
margin-bottom: .25rem;
|
||
}
|
||
.idx {
|
||
font-family: "IBM Plex Mono", monospace;
|
||
font-size: var(--step-0);
|
||
font-weight: 600;
|
||
letter-spacing: .1em;
|
||
color: var(--accent, var(--veg));
|
||
}
|
||
.phase-title { font-size: var(--step-3); font-weight: 600; letter-spacing: -.01em; }
|
||
.cadence {
|
||
margin-left: auto;
|
||
font-family: "IBM Plex Mono", monospace;
|
||
font-size: var(--step-0);
|
||
color: var(--ink-faint);
|
||
white-space: nowrap;
|
||
}
|
||
.phase-why { margin: 0 0 1rem; color: var(--ink-soft); font-size: var(--step-1); max-width: 62ch; }
|
||
|
||
.flow {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
|
||
gap: .75rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
.cell {
|
||
background: var(--surface-sunk);
|
||
border-radius: 3px;
|
||
padding: .625rem .75rem;
|
||
}
|
||
.cell h4 {
|
||
margin: 0 0 .25rem;
|
||
font-family: "IBM Plex Mono", monospace;
|
||
font-size: var(--step-0);
|
||
font-weight: 500;
|
||
letter-spacing: .08em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-faint);
|
||
}
|
||
.cell p { margin: 0; font-size: var(--step-1); line-height: 1.5; }
|
||
.cell code { font-size: .8em; }
|
||
|
||
.gate {
|
||
display: flex;
|
||
gap: .625rem;
|
||
align-items: flex-start;
|
||
background: var(--veg-wash);
|
||
border-radius: 3px;
|
||
padding: .625rem .75rem;
|
||
font-size: var(--step-1);
|
||
}
|
||
.gate strong {
|
||
font-family: "IBM Plex Mono", monospace;
|
||
font-size: var(--step-0);
|
||
letter-spacing: .08em;
|
||
text-transform: uppercase;
|
||
color: var(--veg);
|
||
white-space: nowrap;
|
||
padding-top: .1rem;
|
||
}
|
||
.gate span { color: var(--ink); }
|
||
|
||
.cmds {
|
||
margin: 0 0 1rem;
|
||
padding: .75rem .875rem;
|
||
background: var(--surface-sunk);
|
||
border-radius: 3px;
|
||
overflow-x: auto;
|
||
font-family: "IBM Plex Mono", monospace;
|
||
font-size: var(--step-1);
|
||
line-height: 1.7;
|
||
white-space: pre;
|
||
}
|
||
.cmds .c { color: var(--ink-faint); }
|
||
|
||
/* ---------- traps ---------- */
|
||
|
||
.trap {
|
||
background: var(--signal-wash);
|
||
border: 1px solid color-mix(in srgb, var(--signal) 22%, transparent);
|
||
border-radius: 3px;
|
||
padding: .875rem 1rem;
|
||
margin: 1rem 0;
|
||
font-size: var(--step-1);
|
||
}
|
||
.trap h4 {
|
||
margin: 0 0 .375rem;
|
||
font-size: var(--step-1);
|
||
font-weight: 600;
|
||
color: var(--signal);
|
||
}
|
||
.trap p { margin: 0 0 .5rem; max-width: 64ch; }
|
||
.trap p:last-child { margin-bottom: 0; }
|
||
|
||
/* ---------- tables ---------- */
|
||
|
||
.scroll { overflow-x: auto; margin: 0 0 1.25rem; border: 1px solid var(--line); border-radius: 3px; }
|
||
table { border-collapse: collapse; width: 100%; font-size: var(--step-1); }
|
||
th, td { padding: .5rem .75rem; text-align: left; border-bottom: 1px solid var(--line-soft); }
|
||
thead th {
|
||
background: var(--surface-sunk);
|
||
font-family: "IBM Plex Mono", monospace;
|
||
font-size: var(--step-0);
|
||
font-weight: 500;
|
||
letter-spacing: .06em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-faint);
|
||
white-space: nowrap;
|
||
}
|
||
tbody tr:last-child td { border-bottom: none; }
|
||
td.num { font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
|
||
.win { color: var(--veg); font-weight: 600; }
|
||
.bad { color: var(--signal); font-weight: 600; }
|
||
|
||
.pill {
|
||
display: inline-block;
|
||
font-family: "IBM Plex Mono", monospace;
|
||
font-size: var(--step-0);
|
||
padding: .1rem .45rem;
|
||
border-radius: 2px;
|
||
background: var(--surface-sunk);
|
||
border: 1px solid var(--line);
|
||
white-space: nowrap;
|
||
}
|
||
.pill.done { background: var(--veg-wash); border-color: color-mix(in srgb, var(--veg) 25%, transparent); color: var(--veg); }
|
||
.pill.now { background: var(--water-wash); border-color: color-mix(in srgb, var(--water) 30%, transparent); color: var(--water); }
|
||
.pill.todo { color: var(--ink-faint); }
|
||
|
||
ul { margin: 0 0 1rem; padding-left: 1.15rem; max-width: 66ch; }
|
||
li { margin-bottom: .3rem; }
|
||
|
||
footer {
|
||
margin-top: 4rem;
|
||
padding-top: 1.25rem;
|
||
border-top: 1px solid var(--line);
|
||
font-size: var(--step-1);
|
||
color: var(--ink-faint);
|
||
}
|
||
|
||
@media (max-width: 34rem) {
|
||
.cadence { margin-left: 0; width: 100%; }
|
||
}
|
||
</style>
|
||
|
||
<div class="wrap">
|
||
|
||
<header class="masthead">
|
||
<div class="eyebrow">공정 정의 · v1 · 2026-08-21</div>
|
||
<h1>서산 명천 메시 분할 파이프라인</h1>
|
||
<p class="standfirst">
|
||
드론 사진측량 메시를 건물·수목·차량·지면으로 분할하고,
|
||
원본 면(face)을 클래스별로 쪼개 내보내기까지의 전 공정.
|
||
</p>
|
||
|
||
<dl class="facts">
|
||
<div class="fact"><dt>대상</dt><dd>서산 명천 도로</dd></div>
|
||
<div class="fact"><dt>원본</dt><dd>OBJ 6블록 · 3.0 GB</dd></div>
|
||
<div class="fact"><dt>좌표계</dt><dd>EPSG:5186</dd></div>
|
||
<div class="fact"><dt>학습 자산</dt><dd>SUM Parts</dd></div>
|
||
<div class="fact"><dt>모델</dt><dd>PointVector</dd></div>
|
||
<div class="fact"><dt>연산</dt><dd>RTX 3060 12 GB</dd></div>
|
||
</dl>
|
||
</header>
|
||
|
||
<p>
|
||
공정은 6단계다. 앞 3단계는 <strong>1회성 자산 구축</strong>이고,
|
||
뒤 3단계는 <strong>타일마다 반복</strong>된다.
|
||
각 단계에는 통과 조건(게이트)이 있다. 게이트를 건너뛰면
|
||
다음 단계에서 원인을 알 수 없는 형태로 실패한다 — 실제로 그렇게 여러 번 잃었다.
|
||
</p>
|
||
|
||
<h2>공정</h2>
|
||
|
||
<div class="spine">
|
||
|
||
<!-- 0 -->
|
||
<section class="phase" data-tone="setup">
|
||
<div class="phase-head">
|
||
<span class="idx">단계 0</span>
|
||
<span class="phase-title">실행 환경</span>
|
||
<span class="cadence">1회 · 반나절</span>
|
||
</div>
|
||
<p class="phase-why">
|
||
커스텀 CUDA 커널을 직접 빌드해야 하므로 Windows 네이티브로는 안 된다. WSL2에서만 성립한다.
|
||
</p>
|
||
<div class="flow">
|
||
<div class="cell"><h4>입력</h4><p>없음</p></div>
|
||
<div class="cell"><h4>처리</h4><p>conda 환경 · CUDA 11.8 · torch 2.0.1 · CUDA 확장 5종 빌드</p></div>
|
||
<div class="cell"><h4>출력</h4><p>동작하는 <code>sumparts</code> 환경</p></div>
|
||
</div>
|
||
<div class="cmds"><span class="c"># 환경 + 확장 빌드 + 소스 현대화 패치</span>
|
||
bash scripts/setup_env.sh
|
||
bash scripts/setup_pointnext.sh
|
||
bash scripts/patch_numpy_aliases.sh
|
||
bash scripts/patch_unlabeled_test.sh
|
||
bash scripts/patch_val_mode.sh</div>
|
||
<div class="gate">
|
||
<strong>게이트</strong>
|
||
<span><code>python scripts/verify_env.py</code> 가 <code>ALL OK</code> 를 낼 것.
|
||
확장 6종 import + <code>openpoints</code> 체인 전부 통과해야 한다.</span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 1 -->
|
||
<section class="phase" data-tone="setup">
|
||
<div class="phase-head">
|
||
<span class="idx">단계 1</span>
|
||
<span class="phase-title">학습 데이터 확보</span>
|
||
<span class="cadence">1회 · 30분</span>
|
||
</div>
|
||
<p class="phase-why">
|
||
한국 데이터에 라벨이 없으므로 SUM Parts로 대신 학습한다.
|
||
저자가 학습 가중치를 공개하지 않아 직접 학습이 유일한 경로다.
|
||
</p>
|
||
<div class="flow">
|
||
<div class="cell"><h4>입력</h4><p>HuggingFace 게이트 수락 (브라우저 수동)</p></div>
|
||
<div class="cell"><h4>처리</h4><p>다운로드 5.3 GB → 전개 13 GB → split 정리</p></div>
|
||
<div class="cell"><h4>출력</h4><p>train 24 / val 8 / test 8 타일</p></div>
|
||
</div>
|
||
<div class="cmds">bash scripts/download_data.sh all
|
||
bash scripts/prepare_full_split.sh
|
||
bash scripts/link_data.sh</div>
|
||
<div class="trap">
|
||
<h4>배포본 split 이름이 로더와 다르다</h4>
|
||
<p>디렉토리는 <code>validate/</code>인데 로더는 <code>val/</code>을 찾는다.
|
||
그대로 두면 <strong>예외 없이</strong> 검증 0개로 학습이 돌아간다.
|
||
<code>prepare_full_split.sh</code>가 심볼릭 링크로 해결한다.</p>
|
||
</div>
|
||
<div class="gate">
|
||
<strong>게이트</strong>
|
||
<span>세 split 모두 <code>24 / 8 / 8</code>로 집계될 것.</span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 2 -->
|
||
<section class="phase" data-tone="train">
|
||
<div class="phase-head">
|
||
<span class="idx">단계 2</span>
|
||
<span class="phase-title">모델 학습</span>
|
||
<span class="cadence">1회 · 4시간</span>
|
||
</div>
|
||
<p class="phase-why">
|
||
PointVector를 쓴다. 논문 실측 mIoU 70.0 %로 번들 최고이고, PointNeXt보다 빠르다.
|
||
PointNet은 15.1 %로 실무용이 아니다.
|
||
</p>
|
||
<div class="flow">
|
||
<div class="cell"><h4>입력</h4><p>SUM Parts face 트랙 · 13클래스</p></div>
|
||
<div class="cell"><h4>처리</h4><p>100 epoch · <code>voxel_max 24000</code> · 워치독 자동 재개</p></div>
|
||
<div class="cell"><h4>출력</h4><p><code>ckpt_best.pth</code> 41 MB</p></div>
|
||
</div>
|
||
<div class="cmds">bash scripts/launch_overnight.sh pointvector-xl
|
||
bash scripts/check_training.sh <span class="c"># 진행 확인</span></div>
|
||
<div class="trap">
|
||
<h4>VRAM 초과가 OOM을 내지 않는다</h4>
|
||
<p>WSL2 드라이버는 VRAM을 넘으면 호스트 RAM으로 흘린다.
|
||
학습은 <strong>조용히 완주한다 — 25~100배 느리게.</strong>
|
||
12 GB 카드에서 <code>peak 15.47 GB</code>가 찍힌다.</p>
|
||
<p><strong>판별법: 전력.</strong> 사용률 100 %인데 전력이 낮으면(3060 기준 60 W대)
|
||
연산이 아니라 PCIe 전송 대기다. 정상이면 140 W대.</p>
|
||
</div>
|
||
<div class="gate">
|
||
<strong>게이트</strong>
|
||
<span><code>verify_speed.sh</code> 가 <code>HEALTHY</code> 를 낼 것.
|
||
<code>peak VRAM < 11 GB</code> 이고 전력이 120 W를 넘어야 한다.</span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 3 -->
|
||
<section class="phase" data-tone="apply">
|
||
<div class="phase-head">
|
||
<span class="idx">단계 3</span>
|
||
<span class="phase-title">한국 데이터 변환</span>
|
||
<span class="cadence">타일마다 · 5분</span>
|
||
</div>
|
||
<p class="phase-why">
|
||
원본 OBJ는 읽기만 한다. 모델 입력은 포인트 클라우드 PLY여야 한다 —
|
||
데이터로더가 <code>*.ply</code>만 스캔하고, OBJ에는 포인트별 라벨 필드가 없다.
|
||
</p>
|
||
<div class="flow">
|
||
<div class="cell"><h4>입력</h4><p>ContextCapture OBJ + 텍스처 아틀라스 17~21장</p></div>
|
||
<div class="cell"><h4>처리</h4><p>노선 축 타일링 → 면적가중 샘플링 → 텍스처 UV 조회</p></div>
|
||
<div class="cell"><h4>출력</h4><p>타일당 47만 포인트 PLY</p></div>
|
||
</div>
|
||
<div class="cmds">python scripts/inspect_obj.py Block.obj <span class="c"># 범위·머티리얼 파악</span>
|
||
python scripts/mesh_to_ply.py Block.obj tile.ply \
|
||
--bbox X0 Y0 X1 Y1 --points 470000
|
||
python scripts/check_ply.py tile.ply <span class="c"># 스키마 대조</span></div>
|
||
<div class="trap">
|
||
<h4>색상 스케일이 조용히 망가진다</h4>
|
||
<p>SUM 배포본은 <code>r,g,b</code> <strong>float32 [0,1]</strong>이다.
|
||
<code>red,green,blue</code> uint8로 쓰면 로더가 정규화를 하지 않아
|
||
<strong>255배 큰 특징값</strong>이 네트워크에 들어간다. 에러 없이 결과만 무의미해진다.</p>
|
||
<p>멀티머티리얼도 함정이다. <code>trimesh.load(force='mesh')</code>로 합치면
|
||
텍스처가 전부 유실되어 결과가 회색이 된다. Scene으로 받아 머티리얼별로 샘플링해야 한다.</p>
|
||
</div>
|
||
<div class="gate">
|
||
<strong>게이트</strong>
|
||
<span><code>check_ply.py</code>가 SUM 배포본과 나란히 <code>OK</code>를 낼 것.
|
||
회색 폴백 경고가 없어야 한다.</span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 4 -->
|
||
<section class="phase" data-tone="apply">
|
||
<div class="phase-head">
|
||
<span class="idx">단계 4</span>
|
||
<span class="phase-title">추론 및 클래스 통합</span>
|
||
<span class="cadence">타일마다 · 1분</span>
|
||
</div>
|
||
<p class="phase-why">
|
||
SUM 13클래스를 우리가 필요한 4클래스로 합친다.
|
||
창·문 수준 세부는 필요 없으므로 세부 클래스의 개별 성적은 비용이 아니다.
|
||
</p>
|
||
<div class="flow">
|
||
<div class="cell"><h4>입력</h4><p>변환된 PLY + 학습 체크포인트</p></div>
|
||
<div class="cell"><h4>처리</h4><p>슬라이딩 윈도우 추론 → 13→4 매핑</p></div>
|
||
<div class="cell"><h4>출력</h4><p>포인트별 클래스 + 예측 PLY</p></div>
|
||
</div>
|
||
<div class="scroll">
|
||
<table>
|
||
<thead><tr><th>우리 클래스</th><th>SUM 원본 클래스</th><th class="num">논문 IoU</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>건물</td><td>facade · roof · chimney · dormer · balcony · roof_installation · wall</td><td class="num">85.9–91.7 %</td></tr>
|
||
<tr><td>수목</td><td>high_vegetation</td><td class="num win">96.8 %</td></tr>
|
||
<tr><td>차량</td><td>car · boat</td><td class="num win">95.2 %</td></tr>
|
||
<tr><td>지면 (도로 포함)</td><td>terrain</td><td class="num win">92.3 %</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<p style="font-size:var(--step-1); color:var(--ink-soft);">
|
||
도로면은 face 트랙에서 <code>terrain</code>에 포함된다. 별도 <code>road</code> 클래스는
|
||
texture 트랙(19클래스)에만 있고 우리에겐 필요 없다.
|
||
</p>
|
||
<div class="gate">
|
||
<strong>게이트</strong>
|
||
<span>Mapple로 육안 확인 + 통합 4클래스 mIoU 측정.
|
||
<strong>"전부 건물" 무지성 분류기(IoU 67 %)를 반드시 이길 것.</strong>
|
||
못 이기면 모델이 퇴화한 상태다.</span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 5 -->
|
||
<section class="phase" data-tone="apply">
|
||
<div class="phase-head">
|
||
<span class="idx">단계 5</span>
|
||
<span class="phase-title">메시 분할</span>
|
||
<span class="cadence">타일마다 · 미구현</span>
|
||
</div>
|
||
<p class="phase-why">
|
||
최종 산출물. 포인트 예측을 원본 면으로 되돌려 클래스별 OBJ로 쪼갠다.
|
||
</p>
|
||
<div class="flow">
|
||
<div class="cell"><h4>입력</h4><p>포인트별 예측 + 원본 OBJ</p></div>
|
||
<div class="cell"><h4>처리</h4><p>포인트→면 역매핑 → 면 다수결 → 클래스별 분리</p></div>
|
||
<div class="cell"><h4>출력</h4><p><code>building.obj</code> · <code>vegetation.obj</code> · <code>vehicle.obj</code> · <code>ground.obj</code></p></div>
|
||
</div>
|
||
<div class="trap">
|
||
<h4>원본 vertex는 불가침</h4>
|
||
<p>분할은 재생성이 아니라 <strong>면(face) 분할</strong>이다.
|
||
구멍 메우기는 새 vertex 덧대기만 허용된다. 원본 지면 Z를 DTM으로 덮어쓰면 실패다.</p>
|
||
<p>현재 변환기는 샘플링 시 face index를 남기지 않는다.
|
||
<strong>역매핑을 위해 face index 보존 기능 추가가 필요하다.</strong></p>
|
||
</div>
|
||
<div class="gate">
|
||
<strong>게이트</strong>
|
||
<span>분할된 면 수의 합 = 원본 면 수. 누락도 중복도 없을 것.</span>
|
||
</div>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<h2>정확도가 부족할 때</h2>
|
||
|
||
<p>
|
||
단계 4의 게이트를 통과 못 하면 도메인 갭 때문이다.
|
||
헬싱키 도시로 학습한 모델을 한국 도로 현장에 적용하는 구조적 한계다.
|
||
그때 붙이는 분기가 아래다.
|
||
</p>
|
||
|
||
<div class="phase" data-tone="train" style="margin-bottom:1rem;">
|
||
<div class="phase-head">
|
||
<span class="idx">분기</span>
|
||
<span class="phase-title">자동 라벨링 + 파인튜닝</span>
|
||
<span class="cadence">조건부</span>
|
||
</div>
|
||
<div class="flow">
|
||
<div class="cell"><h4>입력</h4><p>드론 원본 8,120장 + 카메라 포즈</p></div>
|
||
<div class="cell"><h4>처리</h4><p>SAM 3.1 텍스트 프롬프트 → 2D 마스크 → 포즈로 3D 투영 → 면 투표</p></div>
|
||
<div class="cell"><h4>출력</h4><p>한국 데이터 자동 라벨 → 파인튜닝</p></div>
|
||
</div>
|
||
<ul>
|
||
<li><strong>SAM 3의 텍스트 프롬프트가 전제다.</strong> SAM 2는 덩어리만 나누고 이름을 못 붙인다.</li>
|
||
<li><strong>카메라 포즈 없으면 2D→3D 전이 자체가 불가능하다.</strong> 서산 명천은 PPK와 ContextCapture 메타가 있어 성립한다.</li>
|
||
<li>가림 처리 필요 — 메시로 depth test 해서 안 보이는 면에 투표하면 안 된다.</li>
|
||
<li><strong>사람 몫은 전수 라벨링이 아니라 소량 검증이다.</strong> 자동 라벨로 학습하고 자동 라벨로 평가하면 같은 오류를 서로 확인해주는 꼴이라 정확도를 알 수 없다. 최소 1~2타일은 사람이 확인한 정답이 있어야 한다.</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<h2>모델 선택 근거</h2>
|
||
|
||
<div class="scroll">
|
||
<table>
|
||
<thead>
|
||
<tr><th>모델</th><th>발표</th><th>핵심</th><th class="num">논문 mIoU</th><th class="num">우리 실측 s/iter</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>PointNet</td><td>2017</td><td>전체 한 번에 max-pool. 이웃 개념 없음</td><td class="num bad">15.1 %</td><td class="num">0.291</td></tr>
|
||
<tr><td>PointNet++</td><td>2017</td><td>이웃끼리 묶어 계층 처리</td><td class="num">33.1 %</td><td class="num">0.675</td></tr>
|
||
<tr><td>PointNeXt</td><td>2022</td><td>++ 구조 유지, 학습법·크기 개선</td><td class="num">65.3 %</td><td class="num">0.635</td></tr>
|
||
<tr><td><strong>PointVector</strong></td><td>2023</td><td>이웃 특징을 고차원 벡터로 합침</td><td class="num win">70.0 %</td><td class="num win">0.402</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p style="font-size:var(--step-1); color:var(--ink-soft);">
|
||
s/iter는 RTX 3060 12 GB 실측, XL 모델은 VRAM에 맞춰 <code>voxel_max</code>를 낮춘 값이다.
|
||
PointVector가 가장 정확하고 동시에 가장 빠르다.
|
||
</p>
|
||
|
||
<h2>현재 상태</h2>
|
||
|
||
<div class="scroll">
|
||
<table>
|
||
<thead><tr><th>단계</th><th>상태</th><th>비고</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>0 · 실행 환경</td><td><span class="pill done">완료</span></td><td>확장 5종 빌드, 패치 4건</td></tr>
|
||
<tr><td>1 · 학습 데이터</td><td><span class="pill done">완료</span></td><td>13 GB 전개, 24/8/8</td></tr>
|
||
<tr><td>2 · 모델 학습</td><td><span class="pill now">진행 중</span></td><td>PointVector 100 epoch</td></tr>
|
||
<tr><td>3 · 데이터 변환</td><td><span class="pill done">검증됨</span></td><td>BlockYBA 1타일 POC 통과</td></tr>
|
||
<tr><td>4 · 추론·통합</td><td><span class="pill todo">대기</span></td><td>학습 완료 후</td></tr>
|
||
<tr><td>5 · 메시 분할</td><td><span class="pill todo">미구현</span></td><td>face index 보존 필요</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h2>제약</h2>
|
||
|
||
<ul>
|
||
<li><strong>test 세트는 블라인드다.</strong> 라벨이 전부 <code>-1</code>이라 로컬 채점이 원천 불가하다. 논문 수치와 직접 대조하려면 예측을 저자에게 보내야 한다.</li>
|
||
<li><strong>어노테이션 도구는 비공개다.</strong> 저자가 유료 서비스로 판매 중이라 한국 데이터 라벨링에 쓸 수 없다.</li>
|
||
<li><strong>XL 모델은 논문 설정 재현이 불가하다.</strong> <code>voxel_max</code>를 64000에서 낮춰야 12 GB에 들어간다. 이 값은 중립적 손잡이가 아니라 모델의 동작점 일부다 — 같은 체크포인트가 프로토콜에 따라 mIoU 17.19 / 4.20으로 갈렸다.</li>
|
||
<li><strong>데이터는 CC BY-NC 4.0, 코드는 GPL-3.0이다.</strong> 상업 이용은 저자 허락이 필요하고, NC 데이터로 뽑은 가중치도 NC로 취급하는 게 안전하다.</li>
|
||
</ul>
|
||
|
||
<footer>
|
||
실측 환경 — RTX 3060 12 GB · WSL2 Ubuntu 22.04 · CUDA 11.8 · torch 2.0.1 · Python 3.10<br>
|
||
상세 기록은 <code>docs/SUM-Parts-검토노트.md</code>, 스크립트는 <code>scripts/</code>.
|
||
</footer>
|
||
|
||
</div>
|