Files
sum-parts-test/docs/pipeline.html
T
nbrightandClaude Opus 5 609d9a6972 Add SUM Parts reproduction and Seosan Myeongcheon application pipeline
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>
2026-08-21 10:29:25 +09:00

647 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 &lt; 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.991.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>