Initial publish: SamGeo3 multi-prompt segmentation lab.
Scripts, prompt JSON tiers, usage docs, and README. Input images (data/) and segmentation outputs (output/) are gitignored.
This commit is contained in:
+45
@@ -0,0 +1,45 @@
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.egg-info/
|
||||
.ipynb_checkpoints/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Local secrets / HF
|
||||
.env
|
||||
.hf_token
|
||||
|
||||
# ---- Input / output data (publish code only) ----
|
||||
# Keep folder structure via .gitkeep; ignore all payloads.
|
||||
data/*
|
||||
!data/.gitkeep
|
||||
output/*
|
||||
!output/.gitkeep
|
||||
|
||||
# Large model / geospatial / mask artifacts (anywhere)
|
||||
*.pt
|
||||
*.pth
|
||||
*.ckpt
|
||||
*.npy
|
||||
*.tif
|
||||
*.tiff
|
||||
*.jp2
|
||||
*.gpkg
|
||||
*.shp
|
||||
*.dbf
|
||||
*.shx
|
||||
*.prj
|
||||
*.las
|
||||
*.laz
|
||||
|
||||
# Accidental root-level media dumps
|
||||
/*.jpg
|
||||
/*.jpeg
|
||||
/*.JPG
|
||||
/*.JPEG
|
||||
/*.png
|
||||
/*.PNG
|
||||
/*.webp
|
||||
/*.mp4
|
||||
/*.mov
|
||||
@@ -0,0 +1,180 @@
|
||||
# samgeo3-lab
|
||||
|
||||
독립 SamGeo3 (SAM 3 / SAM 3.1) 세그멘테이션 실험 프로젝트.
|
||||
|
||||
- **패키지**: [segment-geospatial](https://github.com/opengeos/segment-geospatial) (`SamGeo3`)
|
||||
- **문서**: https://samgeo.gishub.org
|
||||
- **범위**: 설치 검증 + 단일 이미지 텍스트 프롬프트 세그멘테이션
|
||||
- **비범위**: railway-client / sam31server 통합, 철도 후처리, REST API
|
||||
|
||||
기존 SAM 서버 env/코드와 **공유하지 않습니다**. 이 폴더의 `.venv`만 사용하세요.
|
||||
|
||||
## 가정 (이 머신 기준)
|
||||
|
||||
| 항목 | 상태 |
|
||||
|------|------|
|
||||
| GPU | NVIDIA RTX 3060 (드라이버 CUDA 13.x 가능) |
|
||||
| conda / pixi | 미설치 → **`uv` + Python 3.12 venv** 사용 |
|
||||
| HF 토큰 | `~/.cache/huggingface/token` 존재 |
|
||||
| 체크포인트 | `facebook/sam3.1` HF 캐시 이미 존재 (~3.3GB) |
|
||||
|
||||
## 폴더 구조
|
||||
|
||||
```
|
||||
samgeo3-lab/
|
||||
README.md
|
||||
requirements.txt
|
||||
pyproject.toml
|
||||
scripts/
|
||||
check_install.py # 설치/GPU/import 확인 (+ 옵션: 모델 로드)
|
||||
text_segment.py # 단일 이미지 텍스트 세그멘테이션
|
||||
multi_prompt_segment.py # 멀티 프롬프트 일괄 세그 (모델 1회 로드)
|
||||
prompts/
|
||||
dji_20260306_0016.json # Grok+Gemini 병합 multi-object 프롬프트
|
||||
README.md
|
||||
docs/usage.html
|
||||
data/ # 입력 이미지
|
||||
output/ # 마스크/시각화 결과
|
||||
.venv/ # 로컬 가상환경 (gitignore)
|
||||
```
|
||||
|
||||
## 설치 (Windows + GPU)
|
||||
|
||||
PowerShell, 프로젝트 루트에서:
|
||||
|
||||
```powershell
|
||||
cd D:\MYCLAUDE_PROJECT\samgeo3-lab
|
||||
|
||||
# 1) 독립 venv (Python 3.12)
|
||||
uv venv --python 3.12 .venv
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
|
||||
# 2) CUDA PyTorch 먼저 (CPU wheel 덮어쓰기 방지)
|
||||
uv pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
|
||||
|
||||
# 3) SamGeo3 extras only (api / all 설치 금지)
|
||||
uv pip install "segment-geospatial[samgeo3]>=1.4.1" matplotlib
|
||||
|
||||
# 4) Windows: sam3 import에 필요 (pip extra가 빠질 수 있음)
|
||||
uv pip install "triton-windows>=3.3.0.post19"
|
||||
|
||||
# 5) (선택) 노트북
|
||||
uv pip install ipykernel jupyterlab
|
||||
```
|
||||
|
||||
### 대안: pixi (공식 권장)
|
||||
|
||||
conda/pixi를 쓸 수 있으면 공식 문서 권장 경로가 더 안정적입니다.
|
||||
https://samgeo.gishub.org/installation/#install-with-pixi-recommended
|
||||
|
||||
### Hugging Face (SAM 3 / 3.1)
|
||||
|
||||
1. https://huggingface.co/facebook/sam3.1 접근 승인
|
||||
2. 인증: `hf auth login` (또는 기존 토큰 파일 유지)
|
||||
3. (선택) 로컬 체크포인트만 쓰려면:
|
||||
|
||||
```powershell
|
||||
$env:SAM3_CHECKPOINT_PATH = "C:\path\to\sam3.1_multiplex.pt"
|
||||
```
|
||||
|
||||
## 실행
|
||||
|
||||
```powershell
|
||||
cd D:\MYCLAUDE_PROJECT\samgeo3-lab
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
|
||||
# A) 패키지/GPU 확인
|
||||
python scripts/check_install.py
|
||||
|
||||
# B) 모델 로드까지 확인 (캐시/다운로드)
|
||||
python scripts/check_install.py --load-model
|
||||
|
||||
# C) 샘플 이미지 텍스트 세그멘테이션
|
||||
python scripts/text_segment.py
|
||||
# 동의어 (기본 confidence=0.3 — sam3.1 score가 0.5 미만인 경우 많음):
|
||||
python scripts/text_segment.py --prompt person --confidence 0.3
|
||||
|
||||
# D) 자체 이미지
|
||||
python scripts/text_segment.py --image data\my.jpg --prompt building --min-size 100
|
||||
|
||||
# E) 멀티 프롬프트 (Grok+Gemini 병합 세트, 모델 1회 로드)
|
||||
python scripts/multi_prompt_segment.py --list-only --tier compact
|
||||
python scripts/multi_prompt_segment.py --tier compact --confidence 0.3
|
||||
python scripts/multi_prompt_segment.py --tier A_high
|
||||
python scripts/multi_prompt_segment.py --tier all --confidence 0.25
|
||||
# DJI 드론 정사 기본 이미지: data/DJI_20260306100802_0016.JPG
|
||||
# 프롬프트 JSON: prompts/dji_20260306_0016.json
|
||||
|
||||
# F) 누락 보강 (Gemini gap) — 같은 output 폴더에 추가
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--tier gap --confidence 0.25 `
|
||||
--output-dir output\dji_0016_compact
|
||||
|
||||
# G) 멀티 결과 한 장으로 합치기 (overlay + grid + HTML)
|
||||
python scripts\merge_multi_results.py `
|
||||
--result-dir output\dji_0016_compact `
|
||||
--image data\DJI_20260306100802_0016.JPG
|
||||
# → output\dji_0016_compact\merged\index.html (디스크 마스크 전부 병합)
|
||||
|
||||
# H) 예시: DJI_0100 전체 파이프라인 (0044 프롬프트 → compact → gap → gap2_core → merge)
|
||||
$img = "data\DJI_20260306101434_0100.JPG"
|
||||
$out = "output\dji_0100_compact"
|
||||
$json = "prompts\dji_20260306_0044.json"
|
||||
|
||||
python scripts\multi_prompt_segment.py --prompts-json $json --list-only --tier compact
|
||||
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--prompts-json $json --image $img --tier compact --confidence 0.3 --output-dir $out
|
||||
# (선택) gap / gap2_core 보강 — 같은 $out 에 추가
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--prompts-json $json --image $img --tier gap --confidence 0.25 --output-dir $out
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--prompts-json $json --image $img --tier gap2_core --confidence 0.22 --output-dir $out
|
||||
|
||||
python scripts\merge_multi_results.py --result-dir $out --image $img --max-side 2048
|
||||
start "$out\merged\index.html"
|
||||
```
|
||||
|
||||
상세 주석 버전은 `prompts/README.md` §「DJI_0100 전체 파이프라인」을 보세요.
|
||||
|
||||
결과 파일은 `output/` 아래에 저장됩니다.
|
||||
|
||||
| 파일 | 내용 |
|
||||
|------|------|
|
||||
| `*_mask.png` | 인스턴스별 unique mask |
|
||||
| `*_scores.npy` | 객체별 confidence 점수 |
|
||||
| `*_ann.png` | 오버레이 시각화 |
|
||||
|
||||
**참고**: 모델 로드 시 `missing_keys` 경고가 날 수 있음 (`sam3` 패키지 vs `sam3.1` 체크포인트 일부 키 불일치). 추론은 동작하나 score가 낮을 수 있어 `--confidence` 튜닝이 필요합니다.
|
||||
|
||||
## SamGeo3 빠른 참고
|
||||
|
||||
```python
|
||||
from samgeo import SamGeo3
|
||||
|
||||
sam = SamGeo3(
|
||||
backend="meta", # sam3.1 은 meta 전용
|
||||
model_id="facebook/sam3.1",
|
||||
confidence_threshold=0.5,
|
||||
resolution=1008,
|
||||
)
|
||||
sam.set_image("data/test_image.jpg")
|
||||
sam.generate_masks("person")
|
||||
sam.save_masks("output/mask.png")
|
||||
```
|
||||
|
||||
- `backend="meta"`: `facebook/sam3`, `facebook/sam3.1`, 배치·인터랙티브 지원
|
||||
- `backend="transformers"`: `facebook/sam3` 만
|
||||
|
||||
## 검증 순서
|
||||
|
||||
1. `check_install.py` — CUDA + imports
|
||||
2. `check_install.py --load-model` — 가중치 로드
|
||||
3. `text_segment.py` — 마스크 저장 확인
|
||||
4. `confidence` / `min_size` 튜닝 후 실데이터 실험
|
||||
|
||||
## 참고
|
||||
|
||||
- 업스트림: https://github.com/opengeos/segment-geospatial
|
||||
- API 문서: https://samgeo.gishub.org/samgeo3/
|
||||
- 라이선스(업스트림): MIT
|
||||
@@ -0,0 +1,251 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>DJI_0044 = 0016 확장 — 철로+도로</title>
|
||||
<style>
|
||||
:root {
|
||||
--panel: #1a2332; --panel2: #243044; --text: #e7ecf3; --muted: #9aa8bc;
|
||||
--accent: #3d9cf0; --accent2: #5eead4; --ok: #34d399; --warn: #fbbf24;
|
||||
--border: #2d3a4f; --code-bg: #0b1220;
|
||||
--font: "Segoe UI", "Malgun Gothic", "Apple SD Gothic Neo", sans-serif;
|
||||
--mono: "Cascadia Code", Consolas, monospace;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0; font-family: var(--font); color: var(--text); line-height: 1.65;
|
||||
background: linear-gradient(160deg, #0b1020, #121a28 40%, #0f1419);
|
||||
}
|
||||
.wrap { max-width: 960px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
|
||||
header.hero {
|
||||
background: linear-gradient(135deg, #1e3a5f, #0f766e);
|
||||
border-radius: 16px; padding: 2rem 1.75rem; margin-bottom: 1.5rem;
|
||||
box-shadow: 0 8px 28px rgba(0,0,0,.35);
|
||||
}
|
||||
header.hero h1 { margin: 0 0 .4rem; font-size: 1.65rem; }
|
||||
header.hero p { margin: 0; color: rgba(255,255,255,.92); }
|
||||
.badges { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1rem; }
|
||||
.badge {
|
||||
font-size: .72rem; font-weight: 600; padding: .22rem .55rem; border-radius: 999px;
|
||||
background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.2);
|
||||
}
|
||||
section, nav.toc {
|
||||
background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
|
||||
padding: 1.2rem 1.35rem; margin-bottom: 1.15rem;
|
||||
}
|
||||
section h2 {
|
||||
margin: 0 0 .7rem; font-size: 1.15rem; color: #fff;
|
||||
border-bottom: 1px solid var(--border); padding-bottom: .45rem;
|
||||
}
|
||||
nav.toc h2 { margin: 0 0 .5rem; font-size: .88rem; color: var(--accent2); text-transform: uppercase; }
|
||||
nav.toc ol { margin: 0; padding-left: 1.2rem; }
|
||||
nav.toc a { color: var(--text); text-decoration: none; }
|
||||
nav.toc a:hover { color: var(--accent); }
|
||||
h3 { margin: 1rem 0 .4rem; color: var(--accent2); font-size: 1rem; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: .9rem; margin: .6rem 0 1rem; }
|
||||
th, td { border: 1px solid var(--border); padding: .48rem .6rem; text-align: left; vertical-align: top; }
|
||||
th { background: var(--panel2); color: var(--accent2); }
|
||||
code { font-family: var(--mono); font-size: .84em; background: var(--code-bg);
|
||||
padding: .1em .35em; border-radius: 4px; border: 1px solid var(--border); }
|
||||
pre {
|
||||
background: var(--code-bg); border: 1px solid var(--border); border-radius: 8px;
|
||||
padding: .95rem 1rem; overflow-x: auto; font-family: var(--mono); font-size: .8rem;
|
||||
color: #d1e0f5; line-height: 1.55;
|
||||
}
|
||||
.cmd { color: #7dd3fc; } .c { color: #6b8a9e; }
|
||||
.callout {
|
||||
border-left: 4px solid var(--ok); background: rgba(52,211,153,.1);
|
||||
padding: .8rem 1rem; border-radius: 8px; margin: .8rem 0;
|
||||
}
|
||||
.callout.warn { border-left-color: var(--warn); background: rgba(251,191,36,.1); }
|
||||
.path { color: var(--accent2); font-family: var(--mono); font-size: .88em; }
|
||||
.flow {
|
||||
display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin: .8rem 0;
|
||||
}
|
||||
.flow span {
|
||||
background: var(--panel2); border: 1px solid var(--border); padding: .4rem .7rem;
|
||||
border-radius: 8px; font-size: .88rem;
|
||||
}
|
||||
.flow .arr { background: transparent; border: 0; color: var(--accent2); font-weight: 700; }
|
||||
footer { text-align: center; color: var(--muted); font-size: .84rem; margin-top: 1.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header class="hero">
|
||||
<h1>DJI_0044 = 이미지 1번(0016) 확장</h1>
|
||||
<p>
|
||||
별도 체계가 아닙니다. <strong>0016 프롬프트·tier·gap/gap2 워크플로를 상속</strong>하고,
|
||||
이 장면(철로+도로+열차+야적)에만 필요한 객체를 <code>scene_delta</code> 로 덧붙입니다.
|
||||
</p>
|
||||
<div class="badges">
|
||||
<span class="badge">extends 0016</span>
|
||||
<span class="badge">same pipeline</span>
|
||||
<span class="badge">scene_delta only</span>
|
||||
<span class="badge">SAM 3.1 meta</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="toc">
|
||||
<h2>목차</h2>
|
||||
<ol>
|
||||
<li><a href="#model">확장 모델</a></li>
|
||||
<li><a href="#delta">scene_delta (이 장면 추가분)</a></li>
|
||||
<li><a href="#workflow">실행 순서 (0016과 동일)</a></li>
|
||||
<li><a href="#run">커맨드</a></li>
|
||||
<li><a href="#files">파일</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<section id="model">
|
||||
<h2>1. 확장 모델</h2>
|
||||
<div class="flow">
|
||||
<span>① DJI_0016 base<br/><small>compact / gap / gap2</small></span>
|
||||
<span class="arr">→</span>
|
||||
<span>② scene_delta<br/><small>열차·도로·야적</small></span>
|
||||
<span class="arr">→</span>
|
||||
<span>③ DJI_0044 compact<br/><small>base + delta</small></span>
|
||||
<span class="arr">→</span>
|
||||
<span>④ merge</span>
|
||||
</div>
|
||||
<table>
|
||||
<tr><th>개념</th><th>설명</th></tr>
|
||||
<tr>
|
||||
<td>베이스</td>
|
||||
<td><code>prompts/dji_20260306_0016.json</code> — 철로·산업 orthophoto에서 검증한 전체 세트</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>확장 파일</td>
|
||||
<td><code>prompts/dji_20260306_0044.json</code> — <code>"extends": "…0016.json"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>상속</td>
|
||||
<td>A~E tier, compact, gap, improved, gap2, gap2_core 전부 사용 가능</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>추가</td>
|
||||
<td><code>scene_delta.prompts</code> + 필요 시 tier별 덧붙임</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>스크립트</td>
|
||||
<td><code>multi_prompt_segment.py</code> 가 extends를 자동 병합</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="callout">
|
||||
<strong>원칙</strong>
|
||||
새 이미지가 들어와도 “새 체계”를 만들지 않고,
|
||||
<strong>1번에서 쌓은 프롬프트 자산 + 장면 delta</strong> 로만 확장합니다.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="delta">
|
||||
<h2>2. scene_delta (0044에서 추가되는 객체)</h2>
|
||||
<p class="muted">Gemini 분석 + 비전 확인. 0016에 없거나 이 장면에서 핵심인 항목.</p>
|
||||
<table>
|
||||
<thead><tr><th>그룹</th><th>프롬프트</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>열차</td>
|
||||
<td><code>train</code>, <code>freight car</code>, <code>container car</code>, locomotive, passenger train…</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>도로</td>
|
||||
<td><code>highway</code>, <code>pedestrian crossing</code>, crosswalk, road marking</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>야적</td>
|
||||
<td><code>pipe pile</code>, pipes, bag pile, wooden pile, wood stack, tarp, gravel…</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>기타</td>
|
||||
<td>chain-link fence, sleeper, trackbed, catenary, red roof…</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
베이스에 이미 있는 것(<code>railway track</code>, <code>building</code>, <code>road</code>,
|
||||
<code>car</code>, <code>blue tarp</code>, <code>material pile</code> 등)은
|
||||
<strong>다시 정의하지 않고 상속</strong>합니다.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="workflow">
|
||||
<h2>3. 실행 순서 (0016과 동일 파이프라인)</h2>
|
||||
<ol>
|
||||
<li><code>--tier compact</code> — 0016 compact + scene_delta</li>
|
||||
<li><code>--tier gap</code> — 0016 누락 보강 세트 (상속)</li>
|
||||
<li><code>--tier gap2</code> 또는 <code>gap2_core</code> — 합본 빈 영역 보강 (상속)</li>
|
||||
<li><code>--tier delta</code> — 0044 추가분만 (빠른 재실험)</li>
|
||||
<li><code>merge_multi_results.py</code></li>
|
||||
</ol>
|
||||
<div class="callout warn">
|
||||
<strong>tier 의미</strong>
|
||||
<code>gap</code> / <code>gap2</code> 는 0044 전용이 아니라
|
||||
<strong>0016에서 만든 보강 세트 그대로</strong>입니다. 같은 철로 orthophoto 계열에 재사용.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="run">
|
||||
<h2>4. 커맨드</h2>
|
||||
<pre><span class="cmd">cd D:\MYCLAUDE_PROJECT\samgeo3-lab</span>
|
||||
<span class="cmd">.\.venv\Scripts\Activate.ps1</span>
|
||||
|
||||
<span class="c"># 상속 결과 확인 (compact = 0016 + delta)</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py `
|
||||
--prompts-json prompts\dji_20260306_0044.json `
|
||||
--list-only --tier compact</span>
|
||||
|
||||
<span class="c"># 이 장면 추가분만</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py `
|
||||
--prompts-json prompts\dji_20260306_0044.json `
|
||||
--list-only --tier delta</span>
|
||||
|
||||
<span class="c"># 본 실행 (1번과 동일 패턴)</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py `
|
||||
--prompts-json prompts\dji_20260306_0044.json `
|
||||
--image data\DJI_20260306100928_0044.JPG `
|
||||
--tier compact --confidence 0.3 `
|
||||
--output-dir output\dji_0044_compact</span>
|
||||
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py `
|
||||
--prompts-json prompts\dji_20260306_0044.json `
|
||||
--tier gap --confidence 0.25 `
|
||||
--output-dir output\dji_0044_compact</span>
|
||||
|
||||
<span class="cmd">python scripts\merge_multi_results.py `
|
||||
--result-dir output\dji_0044_compact `
|
||||
--image data\DJI_20260306100928_0044.JPG</span></pre>
|
||||
</section>
|
||||
|
||||
<section id="files">
|
||||
<h2>5. 파일</h2>
|
||||
<table>
|
||||
<tr><th>경로</th><th>역할</th></tr>
|
||||
<tr>
|
||||
<td><span class="path">prompts/dji_20260306_0016.json</span></td>
|
||||
<td><strong>베이스 (이미지 1)</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="path">prompts/dji_20260306_0044.json</span></td>
|
||||
<td>extends 0016 + scene_delta</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="path">docs/dji_gap_fill.html</span></td>
|
||||
<td>0016 gap 리포트</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="path">output/dji_0044_compact/merged/</span></td>
|
||||
<td>0044 합본</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p><a href="dji_gap_fill.html">← 0016 갭 필 리포트</a> · <a href="usage.html">일반 사용법</a></p>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
samgeo3-lab · 0044 extends 0016 · 별도 체계 아님<br />
|
||||
<span class="path">docs/dji_0044_prompts.html</span>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,534 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>DJI 정사 멀티 프롬프트 · 갭 필 리포트 — samgeo3-lab</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0f1419;
|
||||
--panel: #1a2332;
|
||||
--panel2: #243044;
|
||||
--text: #e7ecf3;
|
||||
--muted: #9aa8bc;
|
||||
--accent: #3d9cf0;
|
||||
--accent2: #5eead4;
|
||||
--warn: #fbbf24;
|
||||
--ok: #34d399;
|
||||
--bad: #f87171;
|
||||
--border: #2d3a4f;
|
||||
--code-bg: #0b1220;
|
||||
--radius: 10px;
|
||||
--shadow: 0 8px 28px rgba(0,0,0,.35);
|
||||
--font: "Segoe UI", "Malgun Gothic", "Apple SD Gothic Neo", sans-serif;
|
||||
--mono: "Cascadia Code", "Consolas", "Courier New", monospace;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--font);
|
||||
background: linear-gradient(160deg, #0b1020 0%, #121a28 40%, #0f1419 100%);
|
||||
color: var(--text);
|
||||
line-height: 1.65;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.wrap { max-width: 960px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
|
||||
header.hero {
|
||||
background: linear-gradient(135deg, #1e3a5f 0%, #0f766e 55%, #134e4a 100%);
|
||||
border-radius: 16px;
|
||||
padding: 2rem 1.75rem;
|
||||
box-shadow: var(--shadow);
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
header.hero h1 { margin: 0 0 .4rem; font-size: 1.75rem; letter-spacing: -.02em; }
|
||||
header.hero p { margin: 0; color: rgba(255,255,255,.92); max-width: 42rem; }
|
||||
.badges { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }
|
||||
.badge {
|
||||
font-size: .75rem; font-weight: 600; padding: .25rem .65rem;
|
||||
border-radius: 999px; background: rgba(255,255,255,.14);
|
||||
border: 1px solid rgba(255,255,255,.22);
|
||||
}
|
||||
nav.toc {
|
||||
background: var(--panel); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); padding: 1rem 1.25rem; margin-bottom: 1.75rem;
|
||||
}
|
||||
nav.toc h2 {
|
||||
margin: 0 0 .6rem; font-size: .9rem; color: var(--accent2);
|
||||
text-transform: uppercase; letter-spacing: .06em;
|
||||
}
|
||||
nav.toc ol { margin: 0; padding-left: 1.25rem; }
|
||||
nav.toc a { color: var(--text); text-decoration: none; }
|
||||
nav.toc a:hover { color: var(--accent); text-decoration: underline; }
|
||||
section {
|
||||
background: var(--panel); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); padding: 1.35rem 1.4rem 1.5rem;
|
||||
margin-bottom: 1.25rem; box-shadow: 0 2px 12px rgba(0,0,0,.2);
|
||||
}
|
||||
section h2 {
|
||||
margin: 0 0 .85rem; font-size: 1.22rem; color: #fff;
|
||||
border-bottom: 1px solid var(--border); padding-bottom: .55rem;
|
||||
}
|
||||
section h3 { margin: 1.2rem 0 .5rem; font-size: 1.02rem; color: var(--accent2); }
|
||||
p { margin: .55rem 0; }
|
||||
.muted { color: var(--muted); }
|
||||
ul, ol { margin: .4rem 0 .8rem; padding-left: 1.3rem; }
|
||||
li { margin: .28rem 0; }
|
||||
a { color: var(--accent); }
|
||||
table {
|
||||
width: 100%; border-collapse: collapse; font-size: .9rem; margin: .75rem 0 1rem;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid var(--border); padding: .5rem .65rem;
|
||||
text-align: left; vertical-align: top;
|
||||
}
|
||||
th { background: var(--panel2); color: var(--accent2); font-weight: 600; }
|
||||
tr:nth-child(even) td { background: rgba(255,255,255,.02); }
|
||||
code, kbd {
|
||||
font-family: var(--mono); font-size: .85em;
|
||||
background: var(--code-bg); padding: .1em .38em;
|
||||
border-radius: 4px; border: 1px solid var(--border);
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg); border: 1px solid var(--border);
|
||||
border-radius: 8px; padding: 1rem 1.1rem; overflow-x: auto;
|
||||
font-family: var(--mono); font-size: .8rem; line-height: 1.55;
|
||||
margin: .7rem 0 1rem; color: #d1e0f5;
|
||||
}
|
||||
pre .c { color: #6b8a9e; }
|
||||
pre .cmd { color: #7dd3fc; }
|
||||
.callout {
|
||||
border-radius: 8px; padding: .85rem 1rem; margin: .9rem 0;
|
||||
border-left: 4px solid var(--accent); background: rgba(61,156,240,.1);
|
||||
}
|
||||
.callout.warn { border-left-color: var(--warn); background: rgba(251,191,36,.1); }
|
||||
.callout.ok { border-left-color: var(--ok); background: rgba(52,211,153,.1); }
|
||||
.callout.bad { border-left-color: var(--bad); background: rgba(248,113,113,.1); }
|
||||
.callout strong { display: block; margin-bottom: .25rem; }
|
||||
.tag-ok { color: var(--ok); font-weight: 600; }
|
||||
.tag-fail { color: var(--bad); font-weight: 600; }
|
||||
.path { color: var(--accent2); font-family: var(--mono); font-size: .88em; }
|
||||
.grid-2 {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: .75rem;
|
||||
}
|
||||
@media (max-width: 720px) { .grid-2 { grid-template-columns: 1fr; } }
|
||||
.stat {
|
||||
background: var(--panel2); border: 1px solid var(--border);
|
||||
border-radius: 8px; padding: .85rem 1rem;
|
||||
}
|
||||
.stat .n { font-size: 1.6rem; font-weight: 700; color: var(--accent2); }
|
||||
.stat .l { font-size: .82rem; color: var(--muted); }
|
||||
.chips { display: flex; flex-wrap: wrap; gap: .35rem; margin: .5rem 0; }
|
||||
.chip {
|
||||
font-size: .78rem; padding: .2rem .5rem; border-radius: 6px;
|
||||
background: #0b1220; border: 1px solid var(--border); font-family: var(--mono);
|
||||
}
|
||||
.chip.ok { border-color: rgba(52,211,153,.45); color: #a7f3d0; }
|
||||
.chip.fail { border-color: rgba(248,113,113,.4); color: #fecaca; }
|
||||
footer {
|
||||
text-align: center; color: var(--muted); font-size: .85rem; margin-top: 2rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header class="hero">
|
||||
<h1>DJI 정사 · 멀티 프롬프트 갭 필 리포트</h1>
|
||||
<p>
|
||||
SamGeo3 / SAM 3.1 텍스트 세그 실험 —
|
||||
compact → gap → gap2 단계로 빈 영역을 메운 과정과 결과 요약.
|
||||
</p>
|
||||
<div class="badges">
|
||||
<span class="badge">facebook/sam3.1</span>
|
||||
<span class="badge">backend=meta</span>
|
||||
<span class="badge">DJI_20260306100802_0016</span>
|
||||
<span class="badge">56 classes merged</span>
|
||||
<span class="badge">samgeo3-lab</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="toc" aria-label="목차">
|
||||
<h2>목차</h2>
|
||||
<ol>
|
||||
<li><a href="#overview">개요 · 파이프라인</a></li>
|
||||
<li><a href="#image">대상 이미지</a></li>
|
||||
<li><a href="#empty">합본에서 보인 빈 영역</a></li>
|
||||
<li><a href="#tiers">프롬프트 tier</a></li>
|
||||
<li><a href="#gap">gap (1차 누락 보강)</a></li>
|
||||
<li><a href="#gap2">gap2 (합본 재검토 보강)</a></li>
|
||||
<li><a href="#results">실측 결과</a></li>
|
||||
<li><a href="#run">실행 커맨드</a></li>
|
||||
<li><a href="#outputs">결과 파일</a></li>
|
||||
<li><a href="#limits">한계 · 다음 단계</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<section id="overview">
|
||||
<h2>1. 개요 · 파이프라인</h2>
|
||||
<div class="grid-2">
|
||||
<div class="stat"><div class="n">compact</div><div class="l">1차 19 프롬프트 → ~18 마스크</div></div>
|
||||
<div class="stat"><div class="n">gap</div><div class="l">Gemini 누락 보강 → road, forest…</div></div>
|
||||
<div class="stat"><div class="n">gap2</div><div class="l">합본 빈 영역 44 프롬프트 → 28 성공</div></div>
|
||||
<div class="stat"><div class="n">56</div><div class="l">최종 merge 클래스 수</div></div>
|
||||
</div>
|
||||
<pre><span class="c"># 전체 흐름</span>
|
||||
<span class="cmd">multi_prompt_segment.py --tier compact</span> <span class="c"># 기본 객체</span>
|
||||
<span class="cmd">multi_prompt_segment.py --tier gap</span> <span class="c"># 1차 누락</span>
|
||||
<span class="cmd">multi_prompt_segment.py --tier gap2</span> <span class="c"># 합본 빈 영역</span>
|
||||
<span class="cmd">merge_multi_results.py</span> <span class="c"># overlay + legend + HTML</span></pre>
|
||||
<p class="muted">
|
||||
설정: <span class="path">prompts/dji_20260306_0016.json</span> ·
|
||||
사용법 일반: <a href="usage.html">docs/usage.html</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="image">
|
||||
<h2>2. 대상 이미지</h2>
|
||||
<table>
|
||||
<tr><th>항목</th><th>값</th></tr>
|
||||
<tr>
|
||||
<td>파일</td>
|
||||
<td><code>data/DJI_20260306100802_0016.JPG</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>해상도</td>
|
||||
<td>8192 × 5460 (드론 정사에 가까운 수직뷰)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>장면</td>
|
||||
<td>복선 철로 · 창고/주차장 · 태양광 지붕 · 수목 · 야적장</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>모델</td>
|
||||
<td><code>facebook/sam3.1</code> · <code>backend=meta</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>confidence</td>
|
||||
<td>compact 0.3 · gap 0.25 · gap2 0.22</td>
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section id="empty">
|
||||
<h2>3. 합본에서 보인 빈 영역</h2>
|
||||
<p>
|
||||
1차 merge(<code>combined_with_legend.png</code>) 기준으로
|
||||
마스크 색이 없고 원본 톤이 남는 구간을 재검토했습니다.
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>구역</th><th>추정 내용</th><th>1차에서 부족한 이유</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>선로 상·하 갈색 띠</td>
|
||||
<td>성토·제방·마른 초지</td>
|
||||
<td><code>railway track</code> 밴드만, 바깥 지면 미채움</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>검은 타프 주변</td>
|
||||
<td>흙더미·절토</td>
|
||||
<td><code>black tarp</code>만, 주변 토사 약함</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>시설 사이 공터</td>
|
||||
<td>콘크리트·야적 바닥</td>
|
||||
<td><code>parking lot</code> / <code>road</code>와 다른 표면</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>하측 야적</td>
|
||||
<td>녹생 덮개·시트</td>
|
||||
<td>색 지붕 프롬프트와 다른 tarp/cover</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>소형 구조</td>
|
||||
<td>shed · 부스 · 차양</td>
|
||||
<td><code>building</code>에 안 묶임</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>지붕 잔여</td>
|
||||
<td>금속 / 골판 / 회색</td>
|
||||
<td>white·green·blue roof만으로는 부족</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>선형 세부</td>
|
||||
<td>도상 · 전선</td>
|
||||
<td>정사 + 텍스트 그라운딩 한계</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section id="tiers">
|
||||
<h2>4. 프롬프트 tier</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>tier</th><th>설명</th><th>권장 conf</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>compact</code></td>
|
||||
<td>1차 실험 19개 (철로·건물·차량·타프…)</td>
|
||||
<td>0.30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gap</code></td>
|
||||
<td>Gemini 누락 보강 (road, dense forest, covered truck…)</td>
|
||||
<td>0.25</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gap2</code></td>
|
||||
<td>합본 빈 영역 전체 44개</td>
|
||||
<td>0.22</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gap2_core</code></td>
|
||||
<td>gap2 중 실측 성공만 (~18개, 재실행용)</td>
|
||||
<td>0.22</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>improved</code></td>
|
||||
<td>compact + gap 병합</td>
|
||||
<td>0.28</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>all</code></td>
|
||||
<td>A~E 전체 (중복 제거)</td>
|
||||
<td>0.25~0.30</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section id="gap">
|
||||
<h2>5. gap (1차 누락 보강)</h2>
|
||||
<p class="muted">Gemini 재검토 + 실측 성공 동의어</p>
|
||||
<div class="chips">
|
||||
<span class="chip ok">road</span>
|
||||
<span class="chip ok">asphalt pavement</span>
|
||||
<span class="chip ok">gravel path</span>
|
||||
<span class="chip ok">dense forest</span>
|
||||
<span class="chip ok">covered truck</span>
|
||||
<span class="chip ok">cargo vehicle</span>
|
||||
<span class="chip ok">debris</span>
|
||||
<span class="chip ok">rubbish</span>
|
||||
<span class="chip ok">light pole</span>
|
||||
<span class="chip ok">scrap pile</span>
|
||||
<span class="chip fail">street light</span>
|
||||
<span class="chip fail">lamp post</span>
|
||||
<span class="chip fail">drainage ditch</span>
|
||||
<span class="chip fail">industrial waste</span>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<strong>팁</strong>
|
||||
가로등: <code>light pole</code> @ 0.2 가 유효.
|
||||
배수로·전선은 정사에서 거의 실패.
|
||||
<code>rubbish</code> / <code>scrap pile</code> 은 노이즈 과다 가능.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="gap2">
|
||||
<h2>6. gap2 (합본 재검토 보강)</h2>
|
||||
<h3>타겟 갭 (JSON notes)</h3>
|
||||
<ul>
|
||||
<li>선로 상·하 갈색 성토/제방·마른 초지</li>
|
||||
<li>검은 타프 주변 흙더미·절토면</li>
|
||||
<li>주차장·창고 사이 콘크리트·공터</li>
|
||||
<li>하측 야적 녹생 방수시트/덮개</li>
|
||||
<li>소형 shed · 담장 · 차양</li>
|
||||
<li>금속/골판/회색 지붕 잔여</li>
|
||||
<li>건물 벽면 · 장벽</li>
|
||||
</ul>
|
||||
|
||||
<h3>성공 프롬프트 <span class="tag-ok">28/44</span> (conf=0.22)</h3>
|
||||
<div class="chips">
|
||||
<span class="chip ok">dirt embankment</span>
|
||||
<span class="chip ok">dirt pile</span>
|
||||
<span class="chip ok">dry grass</span>
|
||||
<span class="chip ok">bushes</span>
|
||||
<span class="chip ok">grass</span>
|
||||
<span class="chip ok">vegetation</span>
|
||||
<span class="chip ok">brown field</span>
|
||||
<span class="chip ok">open ground</span>
|
||||
<span class="chip ok">concrete</span>
|
||||
<span class="chip ok">concrete pad</span>
|
||||
<span class="chip ok">concrete pavement</span>
|
||||
<span class="chip ok">industrial yard</span>
|
||||
<span class="chip ok">paved yard</span>
|
||||
<span class="chip ok">green tarp</span>
|
||||
<span class="chip ok">green plastic sheet</span>
|
||||
<span class="chip ok">plastic cover</span>
|
||||
<span class="chip ok">shed</span>
|
||||
<span class="chip ok">small shed</span>
|
||||
<span class="chip ok">metal roof</span>
|
||||
<span class="chip ok">corrugated roof</span>
|
||||
<span class="chip ok">gray roof</span>
|
||||
<span class="chip ok">wall</span>
|
||||
<span class="chip ok">building wall</span>
|
||||
<span class="chip ok">fence wall</span>
|
||||
<span class="chip ok">barrier</span>
|
||||
<span class="chip ok">canopy</span>
|
||||
<span class="chip ok">awning</span>
|
||||
<span class="chip ok">sidewalk</span>
|
||||
</div>
|
||||
|
||||
<h3>실패 <span class="tag-fail">16</span></h3>
|
||||
<div class="chips">
|
||||
<span class="chip fail">embankment</span>
|
||||
<span class="chip fail">railway embankment</span>
|
||||
<span class="chip fail">soil pile</span>
|
||||
<span class="chip fail">earth mound</span>
|
||||
<span class="chip fail">scrub</span>
|
||||
<span class="chip fail">dormant vegetation</span>
|
||||
<span class="chip fail">booth</span>
|
||||
<span class="chip fail">kiosk</span>
|
||||
<span class="chip fail">retaining wall</span>
|
||||
<span class="chip fail">ballast</span>
|
||||
<span class="chip fail">track bed</span>
|
||||
<span class="chip fail">railway ballast</span>
|
||||
<span class="chip fail">power line</span>
|
||||
<span class="chip fail">overhead wire</span>
|
||||
<span class="chip fail">shadow</span>
|
||||
<span class="chip fail">walkway</span>
|
||||
</div>
|
||||
|
||||
<h3>gap2_core (재실행 권장)</h3>
|
||||
<pre>"dirt embankment", "dirt pile", "dry grass", "bushes", "vegetation",
|
||||
"brown field", "open ground", "concrete", "industrial yard",
|
||||
"green tarp", "plastic cover", "shed", "metal roof", "corrugated roof",
|
||||
"gray roof", "building wall", "barrier", "awning"</pre>
|
||||
</section>
|
||||
|
||||
<section id="results">
|
||||
<h2>7. 실측 결과 요약</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>단계</th><th>merge 클래스</th><th>비고</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>compact + gap</td>
|
||||
<td>~28</td>
|
||||
<td>road / dense forest / covered truck 등 추가</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>+ gap2</td>
|
||||
<td><strong>56</strong></td>
|
||||
<td>성토·초지·콘크리트·shed·금속지붕 등</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="callout warn">
|
||||
<strong>과대 마스크 주의</strong>
|
||||
<code>industrial yard</code>, <code>open ground</code>, <code>dirt embankment</code>
|
||||
픽셀 면적이 커서 다른 클래스를 덮을 수 있습니다.
|
||||
필요 시 해당 <code>*_mask.png</code> 를 제외하고 다시 merge 하세요.
|
||||
</div>
|
||||
<div class="callout ok">
|
||||
<strong>빈 영역 개선에 특히 유효했던 프롬프트</strong>
|
||||
dirt embankment · dry grass · vegetation · concrete · green tarp ·
|
||||
shed · metal/corrugated roof · building wall · awning
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="run">
|
||||
<h2>8. 실행 커맨드</h2>
|
||||
<pre><span class="cmd">cd D:\MYCLAUDE_PROJECT\samgeo3-lab</span>
|
||||
<span class="cmd">.\.venv\Scripts\Activate.ps1</span>
|
||||
|
||||
<span class="c"># 1차</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py `
|
||||
--image "data\DJI_20260306100802_0016.JPG" `
|
||||
--tier compact --confidence 0.3 `
|
||||
--output-dir "output\dji_0016_compact"</span>
|
||||
|
||||
<span class="c"># Gemini 누락 보강 (같은 폴더에 마스크 추가)</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py `
|
||||
--tier gap --confidence 0.25 `
|
||||
--output-dir "output\dji_0016_compact"</span>
|
||||
|
||||
<span class="c"># 합본 빈 영역 보강</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py `
|
||||
--tier gap2 --confidence 0.22 `
|
||||
--output-dir "output\dji_0016_compact"</span>
|
||||
|
||||
<span class="c"># 또는 성공 프롬프트만</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py `
|
||||
--tier gap2_core --confidence 0.22 `
|
||||
--output-dir "output\dji_0016_compact"</span>
|
||||
|
||||
<span class="c"># 한 장으로 합치기</span>
|
||||
<span class="cmd">python scripts\merge_multi_results.py `
|
||||
--result-dir "output\dji_0016_compact" `
|
||||
--image "data\DJI_20260306100802_0016.JPG" `
|
||||
--max-side 2560</span></pre>
|
||||
</section>
|
||||
|
||||
<section id="outputs">
|
||||
<h2>9. 결과 파일</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>경로</th><th>내용</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="path">output/dji_0016_compact/*_mask.png</span></td>
|
||||
<td>프롬프트별 인스턴스 마스크</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="path">output/dji_0016_compact/*_ann.png</span></td>
|
||||
<td>개별 오버레이 (대용량 가능)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="path">output/dji_0016_compact/merged/combined_with_legend.png</span></td>
|
||||
<td>전 클래스 합본 + 범례</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="path">output/dji_0016_compact/merged/per_class_grid.png</span></td>
|
||||
<td>클래스별 썸네일 격자</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="path">output/dji_0016_compact/merged/index.html</span></td>
|
||||
<td>브라우저 뷰 (결과 이미지)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="path">output/dji_0016_compact/merged/merge_summary.json</span></td>
|
||||
<td>클래스·픽셀 통계</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="path">prompts/dji_20260306_0016.json</span></td>
|
||||
<td>tier · gap · gap2 · gap2_core 정의</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
결과 미리보기 HTML:
|
||||
<a href="../output/dji_0016_compact/merged/index.html">merged/index.html</a>
|
||||
(상대 경로; 로컬에서 파일 열기)
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="limits">
|
||||
<h2>10. 한계 · 다음 단계</h2>
|
||||
<ul>
|
||||
<li>SAM 3.1 텍스트 세그는 <strong>개념 단위 인스턴스</strong>이지 픽셀 토지피복 분류기가 아님.</li>
|
||||
<li>도상·전선·그림자 등 가늘거나 추상적인 개념은 텍스트만으로 약함.</li>
|
||||
<li>클래스 간 겹침이 큼 → 우선순위 합성 / 배타 마스크 후처리 권장.</li>
|
||||
<li>작은 시설물은 점·박스 인터랙티브(<code>enable_inst_interactivity</code>)가 유리.</li>
|
||||
<li>초대형 정사는 <code>generate_masks_tiled</code> 로 VRAM·경계 품질 개선 가능.</li>
|
||||
</ul>
|
||||
<div class="callout">
|
||||
<strong>권장 운영</strong>
|
||||
compact → gap → gap2_core → merge → 과대 클래스 수동 제외 → 재 merge.
|
||||
confidence는 0.22~0.35 구간에서 장면별로 조정.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
samgeo3-lab · DJI gap-fill report · 2026-07-15<br />
|
||||
파일: <span class="path">docs/dji_gap_fill.html</span>
|
||||
· 일반 사용법: <a href="usage.html">usage.html</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+763
@@ -0,0 +1,763 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>samgeo3-lab 사용법 — SamGeo3 / SAM 3.1</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0f1419;
|
||||
--panel: #1a2332;
|
||||
--panel2: #243044;
|
||||
--text: #e7ecf3;
|
||||
--muted: #9aa8bc;
|
||||
--accent: #3d9cf0;
|
||||
--accent2: #5eead4;
|
||||
--warn: #fbbf24;
|
||||
--ok: #34d399;
|
||||
--border: #2d3a4f;
|
||||
--code-bg: #0b1220;
|
||||
--radius: 10px;
|
||||
--shadow: 0 8px 28px rgba(0,0,0,.35);
|
||||
--font: "Segoe UI", "Malgun Gothic", "Apple SD Gothic Neo", sans-serif;
|
||||
--mono: "Cascadia Code", "Consolas", "Courier New", monospace;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--font);
|
||||
background: linear-gradient(160deg, #0b1020 0%, #121a28 40%, #0f1419 100%);
|
||||
color: var(--text);
|
||||
line-height: 1.65;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
max-width: 920px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1.25rem 4rem;
|
||||
}
|
||||
|
||||
header.hero {
|
||||
background: linear-gradient(135deg, #1e3a5f 0%, #0f766e 100%);
|
||||
border-radius: 16px;
|
||||
padding: 2rem 1.75rem;
|
||||
box-shadow: var(--shadow);
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
header.hero h1 {
|
||||
margin: 0 0 .4rem;
|
||||
font-size: 1.85rem;
|
||||
letter-spacing: -.02em;
|
||||
}
|
||||
header.hero p {
|
||||
margin: 0;
|
||||
color: rgba(255,255,255,.9);
|
||||
max-width: 40rem;
|
||||
}
|
||||
.badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
font-size: .75rem;
|
||||
font-weight: 600;
|
||||
padding: .25rem .6rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,.15);
|
||||
border: 1px solid rgba(255,255,255,.2);
|
||||
}
|
||||
|
||||
nav.toc {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 1rem 1.25rem;
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
nav.toc h2 {
|
||||
margin: 0 0 .6rem;
|
||||
font-size: .95rem;
|
||||
color: var(--accent2);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .06em;
|
||||
}
|
||||
nav.toc ol {
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
nav.toc a {
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
nav.toc a:hover { color: var(--accent); text-decoration: underline; }
|
||||
|
||||
section {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 1.35rem 1.4rem 1.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,.2);
|
||||
}
|
||||
section h2 {
|
||||
margin: 0 0 .85rem;
|
||||
font-size: 1.25rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding-bottom: .55rem;
|
||||
color: #fff;
|
||||
}
|
||||
section h3 {
|
||||
margin: 1.25rem 0 .5rem;
|
||||
font-size: 1.05rem;
|
||||
color: var(--accent2);
|
||||
}
|
||||
p { margin: .55rem 0; color: var(--text); }
|
||||
.muted { color: var(--muted); }
|
||||
ul, ol { margin: .4rem 0 .8rem; padding-left: 1.3rem; }
|
||||
li { margin: .3rem 0; }
|
||||
a { color: var(--accent); }
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: .92rem;
|
||||
margin: .75rem 0 1rem;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid var(--border);
|
||||
padding: .55rem .7rem;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
th {
|
||||
background: var(--panel2);
|
||||
color: var(--accent2);
|
||||
font-weight: 600;
|
||||
}
|
||||
tr:nth-child(even) td { background: rgba(255,255,255,.02); }
|
||||
code, kbd {
|
||||
font-family: var(--mono);
|
||||
font-size: .86em;
|
||||
background: var(--code-bg);
|
||||
padding: .12em .4em;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 1rem 1.1rem;
|
||||
overflow-x: auto;
|
||||
font-family: var(--mono);
|
||||
font-size: .82rem;
|
||||
line-height: 1.55;
|
||||
margin: .7rem 0 1rem;
|
||||
color: #d1e0f5;
|
||||
}
|
||||
pre .c { color: #6b8a9e; } /* comment */
|
||||
pre .cmd { color: #7dd3fc; }
|
||||
|
||||
.callout {
|
||||
border-radius: 8px;
|
||||
padding: .85rem 1rem;
|
||||
margin: .9rem 0;
|
||||
border-left: 4px solid var(--accent);
|
||||
background: rgba(61,156,240,.1);
|
||||
}
|
||||
.callout.warn {
|
||||
border-left-color: var(--warn);
|
||||
background: rgba(251,191,36,.1);
|
||||
}
|
||||
.callout.ok {
|
||||
border-left-color: var(--ok);
|
||||
background: rgba(52,211,153,.1);
|
||||
}
|
||||
.callout strong { display: block; margin-bottom: .25rem; }
|
||||
|
||||
.steps {
|
||||
counter-reset: step;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.steps > li {
|
||||
counter-increment: step;
|
||||
position: relative;
|
||||
padding: .75rem 0 .75rem 3rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.steps > li:last-child { border-bottom: none; }
|
||||
.steps > li::before {
|
||||
content: counter(step);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: .85rem;
|
||||
width: 1.85rem;
|
||||
height: 1.85rem;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
color: #041018;
|
||||
font-weight: 700;
|
||||
font-size: .85rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
font-size: .85rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.path { color: var(--accent2); font-family: var(--mono); font-size: .9em; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header class="hero">
|
||||
<h1>samgeo3-lab 사용법</h1>
|
||||
<p>
|
||||
segment-geospatial의 <strong>SamGeo3</strong> 백엔드로
|
||||
SAM 3 / SAM 3.1 텍스트 프롬프트 세그멘테이션을 실험하는
|
||||
<strong>독립 프로젝트</strong> 가이드입니다.
|
||||
</p>
|
||||
<div class="badges">
|
||||
<span class="badge">Python 3.12</span>
|
||||
<span class="badge">CUDA / RTX GPU</span>
|
||||
<span class="badge">backend=meta</span>
|
||||
<span class="badge">facebook/sam3.1</span>
|
||||
<span class="badge">Windows</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="toc" aria-label="목차">
|
||||
<h2>목차</h2>
|
||||
<ol>
|
||||
<li><a href="#overview">개요 · 범위</a></li>
|
||||
<li><a href="#structure">폴더 구조</a></li>
|
||||
<li><a href="#install">설치</a></li>
|
||||
<li><a href="#hf">Hugging Face · 체크포인트</a></li>
|
||||
<li><a href="#run">실행 커맨드</a></li>
|
||||
<li><a href="#cli">text_segment.py 옵션</a></li>
|
||||
<li><a href="#multi">멀티 프롬프트 (Grok+Gemini)</a></li>
|
||||
<li><a href="#example-0100">예시: DJI_0100 전체 파이프라인</a></li>
|
||||
<li><a href="#output">출력 파일</a></li>
|
||||
<li><a href="#api">Python API 빠른 참고</a></li>
|
||||
<li><a href="#tips">튜닝 · 트러블슈팅</a></li>
|
||||
<li><a href="#links">참고 링크</a></li>
|
||||
<li><a href="dji_gap_fill.html">DJI_0016 갭 필 리포트</a></li>
|
||||
<li><a href="dji_0044_prompts.html">DJI_0044 철로+도로 프롬프트</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<section id="overview">
|
||||
<h2>1. 개요 · 범위</h2>
|
||||
<p>
|
||||
프로젝트 경로: <span class="path">D:\MYCLAUDE_PROJECT\samgeo3-lab</span>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>항목</th><th>내용</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>패키지</td>
|
||||
<td><code>segment-geospatial</code> (PyPI) — 클래스 <code>SamGeo3</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>하는 일</td>
|
||||
<td>설치 검증, 단일 이미지 텍스트 세그멘테이션, 마스크/시각화 저장</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>하지 않는 일</td>
|
||||
<td>railway-client / sam31server 통합, 철도 후처리, REST API, AnyLabeling/YOLO 파이프라인</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>환경 원칙</td>
|
||||
<td>이 폴더의 <code>.venv</code>만 사용. 다른 SAM 서버 env와 <strong>섞지 않음</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="callout ok">
|
||||
<strong>이미 구성된 경우</strong>
|
||||
설치 단계는 건너뛰고 <a href="#run">§5 실행 커맨드</a>부터 진행하면 됩니다.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="structure">
|
||||
<h2>2. 폴더 구조</h2>
|
||||
<pre>samgeo3-lab/
|
||||
README.md
|
||||
docs/usage.html ← 이 문서
|
||||
requirements.txt
|
||||
pyproject.toml
|
||||
scripts/
|
||||
check_install.py # GPU / import / (옵션) 모델 로드 확인
|
||||
text_segment.py # 단일 이미지 텍스트 세그멘테이션
|
||||
data/ # 입력 이미지
|
||||
output/ # 마스크 · 점수 · 오버레이 결과
|
||||
.venv/ # 독립 가상환경</pre>
|
||||
</section>
|
||||
|
||||
<section id="install">
|
||||
<h2>3. 설치 (Windows + GPU)</h2>
|
||||
<p class="muted">PowerShell에서 프로젝트 루트로 이동한 뒤 순서대로 실행합니다.</p>
|
||||
<pre><span class="cmd">cd D:\MYCLAUDE_PROJECT\samgeo3-lab</span>
|
||||
|
||||
<span class="c"># 1) 독립 venv (Python 3.12)</span>
|
||||
<span class="cmd">uv venv --python 3.12 .venv</span>
|
||||
<span class="cmd">.\.venv\Scripts\Activate.ps1</span>
|
||||
|
||||
<span class="c"># 2) CUDA PyTorch 먼저 (CPU wheel 덮어쓰기 방지)</span>
|
||||
<span class="cmd">uv pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121</span>
|
||||
|
||||
<span class="c"># 3) SamGeo3 extras only (api / all 설치 금지)</span>
|
||||
<span class="cmd">uv pip install "segment-geospatial[samgeo3]>=1.4.1" matplotlib</span>
|
||||
|
||||
<span class="c"># 4) Windows: sam3 import에 필요</span>
|
||||
<span class="cmd">uv pip install "triton-windows>=3.3.0.post19"</span>
|
||||
|
||||
<span class="c"># 5) (선택) 노트북</span>
|
||||
<span class="cmd">uv pip install ipykernel jupyterlab</span></pre>
|
||||
|
||||
<div class="callout warn">
|
||||
<strong>주의</strong>
|
||||
<code>triton-windows</code>가 없으면 <code>import sam3</code> 가 실패합니다.
|
||||
<code>segment-geospatial[samgeo3]</code> 설치 후에도 반드시 확인하세요.
|
||||
</div>
|
||||
|
||||
<h3>대안: pixi (공식 권장)</h3>
|
||||
<p>
|
||||
conda/pixi 환경이 가능하면 PyTorch·CUDA·SAM3 의존성 해결에 유리합니다.<br />
|
||||
<a href="https://samgeo.gishub.org/installation/#install-with-pixi-recommended" target="_blank" rel="noopener">
|
||||
samgeo.gishub.org — Install with pixi
|
||||
</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="hf">
|
||||
<h2>4. Hugging Face · 체크포인트</h2>
|
||||
<ol class="steps">
|
||||
<li>
|
||||
<strong>모델 접근 승인</strong><br />
|
||||
<a href="https://huggingface.co/facebook/sam3.1" target="_blank" rel="noopener">facebook/sam3.1</a>
|
||||
(및 필요 시 <a href="https://huggingface.co/facebook/sam3" target="_blank" rel="noopener">sam3</a>) 폼 제출 후 승인
|
||||
</li>
|
||||
<li>
|
||||
<strong>인증</strong><br />
|
||||
<code>hf auth login</code> 또는 기존 토큰 파일
|
||||
<code>~/.cache/huggingface/token</code> 유지
|
||||
</li>
|
||||
<li>
|
||||
<strong>(선택) 로컬 체크포인트</strong>
|
||||
<pre><span class="cmd">$env:SAM3_CHECKPOINT_PATH = "C:\path\to\sam3.1_multiplex.pt"</span></pre>
|
||||
스크립트에서는 <code>--checkpoint</code> 로도 지정 가능합니다.
|
||||
</li>
|
||||
</ol>
|
||||
<div class="callout">
|
||||
<strong>백엔드 제약</strong>
|
||||
<ul>
|
||||
<li><code>backend="meta"</code> — <code>facebook/sam3</code>, <code>facebook/sam3.1</code> (배치·인터랙티브 풍부)</li>
|
||||
<li><code>backend="transformers"</code> — <code>facebook/sam3</code> 만. <strong>sam3.1은 meta 전용</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="run">
|
||||
<h2>5. 실행 커맨드</h2>
|
||||
<p>매번 가상환경을 활성화한 뒤 실행합니다.</p>
|
||||
<pre><span class="cmd">cd D:\MYCLAUDE_PROJECT\samgeo3-lab</span>
|
||||
<span class="cmd">.\.venv\Scripts\Activate.ps1</span>
|
||||
|
||||
<span class="c"># A) 패키지 / GPU / import 확인</span>
|
||||
<span class="cmd">python scripts\check_install.py</span>
|
||||
|
||||
<span class="c"># B) 모델 로드까지 확인 (HF 캐시 또는 다운로드)</span>
|
||||
<span class="cmd">python scripts\check_install.py --load-model</span>
|
||||
|
||||
<span class="c"># C) 샘플 이미지 텍스트 세그멘테이션 (기본 prompt=person, confidence=0.3)</span>
|
||||
<span class="cmd">python scripts\text_segment.py</span>
|
||||
<span class="cmd">python scripts\text_segment.py --prompt person --confidence 0.3</span>
|
||||
|
||||
<span class="c"># D) 자체 이미지</span>
|
||||
<span class="cmd">python scripts\text_segment.py --image data\my.jpg --prompt building --min-size 100</span></pre>
|
||||
|
||||
<h3>권장 검증 순서</h3>
|
||||
<ol>
|
||||
<li><code>check_install.py</code> — CUDA + imports</li>
|
||||
<li><code>check_install.py --load-model</code> — 가중치 로드</li>
|
||||
<li><code>text_segment.py</code> — 마스크 저장 확인</li>
|
||||
<li><code>--confidence</code> / <code>--min-size</code> 튜닝 후 실데이터 실험</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section id="cli">
|
||||
<h2>6. text_segment.py 옵션</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>옵션</th><th>기본값</th><th>설명</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>--image</code></td>
|
||||
<td><code>data/test_image.jpg</code></td>
|
||||
<td>입력 이미지 경로 (없으면 샘플 자동 다운로드)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--prompt</code></td>
|
||||
<td><code>person</code></td>
|
||||
<td>텍스트 프롬프트 (예: building, tree, car)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--model-id</code></td>
|
||||
<td><code>facebook/sam3.1</code></td>
|
||||
<td>HF 모델 ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--backend</code></td>
|
||||
<td><code>meta</code></td>
|
||||
<td><code>meta</code> | <code>transformers</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--confidence</code></td>
|
||||
<td><code>0.3</code></td>
|
||||
<td>confidence threshold. 0.5면 마스크 0개일 수 있음</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--mask-threshold</code></td>
|
||||
<td><code>0.5</code></td>
|
||||
<td>transformers 후처리용 마스크 threshold</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--resolution</code></td>
|
||||
<td><code>1008</code></td>
|
||||
<td>meta 입력 해상도</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--min-size</code> / <code>--max-size</code></td>
|
||||
<td><code>0</code> / 없음</td>
|
||||
<td>마스크 픽셀 면적 필터</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--device</code></td>
|
||||
<td>자동</td>
|
||||
<td><code>cuda</code> | <code>cpu</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--checkpoint</code></td>
|
||||
<td>환경변수 또는 없음</td>
|
||||
<td>로컬 <code>.pt</code> 경로</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--output-dir</code></td>
|
||||
<td><code>output/</code></td>
|
||||
<td>결과 저장 디렉터리</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--no-viz</code></td>
|
||||
<td>off</td>
|
||||
<td>오버레이 PNG 생략 (마스크는 저장)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>check_install.py 옵션</h3>
|
||||
<pre><span class="cmd">python scripts\check_install.py</span>
|
||||
<span class="cmd">python scripts\check_install.py --load-model</span>
|
||||
<span class="cmd">python scripts\check_install.py --load-model --model-id facebook/sam3.1 --device cuda</span></pre>
|
||||
</section>
|
||||
|
||||
<section id="multi">
|
||||
<h2>6b. 멀티 프롬프트 (Grok + Gemini 병합)</h2>
|
||||
<p>
|
||||
드론 정사 장면 분석 결과를 합친 프롬프트 JSON과 일괄 실행 스크립트입니다.
|
||||
모델·이미지를 <strong>1회만</strong> 로드한 뒤 프롬프트를 순차
|
||||
<code>generate_masks</code> 합니다.
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>경로</th><th>설명</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>prompts/dji_20260306_0016.json</code></td>
|
||||
<td>tier A~D + compact, 장면 노트(KO), 권장 confidence</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>scripts/multi_prompt_segment.py</code></td>
|
||||
<td>멀티 프롬프트 러너 + <code>summary.json</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data/DJI_20260306100802_0016.JPG</code></td>
|
||||
<td>기본 입력 (없으면 sample 경로 fallback)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<pre><span class="cmd">python scripts\multi_prompt_segment.py --list-only --tier compact</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py --tier compact --confidence 0.3</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py --tier A_high</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py --tier all --confidence 0.25</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py --prompts "railway track,building,solar panel"</span></pre>
|
||||
<p class="muted">
|
||||
tier: <code>compact</code> | <code>gap</code> | <code>gap2</code> | <code>gap2_core</code> |
|
||||
<code>improved</code> | <code>all</code> | <code>A_high</code> |
|
||||
<code>B_facility</code> | <code>C_detail</code> | <code>D_rail_domain</code>
|
||||
</p>
|
||||
<p>결과 디렉터리: <code>output/<이미지stem>_<tier>/</code> — 프롬프트별 mask/ann + <code>summary.json</code></p>
|
||||
|
||||
<h3>DJI 정사 — 단일 프롬프트 예시 (실제 경로)</h3>
|
||||
<pre><span class="cmd">cd D:\MYCLAUDE_PROJECT\samgeo3-lab</span>
|
||||
<span class="cmd">.\.venv\Scripts\Activate.ps1</span>
|
||||
|
||||
<span class="cmd">$img = "data\DJI_20260306100802_0016.JPG"</span>
|
||||
<span class="cmd">$out = "output\dji_0016_analysis"</span>
|
||||
|
||||
<span class="cmd">python scripts\text_segment.py --image $img --prompt "solar panel" --confidence 0.3 --output-dir $out</span>
|
||||
<span class="cmd">python scripts\text_segment.py --image $img --prompt "building" --confidence 0.3 --output-dir $out</span>
|
||||
<span class="cmd">python scripts\text_segment.py --image $img --prompt "railway track" --confidence 0.3 --output-dir $out</span></pre>
|
||||
|
||||
<h3>기대 난이도 (정사 드론)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>프롬프트</th><th>기대</th><th>튜닝 팁</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>building</code>, <code>solar panel</code>, <code>tree</code>, <code>car</code>, <code>truck</code></td>
|
||||
<td>높음</td>
|
||||
<td>기본 객체. 낮은 confidence에서도 비교적 잘 잡힘</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>railway track</code>, <code>fence</code>, <code>parking lot</code>, <code>blue tarp</code></td>
|
||||
<td>중~높음</td>
|
||||
<td>선형·구획. context에 따라 변동</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>utility pole</code>, <code>container</code>, <code>material pile</code></td>
|
||||
<td>중</td>
|
||||
<td>작거나 밀집. <code>--min-size</code> 권장</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rail</code>, <code>ballast</code>, <code>railroad sleeper</code>, <code>catenary</code></td>
|
||||
<td>낮~중</td>
|
||||
<td>도메인·가는 객체. confidence 튜닝 필수</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="callout">
|
||||
<strong>권장</strong>
|
||||
confidence <strong>0.25~0.35</strong> 에서 시작. 작은 폴·미세 자재는 <code>--min-size</code>로 노이즈 필터.
|
||||
</div>
|
||||
|
||||
<h3>결과 합치기 (merge)</h3>
|
||||
<p>멀티 프롬프트 실행 후 클래스별 마스크를 <strong>한 장 오버레이 + 격자 + HTML</strong>로 합칩니다.</p>
|
||||
<pre><span class="cmd">python scripts\merge_multi_results.py `
|
||||
--result-dir "output\dji_0016_compact" `
|
||||
--image "data\DJI_20260306100802_0016.JPG" `
|
||||
--max-side 2560</span></pre>
|
||||
<p>출력: <code>output\dji_0016_compact\merged\</code></p>
|
||||
<ul>
|
||||
<li><code>combined_with_legend.png</code> — 전 클래스 오버레이 + 범례</li>
|
||||
<li><code>per_class_grid.png</code> — 클래스별 썸네일</li>
|
||||
<li><code>index.html</code> — 브라우저 뷰</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="example-0100">
|
||||
<h2>6c. 예시: DJI_0100 전체 파이프라인</h2>
|
||||
<p>
|
||||
0044 프롬프트 JSON(<strong>0016 상속 + 도로/열차 delta</strong>)을
|
||||
<code>DJI_…_0100.JPG</code>에 적용하는 end-to-end 예입니다.
|
||||
권장 순서: <code>compact</code> → (선택) <code>gap</code> → (선택) <code>gap2_core</code> → merge → HTML.
|
||||
</p>
|
||||
<p class="muted">
|
||||
confidence: compact <code>0.3</code> · gap <code>0.25</code> · gap2_core <code>0.22</code>.
|
||||
같은 출력 폴더에 단계별로 마스크를 쌓은 뒤 merge하면 한 HTML에서 전부 볼 수 있습니다.
|
||||
</p>
|
||||
<pre><span class="cmd">cd D:\MYCLAUDE_PROJECT\samgeo3-lab</span>
|
||||
<span class="cmd">.\.venv\Scripts\Activate.ps1</span>
|
||||
|
||||
<span class="cmd">$img = "data\DJI_20260306101434_0100.JPG"</span>
|
||||
<span class="cmd">$out = "output\dji_0100_compact"</span>
|
||||
<span class="cmd">$json = "prompts\dji_20260306_0044.json"</span>
|
||||
|
||||
<span class="c"># (선택) 프롬프트 목록 확인</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py --prompts-json $json --list-only --tier compact</span>
|
||||
|
||||
<span class="c"># 1) 세그 — compact (0016 상속 + 도로/열차 delta)</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py `
|
||||
--prompts-json $json `
|
||||
--image $img `
|
||||
--tier compact `
|
||||
--confidence 0.3 `
|
||||
--output-dir $out</span>
|
||||
|
||||
<span class="c"># 2) (선택) gap 보강</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py `
|
||||
--prompts-json $json `
|
||||
--image $img `
|
||||
--tier gap `
|
||||
--confidence 0.25 `
|
||||
--output-dir $out</span>
|
||||
|
||||
<span class="c"># 3) (선택) gap2_core 보강</span>
|
||||
<span class="cmd">python scripts\multi_prompt_segment.py `
|
||||
--prompts-json $json `
|
||||
--image $img `
|
||||
--tier gap2_core `
|
||||
--confidence 0.22 `
|
||||
--output-dir $out</span>
|
||||
|
||||
<span class="c"># 4) 합치기 + interactive index.html</span>
|
||||
<span class="cmd">python scripts\merge_multi_results.py `
|
||||
--result-dir $out `
|
||||
--image $img `
|
||||
--max-side 2048</span>
|
||||
|
||||
<span class="c"># 5) 결과 열기</span>
|
||||
<span class="cmd">start "$out\merged\index.html"</span></pre>
|
||||
<div class="callout ok">
|
||||
<strong>결과</strong>
|
||||
브라우저에서 <code>output\dji_0100_compact\merged\index.html</code> 을 열면
|
||||
오버레이·클래스별 격자·범례를 한눈에 볼 수 있습니다.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="output">
|
||||
<h2>7. 출력 파일</h2>
|
||||
<p>결과는 <code>output/</code> 아래에 저장됩니다. 파일명 패턴: <code>{이미지stem}_{prompt}_*</code></p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>파일</th><th>내용</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>*_mask.png</code></td>
|
||||
<td>인스턴스별 unique mask (객체마다 다른 픽셀 값)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>*_scores.npy</code></td>
|
||||
<td>객체별 confidence 점수 (NumPy 배열). float PNG는 저장 불가</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>*_ann.png</code></td>
|
||||
<td>원본 + 마스크/박스/점수 오버레이 시각화</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="muted">예: <code>test_image_person_mask.png</code>, <code>test_image_person_ann.png</code></p>
|
||||
</section>
|
||||
|
||||
<section id="api">
|
||||
<h2>8. Python API 빠른 참고</h2>
|
||||
<pre><span class="cmd">from samgeo import SamGeo3</span>
|
||||
|
||||
sam = SamGeo3(
|
||||
backend="meta", <span class="c"># sam3.1 은 meta 전용</span>
|
||||
model_id="facebook/sam3.1",
|
||||
confidence_threshold=0.3, <span class="c"># 환경에 맞게 튜닝</span>
|
||||
resolution=1008,
|
||||
enable_segmentation=True,
|
||||
enable_inst_interactivity=False,
|
||||
)
|
||||
sam.set_image("data/test_image.jpg")
|
||||
sam.generate_masks("person") <span class="c"># 텍스트 프롬프트</span>
|
||||
sam.save_masks("output/mask.png")
|
||||
sam.show_anns(output="output/ann.png")</pre>
|
||||
|
||||
<h3>포인트 / 박스 인터랙티브 (참고)</h3>
|
||||
<pre>import numpy as np
|
||||
from samgeo import SamGeo3
|
||||
|
||||
sam = SamGeo3(backend="meta", enable_inst_interactivity=True)
|
||||
sam.set_image("image.jpg")
|
||||
masks, scores, logits = sam.predict_inst(
|
||||
point_coords=np.array([[520, 375]]),
|
||||
point_labels=np.array([1]), <span class="c"># 1=foreground, 0=background</span>
|
||||
)</pre>
|
||||
|
||||
<h3>배치 (meta만)</h3>
|
||||
<pre>sam = SamGeo3(backend="meta")
|
||||
sam.set_image_batch(["a.jpg", "b.jpg"])
|
||||
sam.generate_masks_batch("tree")
|
||||
sam.save_masks_batch("output/", prefix="tree_mask")</pre>
|
||||
</section>
|
||||
|
||||
<section id="tips">
|
||||
<h2>9. 튜닝 · 트러블슈팅</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>증상</th><th>원인 / 대응</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>No module named 'triton'</code></td>
|
||||
<td><code>uv pip install "triton-windows>=3.3.0.post19"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>No objects found</code></td>
|
||||
<td>
|
||||
<code>--confidence</code> 낮추기 (예: 0.3 → 0.2), 프롬프트 변경,
|
||||
명확한 객체가 보이는 이미지 사용
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>로드 시 <code>missing_keys</code> 경고</td>
|
||||
<td>
|
||||
sam3 패키지와 sam3.1 체크포인트 일부 키 불일치.
|
||||
추론은 동작할 수 있으나 score가 낮을 수 있음 → confidence 튜닝
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>CUDA not available</code></td>
|
||||
<td>
|
||||
CUDA torch 재설치, <code>nvidia-smi</code> 확인.
|
||||
SAM3 meta는 GPU 권장 (CPU만으로는 제한)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HF 접근 거부 / 다운로드 실패</td>
|
||||
<td>모델 게이트 승인 + <code>hf auth login</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>작은 객체 과다 검출</td>
|
||||
<td><code>--min-size</code> 로 픽셀 면적 필터</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GeoTIFF 멀티밴드</td>
|
||||
<td>API: <code>set_image(path, bands=[4,3,2])</code> (1-based band index)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="callout warn">
|
||||
<strong>confidence 기본값 0.3 이유</strong>
|
||||
이 환경에서 sam3.1 meta score가 대략 0.33–0.35 부근으로 나오는 경우가 있어,
|
||||
기본 0.5를 쓰면 마스크가 0개일 수 있습니다. 실데이터에서 점수 분포를 보고 조정하세요.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="links">
|
||||
<h2>10. 참고 링크</h2>
|
||||
<ul>
|
||||
<li>업스트림: <a href="https://github.com/opengeos/segment-geospatial" target="_blank" rel="noopener">opengeos/segment-geospatial</a></li>
|
||||
<li>문서: <a href="https://samgeo.gishub.org" target="_blank" rel="noopener">samgeo.gishub.org</a></li>
|
||||
<li>SamGeo3 API: <a href="https://samgeo.gishub.org/samgeo3/" target="_blank" rel="noopener">samgeo3 문서</a></li>
|
||||
<li>설치(pixi): <a href="https://samgeo.gishub.org/installation/" target="_blank" rel="noopener">Installation</a></li>
|
||||
<li>라이선스(업스트림): MIT</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
samgeo3-lab · 독립 SamGeo3 실험 환경 · 생성일 2026-07-15<br />
|
||||
파일 위치: <span class="path">docs/usage.html</span>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,212 @@
|
||||
# Multi-object prompts (Grok + Gemini)
|
||||
|
||||
비전 분석 결과를 합친 SAM 3.1 / SamGeo3 텍스트 프롬프트 세트입니다.
|
||||
|
||||
| 파일 | 설명 |
|
||||
|------|------|
|
||||
| `dji_20260306_0016.json` | DJI 드론 정사 철도·산업 장면 (Grok+Gemini 병합) |
|
||||
| `dji_20260306_0044.json` | **0016 확장** (`extends`) + scene_delta (철로+도로+열차) |
|
||||
|
||||
## tier
|
||||
|
||||
| tier | 내용 |
|
||||
|------|------|
|
||||
| `compact` | 1차 실험용 19개 (기본) |
|
||||
| `gap` | **누락 보강 12개** (Gemini 재검토: road, dense forest, covered truck 등) |
|
||||
| `gap2` | 합본 빈 영역 보강 (다수 프롬프트) |
|
||||
| `gap2_core` | gap2 중 실측 성공 프롬프트만 (재실행 권장) |
|
||||
| `improved` | compact + gap 병합 (~31개) |
|
||||
| `A_high` | 1차 고확률 |
|
||||
| `B_facility` | 시설·구조 |
|
||||
| `C_detail` | 야적·지면·지붕색 |
|
||||
| `D_rail_domain` | 철도 특화 (편차 큼) |
|
||||
| `E_missed` | gap 확장판 (동의어 포함) |
|
||||
| `all` | A→E 전부 (중복 제거) |
|
||||
|
||||
## 기대 난이도 (정사 드론 기준)
|
||||
|
||||
| 프롬프트 | 기대 | 튜닝 팁 |
|
||||
|----------|------|---------|
|
||||
| `building`, `solar panel`, `tree`, `car`, `truck` | 높음 | 기본 객체. 낮은 confidence에서도 비교적 잘 잡힘 |
|
||||
| `railway track`, `fence`, `parking lot`, `blue tarp` | 중~높음 | 선형·구획. context에 따라 변동 |
|
||||
| `utility pole`, `container`, `material pile` | 중 | 작거나 밀집. `--min-size`로 노이즈 제거 |
|
||||
| `rail`, `ballast`, `railroad sleeper`, `catenary` | 낮~중 | 도메인 용어·가는 객체. confidence 튜닝 필수 |
|
||||
|
||||
권장: **confidence 0.25~0.35** 로 시작.
|
||||
|
||||
## 단일 프롬프트 (`text_segment.py`)
|
||||
|
||||
```powershell
|
||||
cd D:\MYCLAUDE_PROJECT\samgeo3-lab
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
|
||||
$img = "data\DJI_20260306100802_0016.JPG"
|
||||
# 또는: $img = "D:\MYCLAUDE_PROJECT\segment-geospatial\sample\DJI_20260306100802_0016.JPG"
|
||||
$out = "output\dji_0016_analysis"
|
||||
|
||||
python scripts\text_segment.py `
|
||||
--image $img `
|
||||
--prompt "solar panel" `
|
||||
--confidence 0.3 `
|
||||
--output-dir $out
|
||||
|
||||
python scripts\text_segment.py `
|
||||
--image $img `
|
||||
--prompt "building" `
|
||||
--confidence 0.3 `
|
||||
--output-dir $out
|
||||
|
||||
python scripts\text_segment.py `
|
||||
--image $img `
|
||||
--prompt "railway track" `
|
||||
--confidence 0.3 `
|
||||
--output-dir $out
|
||||
```
|
||||
|
||||
결과 예: `output\dji_0016_analysis\DJI_..._solar_panel_mask.png`
|
||||
|
||||
## 멀티 프롬프트 일괄 (`multi_prompt_segment.py`) — 이미 포함됨
|
||||
|
||||
모델·이미지를 **1회 로드** 후 프롬프트 루프. 결과는
|
||||
`output\<stem>_<tier>\{stem}_{prompt}_mask.png` 등 + `summary.json`.
|
||||
|
||||
```powershell
|
||||
cd D:\MYCLAUDE_PROJECT\samgeo3-lab
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
|
||||
# 프롬프트 목록만
|
||||
python scripts\multi_prompt_segment.py --list-only --tier compact
|
||||
|
||||
# compact 19개 일괄 (권장 1차)
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--image "data\DJI_20260306100802_0016.JPG" `
|
||||
--tier compact `
|
||||
--confidence 0.3 `
|
||||
--output-dir "output\dji_0016_compact"
|
||||
|
||||
# 누락 보강 (Gemini gap) — confidence 약간 낮게, 같은 폴더에 추가하면 merge에 포함
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--image "data\DJI_20260306100802_0016.JPG" `
|
||||
--tier gap `
|
||||
--confidence 0.25 `
|
||||
--output-dir "output\dji_0016_compact"
|
||||
|
||||
# 1차+보강 한 번에
|
||||
python scripts\multi_prompt_segment.py --tier improved --confidence 0.28
|
||||
|
||||
# 1차 고확률만
|
||||
python scripts\multi_prompt_segment.py --tier A_high --confidence 0.3
|
||||
|
||||
# 전체 tier
|
||||
python scripts\multi_prompt_segment.py --tier all --confidence 0.25
|
||||
|
||||
# 직접 지정
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--prompts "solar panel,building,railway track,tree,car,truck" `
|
||||
--confidence 0.3 `
|
||||
--output-dir "output\dji_0016_analysis"
|
||||
```
|
||||
|
||||
## DJI_0044 = 이미지 1번(0016) 확장 (별도 체계 아님)
|
||||
|
||||
```text
|
||||
0016 base (compact/gap/gap2) + scene_delta(열차·도로·야적) → 0044
|
||||
```
|
||||
|
||||
```powershell
|
||||
# 상속 확인
|
||||
python scripts\multi_prompt_segment.py --prompts-json prompts\dji_20260306_0044.json --list-only --tier compact
|
||||
python scripts\multi_prompt_segment.py --prompts-json prompts\dji_20260306_0044.json --list-only --tier delta
|
||||
|
||||
# 0016과 동일한 파이프라인
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--prompts-json prompts\dji_20260306_0044.json `
|
||||
--image data\DJI_20260306100928_0044.JPG `
|
||||
--tier compact --confidence 0.3 `
|
||||
--output-dir output\dji_0044_compact
|
||||
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--prompts-json prompts\dji_20260306_0044.json `
|
||||
--tier gap --confidence 0.25 `
|
||||
--output-dir output\dji_0044_compact
|
||||
|
||||
python scripts\merge_multi_results.py `
|
||||
--result-dir output\dji_0044_compact `
|
||||
--image data\DJI_20260306100928_0044.JPG
|
||||
```
|
||||
|
||||
HTML: `docs/dji_0044_prompts.html`
|
||||
|
||||
## 예시: DJI_0100 전체 파이프라인 (compact → gap → gap2_core → merge)
|
||||
|
||||
0044 프롬프트 JSON(0016 상속 + 도로/열차 delta)을 **0100 이미지**에 적용하는
|
||||
권장 end-to-end 예입니다. gap / gap2_core 단계는 선택입니다.
|
||||
|
||||
```powershell
|
||||
cd D:\MYCLAUDE_PROJECT\samgeo3-lab
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
|
||||
$img = "data\DJI_20260306101434_0100.JPG"
|
||||
$out = "output\dji_0100_compact"
|
||||
$json = "prompts\dji_20260306_0044.json"
|
||||
|
||||
# (선택) 프롬프트 목록 확인
|
||||
python scripts\multi_prompt_segment.py --prompts-json $json --list-only --tier compact
|
||||
|
||||
# 1) 세그 — compact (0016 상속 + 도로/열차 delta)
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--prompts-json $json `
|
||||
--image $img `
|
||||
--tier compact `
|
||||
--confidence 0.3 `
|
||||
--output-dir $out
|
||||
|
||||
# 2) (선택) gap 보강
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--prompts-json $json `
|
||||
--image $img `
|
||||
--tier gap `
|
||||
--confidence 0.25 `
|
||||
--output-dir $out
|
||||
|
||||
# 3) (선택) gap2_core 보강
|
||||
python scripts\multi_prompt_segment.py `
|
||||
--prompts-json $json `
|
||||
--image $img `
|
||||
--tier gap2_core `
|
||||
--confidence 0.22 `
|
||||
--output-dir $out
|
||||
|
||||
# 4) 합치기 + interactive index.html
|
||||
python scripts\merge_multi_results.py `
|
||||
--result-dir $out `
|
||||
--image $img `
|
||||
--max-side 2048
|
||||
|
||||
# 5) 결과 열기
|
||||
start "$out\merged\index.html"
|
||||
```
|
||||
|
||||
권장 confidence: compact `0.3` → gap `0.25` → gap2_core `0.22`.
|
||||
같은 `$out` 폴더에 단계별로 마스크를 쌓은 뒤 merge하면 한 HTML에서 전부 볼 수 있습니다.
|
||||
|
||||
## 결과 합치기 (overlay + grid + HTML)
|
||||
|
||||
`multi_prompt_segment` 실행 후:
|
||||
|
||||
```powershell
|
||||
python scripts\merge_multi_results.py `
|
||||
--result-dir "output\dji_0016_compact" `
|
||||
--image "data\DJI_20260306100802_0016.JPG" `
|
||||
--max-side 2560
|
||||
```
|
||||
|
||||
출력 (`output\dji_0016_compact\merged\`):
|
||||
|
||||
| 파일 | 내용 |
|
||||
|------|------|
|
||||
| `combined_overlay.png` | 전 클래스 색상 오버레이 |
|
||||
| `combined_with_legend.png` | 오버레이 + 범례 |
|
||||
| `per_class_grid.png` | 클래스별 썸네일 격자 |
|
||||
| `index.html` | 브라우저로 한눈에 보기 |
|
||||
| `merge_summary.json` | 클래스·픽셀 통계 |
|
||||
@@ -0,0 +1,385 @@
|
||||
{
|
||||
"id": "dji_20260306_0016",
|
||||
"image": "data/DJI_20260306100802_0016.JPG",
|
||||
"image_abs_hint": "D:/MYCLAUDE_PROJECT/segment-geospatial/sample/DJI_20260306100802_0016.JPG",
|
||||
"sources": ["grok-vision", "gemini-vision", "gemini-missed-review"],
|
||||
"scene_notes_ko": {
|
||||
"center": "복선 철로, 침목, 자갈 도상(발라스트), 전차선 전주(폴)",
|
||||
"above_track": "나대지/낙엽 경사면, 검은 덮개막, 파란 타프",
|
||||
"below_track": "녹색 방음벽/펜스 라인",
|
||||
"top_left": "낙엽 수목 지역",
|
||||
"top_right": "대형 창고·차양, 주차장, 흰 승용차, 덮개 화물차, 녹색 지붕 소형 건물, 파란 컨테이너, 노란 안전구역, 가로등/전주",
|
||||
"bottom": "태양광 패널 지붕, 흰/회색 지붕, 파란/녹색 지붕, 자재 야적(철재·파이프), 녹색 방수시트, 드럼통 더미",
|
||||
"missed_review_ko": "범례에 가려진 우측 숲·철로 연속부; parking lot만 되고 road 누락; covered truck; street light; drainage; debris"
|
||||
},
|
||||
"tiers": {
|
||||
"A_high": {
|
||||
"description": "잘 잡힐 가능성 높음 — 강력한 기본 객체",
|
||||
"prompts": [
|
||||
"railway track",
|
||||
"train track",
|
||||
"railroad",
|
||||
"building",
|
||||
"roof",
|
||||
"solar panel",
|
||||
"tree",
|
||||
"car",
|
||||
"truck",
|
||||
"fence"
|
||||
]
|
||||
},
|
||||
"B_facility": {
|
||||
"description": "구체적 시설·구조",
|
||||
"prompts": [
|
||||
"warehouse",
|
||||
"factory building",
|
||||
"parking lot",
|
||||
"container",
|
||||
"utility pole",
|
||||
"electric pole",
|
||||
"catenary pole",
|
||||
"sound barrier",
|
||||
"retaining wall",
|
||||
"drainage ditch",
|
||||
"canopy",
|
||||
"awning"
|
||||
]
|
||||
},
|
||||
"C_detail": {
|
||||
"description": "세부·야적·지면·지붕 색",
|
||||
"prompts": [
|
||||
"solar farm",
|
||||
"photovoltaic panel",
|
||||
"cargo truck",
|
||||
"covered truck",
|
||||
"material pile",
|
||||
"pipe stack",
|
||||
"steel pile",
|
||||
"steel pipe",
|
||||
"bare ground",
|
||||
"dirt road",
|
||||
"paved road",
|
||||
"shed",
|
||||
"small building",
|
||||
"blue tarp",
|
||||
"black tarp",
|
||||
"green tarp",
|
||||
"construction tarp",
|
||||
"green roof",
|
||||
"blue roof",
|
||||
"white roof",
|
||||
"parking space"
|
||||
]
|
||||
},
|
||||
"D_rail_domain": {
|
||||
"description": "철도 특화 — 성공 편차 큼, confidence/min_size 튜닝 필요",
|
||||
"prompts": [
|
||||
"rail",
|
||||
"ballast",
|
||||
"railroad sleeper",
|
||||
"overhead line",
|
||||
"railway catenary",
|
||||
"railway embankment",
|
||||
"noise barrier wall"
|
||||
]
|
||||
},
|
||||
"E_missed": {
|
||||
"description": "Gemini 재검토: 1차 compact에서 놓치거나 가려진 영역 보강",
|
||||
"review_notes_ko": [
|
||||
"범례로 가려진 우측: dense forest / tree 연속부",
|
||||
"철로·도상 연속: railway 보강은 D와 중복 가능, gravel path 추가",
|
||||
"parking lot 연결 아스팔트 road 누락",
|
||||
"covered truck / cargo vehicle 누락",
|
||||
"street light / lamp post, drainage ditch",
|
||||
"debris / rubbish / industrial waste"
|
||||
],
|
||||
"prompts": [
|
||||
"road",
|
||||
"asphalt pavement",
|
||||
"asphalt road",
|
||||
"paved road",
|
||||
"gravel path",
|
||||
"dense forest",
|
||||
"forest",
|
||||
"covered truck",
|
||||
"cargo vehicle",
|
||||
"blue covered truck",
|
||||
"street light",
|
||||
"lamp post",
|
||||
"street lamp",
|
||||
"light pole",
|
||||
"drainage ditch",
|
||||
"drainage channel",
|
||||
"debris",
|
||||
"rubbish",
|
||||
"industrial waste",
|
||||
"scrap metal",
|
||||
"scrap pile"
|
||||
],
|
||||
"recommended_confidence": 0.25
|
||||
}
|
||||
},
|
||||
"compact": {
|
||||
"description": "1차 실험용 압축 세트",
|
||||
"prompts": [
|
||||
"railway track",
|
||||
"railroad",
|
||||
"fence",
|
||||
"utility pole",
|
||||
"building",
|
||||
"warehouse",
|
||||
"solar panel",
|
||||
"green roof",
|
||||
"blue roof",
|
||||
"white roof",
|
||||
"car",
|
||||
"truck",
|
||||
"blue tarp",
|
||||
"black tarp",
|
||||
"container",
|
||||
"material pile",
|
||||
"tree",
|
||||
"bare ground",
|
||||
"parking lot"
|
||||
]
|
||||
},
|
||||
"gap": {
|
||||
"description": "누락 보강 전용 — Gemini 추천 + 실측 성공 동의어",
|
||||
"prompts": [
|
||||
"road",
|
||||
"asphalt pavement",
|
||||
"gravel path",
|
||||
"dense forest",
|
||||
"covered truck",
|
||||
"cargo vehicle",
|
||||
"street light",
|
||||
"lamp post",
|
||||
"light pole",
|
||||
"drainage ditch",
|
||||
"debris",
|
||||
"rubbish",
|
||||
"industrial waste",
|
||||
"scrap pile"
|
||||
],
|
||||
"recommended_confidence": 0.25,
|
||||
"notes_ko": {
|
||||
"worked": ["road", "asphalt pavement", "gravel path", "dense forest", "covered truck", "cargo vehicle", "debris", "rubbish", "light pole", "scrap pile"],
|
||||
"hard_at_0.25": ["street light", "lamp post", "drainage ditch", "industrial waste"],
|
||||
"tip": "가로등은 light pole@0.2, 배수로는 정사에서 거의 안 잡힘, rubbish/scrap pile은 노이즈 많을 수 있음"
|
||||
}
|
||||
},
|
||||
"improved": {
|
||||
"description": "compact + gap 병합 (1차+누락 보강, 중복 제거)",
|
||||
"prompts": [
|
||||
"railway track",
|
||||
"railroad",
|
||||
"fence",
|
||||
"utility pole",
|
||||
"building",
|
||||
"warehouse",
|
||||
"solar panel",
|
||||
"green roof",
|
||||
"blue roof",
|
||||
"white roof",
|
||||
"car",
|
||||
"truck",
|
||||
"blue tarp",
|
||||
"black tarp",
|
||||
"container",
|
||||
"material pile",
|
||||
"tree",
|
||||
"bare ground",
|
||||
"parking lot",
|
||||
"road",
|
||||
"asphalt pavement",
|
||||
"gravel path",
|
||||
"dense forest",
|
||||
"covered truck",
|
||||
"cargo vehicle",
|
||||
"street light",
|
||||
"lamp post",
|
||||
"light pole",
|
||||
"drainage ditch",
|
||||
"debris",
|
||||
"rubbish",
|
||||
"industrial waste",
|
||||
"scrap pile"
|
||||
],
|
||||
"recommended_confidence": 0.28
|
||||
},
|
||||
"gap2": {
|
||||
"description": "combined_with_legend 재검토: 아직 원본이 비어 보이는 영역 보강",
|
||||
"source": "visual review of final merged overlay (Downloads/combined_with_legend.png)",
|
||||
"gaps_ko": [
|
||||
"선로 상·하 갈색 성토/제방·마른 초지(scrub) — railway 노란 밴드 바깥",
|
||||
"검은 타프 주변 갈색 흙더미·절토면",
|
||||
"주차장·창고 사이 미포장 공터·콘크리트 슬라브",
|
||||
"하측 야적장 녹생 방수시트/덮개(green tarp) 일부 미채움",
|
||||
"소형 부스·shed·담장·차양",
|
||||
"금속/골판 지붕 중 색 라벨에 안 걸린 부분",
|
||||
"전차선·가느다란 전선/폴 잔여",
|
||||
"건물 벽면·그림자 밴드",
|
||||
"선로 침목·도상(ballast) 세부"
|
||||
],
|
||||
"prompts": [
|
||||
"embankment",
|
||||
"railway embankment",
|
||||
"dirt embankment",
|
||||
"soil pile",
|
||||
"dirt pile",
|
||||
"earth mound",
|
||||
"dry grass",
|
||||
"scrub",
|
||||
"bushes",
|
||||
"grass",
|
||||
"vegetation",
|
||||
"dormant vegetation",
|
||||
"brown field",
|
||||
"open ground",
|
||||
"concrete",
|
||||
"concrete pad",
|
||||
"concrete pavement",
|
||||
"industrial yard",
|
||||
"paved yard",
|
||||
"green tarp",
|
||||
"green plastic sheet",
|
||||
"plastic cover",
|
||||
"shed",
|
||||
"small shed",
|
||||
"booth",
|
||||
"kiosk",
|
||||
"metal roof",
|
||||
"corrugated roof",
|
||||
"gray roof",
|
||||
"wall",
|
||||
"building wall",
|
||||
"fence wall",
|
||||
"barrier",
|
||||
"retaining wall",
|
||||
"canopy",
|
||||
"awning",
|
||||
"ballast",
|
||||
"track bed",
|
||||
"railway ballast",
|
||||
"power line",
|
||||
"overhead wire",
|
||||
"shadow",
|
||||
"sidewalk",
|
||||
"walkway"
|
||||
],
|
||||
"recommended_confidence": 0.22,
|
||||
"worked_at_0.22": [
|
||||
"dirt embankment",
|
||||
"dirt pile",
|
||||
"dry grass",
|
||||
"bushes",
|
||||
"grass",
|
||||
"vegetation",
|
||||
"brown field",
|
||||
"open ground",
|
||||
"concrete",
|
||||
"concrete pad",
|
||||
"concrete pavement",
|
||||
"industrial yard",
|
||||
"paved yard",
|
||||
"green tarp",
|
||||
"green plastic sheet",
|
||||
"plastic cover",
|
||||
"shed",
|
||||
"small shed",
|
||||
"metal roof",
|
||||
"corrugated roof",
|
||||
"gray roof",
|
||||
"wall",
|
||||
"building wall",
|
||||
"fence wall",
|
||||
"barrier",
|
||||
"canopy",
|
||||
"awning",
|
||||
"sidewalk"
|
||||
],
|
||||
"failed_at_0.22": [
|
||||
"embankment",
|
||||
"railway embankment",
|
||||
"soil pile",
|
||||
"earth mound",
|
||||
"scrub",
|
||||
"dormant vegetation",
|
||||
"booth",
|
||||
"kiosk",
|
||||
"retaining wall",
|
||||
"ballast",
|
||||
"track bed",
|
||||
"railway ballast",
|
||||
"power line",
|
||||
"overhead wire",
|
||||
"shadow",
|
||||
"walkway"
|
||||
]
|
||||
},
|
||||
"gap2_core": {
|
||||
"description": "gap2 중 실측 성공 프롬프트만 (재실행 권장 세트)",
|
||||
"prompts": [
|
||||
"dirt embankment",
|
||||
"dirt pile",
|
||||
"dry grass",
|
||||
"bushes",
|
||||
"vegetation",
|
||||
"brown field",
|
||||
"open ground",
|
||||
"concrete",
|
||||
"industrial yard",
|
||||
"green tarp",
|
||||
"plastic cover",
|
||||
"shed",
|
||||
"metal roof",
|
||||
"corrugated roof",
|
||||
"gray roof",
|
||||
"building wall",
|
||||
"barrier",
|
||||
"awning"
|
||||
],
|
||||
"recommended_confidence": 0.22
|
||||
},
|
||||
"recommended": {
|
||||
"model_id": "facebook/sam3.1",
|
||||
"backend": "meta",
|
||||
"confidence": 0.3,
|
||||
"confidence_range": [0.25, 0.35],
|
||||
"gap_confidence": 0.25,
|
||||
"min_size": 0,
|
||||
"note": "1차는 compact@0.3, 누락 보강은 gap@0.25 후 merge. 작은 폴/자재는 --min-size."
|
||||
},
|
||||
"expected_difficulty": [
|
||||
{
|
||||
"prompts": ["building", "solar panel", "tree", "car", "truck"],
|
||||
"level": "high",
|
||||
"level_ko": "높음",
|
||||
"tip_ko": "기본 객체. 낮은 confidence에서도 비교적 잘 잡힘."
|
||||
},
|
||||
{
|
||||
"prompts": ["railway track", "fence", "parking lot", "blue tarp"],
|
||||
"level": "medium_high",
|
||||
"level_ko": "중~높음",
|
||||
"tip_ko": "선형·구획 객체. 장면 context에 따라 성능 변동."
|
||||
},
|
||||
{
|
||||
"prompts": ["road", "asphalt pavement", "dense forest", "covered truck"],
|
||||
"level": "medium",
|
||||
"level_ko": "중 (gap 보강)",
|
||||
"tip_ko": "1차에서 자주 누락. gap tier로 재실행. confidence 0.25 권장."
|
||||
},
|
||||
{
|
||||
"prompts": ["utility pole", "container", "material pile", "street light", "lamp post"],
|
||||
"level": "medium",
|
||||
"level_ko": "중",
|
||||
"tip_ko": "작거나 밀집. --min-size로 노이즈 제거 권장."
|
||||
},
|
||||
{
|
||||
"prompts": ["rail", "ballast", "railroad sleeper", "railway catenary", "debris", "rubbish"],
|
||||
"level": "low_medium",
|
||||
"level_ko": "낮~중",
|
||||
"tip_ko": "도메인·가는 객체·잔해. confidence 튜닝 필수."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"id": "dji_20260306_0044",
|
||||
"extends": "prompts/dji_20260306_0016.json",
|
||||
"extends_note_ko": "1번 이미지(DJI_0016) 프롬프트·tier·gap/gap2 워크플로를 그대로 상속. 이 파일은 장면 전용 객체만 추가 확장. 별도 체계 아님.",
|
||||
"image": "data/DJI_20260306100928_0044.JPG",
|
||||
"image_abs_hint": "D:/MYCLAUDE_PROJECT/segment-geospatial/sample/DJI_20260306100928_0044.JPG",
|
||||
"sources": ["extends:dji_20260306_0016", "gemini-vision", "grok-vision"],
|
||||
"scene_notes_ko": {
|
||||
"extends": "0016 = 철로+산업단지 베이스. 0044 = 동일 파이프라인 + 도로·열차·야적 강화",
|
||||
"top": "다차로 도로·교차로·횡단보도·주행 차량·가로수·밀집 건물·태양광",
|
||||
"middle": "야적장·파이프·백색 자루·목재·철재·파란 타프·철조망 펜스",
|
||||
"bottom": "복선 철로·열차(객차/화차)·전차선 폴·나대지"
|
||||
},
|
||||
"scene_delta": {
|
||||
"description": "0016 베이스에 없는(또는 이 장면에서 핵심인) 추가 객체 — Gemini+Grok",
|
||||
"prompts": [
|
||||
"train",
|
||||
"locomotive",
|
||||
"passenger train",
|
||||
"freight car",
|
||||
"container car",
|
||||
"highway",
|
||||
"pedestrian crossing",
|
||||
"crosswalk",
|
||||
"road marking",
|
||||
"chain-link fence",
|
||||
"pipe stack",
|
||||
"pipe pile",
|
||||
"pipes",
|
||||
"industrial pipes",
|
||||
"bag pile",
|
||||
"bulk bag",
|
||||
"white sack pile",
|
||||
"wooden pile",
|
||||
"lumber pile",
|
||||
"wood stack",
|
||||
"tarp",
|
||||
"gravel",
|
||||
"gravel ground",
|
||||
"paved ground",
|
||||
"dirt ground",
|
||||
"shrub",
|
||||
"sleeper",
|
||||
"trackbed",
|
||||
"catenary",
|
||||
"boxcar",
|
||||
"construction material",
|
||||
"industrial material",
|
||||
"debris pile",
|
||||
"red roof"
|
||||
],
|
||||
"recommended_confidence": 0.28
|
||||
},
|
||||
"tiers": {
|
||||
"A_high": {
|
||||
"description": "0016 A_high + 도로/열차 핵심 추가",
|
||||
"prompts": [
|
||||
"road",
|
||||
"highway",
|
||||
"train",
|
||||
"freight car",
|
||||
"container car",
|
||||
"car",
|
||||
"truck"
|
||||
]
|
||||
},
|
||||
"B_facility": {
|
||||
"description": "0016 B + 횡단보도·도로시설",
|
||||
"prompts": [
|
||||
"pedestrian crossing",
|
||||
"crosswalk",
|
||||
"road marking",
|
||||
"chain-link fence",
|
||||
"street light",
|
||||
"light pole"
|
||||
]
|
||||
},
|
||||
"C_detail": {
|
||||
"description": "0016 C + 야적 자재 세분",
|
||||
"prompts": [
|
||||
"pipe pile",
|
||||
"pipes",
|
||||
"bag pile",
|
||||
"wooden pile",
|
||||
"wood stack",
|
||||
"tarp",
|
||||
"gravel",
|
||||
"dirt ground"
|
||||
]
|
||||
},
|
||||
"D_rail_domain": {
|
||||
"description": "0016 D + 열차 관련",
|
||||
"prompts": [
|
||||
"train",
|
||||
"freight car",
|
||||
"container car",
|
||||
"sleeper",
|
||||
"trackbed",
|
||||
"catenary"
|
||||
]
|
||||
}
|
||||
},
|
||||
"compact": {
|
||||
"description": "0016 compact 상속 + scene_delta(핵심만 필터하지 않고 전체 병합; 실행 시 길면 --tier delta)",
|
||||
"include_scene_delta": true,
|
||||
"prompts": [
|
||||
"train",
|
||||
"freight car",
|
||||
"container car",
|
||||
"highway",
|
||||
"pedestrian crossing",
|
||||
"pipe pile",
|
||||
"wooden pile",
|
||||
"wood stack"
|
||||
],
|
||||
"recommended_confidence": 0.3
|
||||
},
|
||||
"recommended": {
|
||||
"model_id": "facebook/sam3.1",
|
||||
"backend": "meta",
|
||||
"confidence": 0.3,
|
||||
"confidence_range": [0.25, 0.35],
|
||||
"workflow_ko": [
|
||||
"1) 0016과 동일: compact → gap → gap2_core (상속된 tier)",
|
||||
"2) 0044 전용만: --tier delta",
|
||||
"3) 합본: base compact+delta = --tier compact",
|
||||
"4) merge_multi_results.py"
|
||||
],
|
||||
"note": "별도 체계 아님. 1번 이미지 확장."
|
||||
},
|
||||
"expected_difficulty": [
|
||||
{
|
||||
"prompts": ["building", "train", "car", "truck", "tree", "road"],
|
||||
"level": "high",
|
||||
"level_ko": "높음",
|
||||
"tip_ko": "0016과 동일 orthophoto 기본 객체 + 열차"
|
||||
},
|
||||
{
|
||||
"prompts": ["railway track", "highway", "fence", "material pile", "pedestrian crossing"],
|
||||
"level": "medium_high",
|
||||
"level_ko": "중~높음",
|
||||
"tip_ko": "선형·구획"
|
||||
},
|
||||
{
|
||||
"prompts": ["pipe pile", "bag pile", "wooden pile", "street light"],
|
||||
"level": "medium",
|
||||
"level_ko": "중",
|
||||
"tip_ko": "밀집 자재 — min_size / conf 0.22~0.28"
|
||||
},
|
||||
{
|
||||
"prompts": ["rail", "ballast", "sleeper", "catenary", "locomotive"],
|
||||
"level": "low_medium",
|
||||
"level_ko": "낮~중",
|
||||
"tip_ko": "도메인·세분 용어 — 0016 gap2와 동일 한계"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
[project]
|
||||
name = "samgeo3-lab"
|
||||
version = "0.1.0"
|
||||
description = "Independent SamGeo3 (SAM 3 / SAM 3.1) segmentation experiments"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
license = { text = "MIT" }
|
||||
dependencies = [
|
||||
"segment-geospatial[samgeo3]>=1.4.1",
|
||||
"triton-windows>=3.3.0.post19; sys_platform == 'windows'",
|
||||
"matplotlib>=3.8",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
notebook = ["ipykernel>=6.29", "jupyterlab>=4.0"]
|
||||
|
||||
[tool.uv]
|
||||
# Torch CUDA wheels are installed separately from the pytorch index.
|
||||
# See README install section.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Pin loosely for experiments. Install torch CUDA wheels FIRST (see README).
|
||||
# uv pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
|
||||
segment-geospatial[samgeo3]>=1.4.1
|
||||
triton-windows>=3.3.0.post19; sys_platform == "windows"
|
||||
matplotlib>=3.8
|
||||
ipykernel>=6.29
|
||||
jupyterlab>=4.0
|
||||
@@ -0,0 +1,174 @@
|
||||
"""Verify SamGeo3 / PyTorch / CUDA install for this isolated lab env.
|
||||
|
||||
Usage (from project root, with venv active):
|
||||
python scripts/check_install.py
|
||||
python scripts/check_install.py --load-model
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
|
||||
def _ok(msg: str) -> None:
|
||||
print(f"[OK] {msg}")
|
||||
|
||||
|
||||
def _warn(msg: str) -> None:
|
||||
print(f"[WARN] {msg}")
|
||||
|
||||
|
||||
def _fail(msg: str) -> None:
|
||||
print(f"[FAIL] {msg}")
|
||||
|
||||
|
||||
def check_torch() -> bool:
|
||||
try:
|
||||
import torch
|
||||
except ImportError as e:
|
||||
_fail(f"torch import failed: {e}")
|
||||
return False
|
||||
|
||||
_ok(f"torch {torch.__version__}")
|
||||
cuda = torch.cuda.is_available()
|
||||
if cuda:
|
||||
name = torch.cuda.get_device_name(0)
|
||||
cap = torch.cuda.get_device_capability(0)
|
||||
_ok(f"CUDA available | GPU={name} | capability={cap}")
|
||||
_ok(f"cuda runtime reported by torch: {torch.version.cuda}")
|
||||
else:
|
||||
_warn("CUDA not available — SAM3 meta backend needs NVIDIA GPU + CUDA torch")
|
||||
return True
|
||||
|
||||
|
||||
def check_samgeo3_imports() -> bool:
|
||||
try:
|
||||
import samgeo
|
||||
from samgeo import SamGeo3
|
||||
except ImportError as e:
|
||||
_fail(f"samgeo / SamGeo3 import failed: {e}")
|
||||
return False
|
||||
|
||||
_ok(f"samgeo {getattr(samgeo, '__version__', 'unknown')}")
|
||||
_ok("SamGeo3 class importable")
|
||||
|
||||
try:
|
||||
import sam3 # noqa: F401
|
||||
|
||||
_ok(f"sam3 package present ({getattr(sam3, '__version__', 'no __version__')})")
|
||||
except ImportError as e:
|
||||
_fail(f"sam3 package missing (required for backend='meta'): {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def check_hf_access() -> None:
|
||||
token_env = bool(os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN"))
|
||||
token_file = os.path.expanduser("~/.cache/huggingface/token")
|
||||
has_file = os.path.isfile(token_file)
|
||||
if token_env:
|
||||
_ok("HF token found in environment")
|
||||
elif has_file:
|
||||
_ok(f"HF token file present: {token_file}")
|
||||
else:
|
||||
_warn(
|
||||
"No HF token found. SAM3/3.1 gated models need: "
|
||||
"hf auth login (after HF access approval)"
|
||||
)
|
||||
|
||||
ckpt = os.environ.get("SAM3_CHECKPOINT_PATH")
|
||||
if ckpt:
|
||||
exists = os.path.isfile(ckpt)
|
||||
( _ok if exists else _fail)(f"SAM3_CHECKPOINT_PATH={ckpt} exists={exists}")
|
||||
else:
|
||||
_warn("SAM3_CHECKPOINT_PATH not set (optional; HF download/cache used otherwise)")
|
||||
|
||||
|
||||
def try_load_model(model_id: str, device: str | None) -> bool:
|
||||
try:
|
||||
from samgeo import SamGeo3
|
||||
import torch
|
||||
except ImportError as e:
|
||||
_fail(f"imports for model load failed: {e}")
|
||||
return False
|
||||
|
||||
if device is None:
|
||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
|
||||
if device == "cuda" and not torch.cuda.is_available():
|
||||
_fail("requested CUDA but torch.cuda.is_available() is False")
|
||||
return False
|
||||
|
||||
print(f"\nLoading SamGeo3(backend='meta', model_id='{model_id}', device='{device}') ...")
|
||||
try:
|
||||
kwargs = {
|
||||
"backend": "meta",
|
||||
"model_id": model_id,
|
||||
"device": device,
|
||||
"confidence_threshold": 0.5,
|
||||
"enable_segmentation": True,
|
||||
"enable_inst_interactivity": False,
|
||||
}
|
||||
ckpt = os.environ.get("SAM3_CHECKPOINT_PATH")
|
||||
if ckpt and os.path.isfile(ckpt):
|
||||
kwargs["checkpoint_path"] = ckpt
|
||||
kwargs["load_from_HF"] = False
|
||||
_ok(f"using local checkpoint: {ckpt}")
|
||||
|
||||
sam = SamGeo3(**kwargs)
|
||||
_ok(f"model loaded | backend={sam.backend} | device={getattr(sam, 'device', device)}")
|
||||
del sam
|
||||
if torch.cuda.is_available():
|
||||
torch.cuda.empty_cache()
|
||||
return True
|
||||
except Exception as e:
|
||||
_fail(f"model load failed: {e}")
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="SamGeo3 install verification")
|
||||
parser.add_argument(
|
||||
"--load-model",
|
||||
action="store_true",
|
||||
help="Also instantiate SamGeo3 (downloads weights if needed)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--model-id",
|
||||
default="facebook/sam3.1",
|
||||
help="HF model id for --load-model (default: facebook/sam3.1)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--device",
|
||||
default=None,
|
||||
help="cuda | cpu (default: auto)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
print("=== SamGeo3 lab: install check ===\n")
|
||||
print(f"python: {sys.version}")
|
||||
print(f"exe: {sys.executable}\n")
|
||||
|
||||
ok = True
|
||||
ok = check_torch() and ok
|
||||
ok = check_samgeo3_imports() and ok
|
||||
check_hf_access()
|
||||
|
||||
if args.load_model:
|
||||
ok = try_load_model(args.model_id, args.device) and ok
|
||||
|
||||
print()
|
||||
if ok:
|
||||
_ok("all required checks passed")
|
||||
return 0
|
||||
_fail("one or more required checks failed")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,570 @@
|
||||
"""Run SamGeo3 text segmentation for multiple prompts (one model load).
|
||||
|
||||
Prompt sets are Grok + Gemini vision merged lists (see prompts/*.json).
|
||||
|
||||
Usage (from project root, venv active):
|
||||
python scripts/multi_prompt_segment.py
|
||||
python scripts/multi_prompt_segment.py --tier compact
|
||||
python scripts/multi_prompt_segment.py --tier A_high
|
||||
python scripts/multi_prompt_segment.py --tier all
|
||||
python scripts/multi_prompt_segment.py --prompts "building,solar panel,tree"
|
||||
python scripts/multi_prompt_segment.py --image data/DJI_20260306100802_0016.JPG --tier compact
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_PROMPTS_JSON = ROOT / "prompts" / "dji_20260306_0016.json"
|
||||
DEFAULT_IMAGE = ROOT / "data" / "DJI_20260306100802_0016.JPG"
|
||||
# fallback to sample folder outside lab if data copy missing
|
||||
SAMPLE_FALLBACK = Path(
|
||||
r"D:\MYCLAUDE_PROJECT\segment-geospatial\sample\DJI_20260306100802_0016.JPG"
|
||||
)
|
||||
|
||||
|
||||
def safe_name(prompt: str) -> str:
|
||||
return "".join(c if c.isalnum() or c in "-_" else "_" for c in prompt.strip())
|
||||
|
||||
|
||||
def _dedupe(prompts: list[str]) -> list[str]:
|
||||
seen: set[str] = set()
|
||||
out: list[str] = []
|
||||
for p in prompts:
|
||||
p = p.strip()
|
||||
if p and p not in seen:
|
||||
seen.add(p)
|
||||
out.append(p)
|
||||
return out
|
||||
|
||||
|
||||
def _merge_prompt_lists(*lists: list[str]) -> list[str]:
|
||||
merged: list[str] = []
|
||||
for lst in lists:
|
||||
merged.extend(lst or [])
|
||||
return _dedupe(merged)
|
||||
|
||||
|
||||
def load_prompt_config(path: Path, _stack: list[Path] | None = None) -> dict[str, Any]:
|
||||
"""Load JSON; if 'extends' is set, inherit base (image-1) and apply scene delta.
|
||||
|
||||
Extension model (image N expands image 1, not a separate system):
|
||||
- base tiers / compact / gap / improved / gap2* are kept
|
||||
- child may add scene_delta.prompts and optional tier/set additions
|
||||
- child image path overrides base image
|
||||
"""
|
||||
path = path.resolve()
|
||||
stack = _stack or []
|
||||
if path in stack:
|
||||
raise ValueError(f"Circular extends: {path}")
|
||||
stack = stack + [path]
|
||||
|
||||
with open(path, encoding="utf-8") as f:
|
||||
cfg = json.load(f)
|
||||
|
||||
extends = cfg.get("extends")
|
||||
if not extends:
|
||||
return cfg
|
||||
|
||||
base_path = Path(extends)
|
||||
if not base_path.is_absolute():
|
||||
# relative to project root first, then to this file's dir
|
||||
cand = (ROOT / extends).resolve()
|
||||
if not cand.is_file():
|
||||
cand = (path.parent / extends).resolve()
|
||||
base_path = cand
|
||||
if not base_path.is_file():
|
||||
raise FileNotFoundError(f"extends not found: {extends} (from {path})")
|
||||
|
||||
base = load_prompt_config(base_path, stack)
|
||||
|
||||
# Start from base, then overlay child metadata / image
|
||||
merged: dict[str, Any] = json.loads(json.dumps(base)) # deep copy
|
||||
merged["id"] = cfg.get("id", base.get("id"))
|
||||
merged["extends"] = str(extends)
|
||||
merged["extends_resolved"] = str(base_path)
|
||||
for key in (
|
||||
"image",
|
||||
"image_abs_hint",
|
||||
"sources",
|
||||
"scene_notes_ko",
|
||||
"recommended",
|
||||
"expected_difficulty",
|
||||
):
|
||||
if key in cfg:
|
||||
merged[key] = cfg[key]
|
||||
|
||||
# scene-only additions (this image expands base)
|
||||
delta = list(cfg.get("scene_delta", {}).get("prompts", []))
|
||||
merged["scene_delta"] = cfg.get("scene_delta", {"prompts": delta})
|
||||
|
||||
# Merge tiers: base list + child additions (if child redefines tier fully, use add only
|
||||
# unless replace_tiers=true)
|
||||
child_tiers = cfg.get("tiers", {})
|
||||
if cfg.get("replace_tiers"):
|
||||
merged["tiers"] = child_tiers
|
||||
else:
|
||||
base_tiers = dict(merged.get("tiers", {}))
|
||||
for tname, tblock in child_tiers.items():
|
||||
add = list(tblock.get("prompts", []))
|
||||
if tname in base_tiers:
|
||||
base_tiers[tname] = {
|
||||
**base_tiers[tname],
|
||||
"description": tblock.get(
|
||||
"description", base_tiers[tname].get("description", "")
|
||||
),
|
||||
"prompts": _merge_prompt_lists(
|
||||
base_tiers[tname].get("prompts", []), add
|
||||
),
|
||||
}
|
||||
else:
|
||||
base_tiers[tname] = tblock
|
||||
# also append scene_delta into A_high by default for visibility
|
||||
if delta and "A_high" in base_tiers:
|
||||
base_tiers["A_high"] = {
|
||||
**base_tiers["A_high"],
|
||||
"prompts": _merge_prompt_lists(
|
||||
base_tiers["A_high"].get("prompts", []), delta
|
||||
),
|
||||
}
|
||||
merged["tiers"] = base_tiers
|
||||
|
||||
# Named sets: inherit base, extend with delta / child prompts
|
||||
for set_name in (
|
||||
"compact",
|
||||
"gap",
|
||||
"improved",
|
||||
"gap2",
|
||||
"gap2_core",
|
||||
"compact_plus",
|
||||
):
|
||||
base_set = merged.get(set_name, {})
|
||||
base_prompts = list(base_set.get("prompts", [])) if isinstance(base_set, dict) else []
|
||||
child_set = cfg.get(set_name)
|
||||
if child_set is None:
|
||||
# default: base set + scene_delta for compact / improved
|
||||
if set_name in ("compact", "improved", "compact_plus") and delta:
|
||||
merged[set_name] = {
|
||||
**(base_set if isinstance(base_set, dict) else {}),
|
||||
"description": (
|
||||
f"extends base {set_name} + scene_delta "
|
||||
f"({cfg.get('id', 'child')})"
|
||||
),
|
||||
"prompts": _merge_prompt_lists(base_prompts, delta),
|
||||
}
|
||||
continue
|
||||
if child_set.get("mode") == "replace":
|
||||
merged[set_name] = child_set
|
||||
else:
|
||||
# default mode: append (base + child + optional delta)
|
||||
extra = list(child_set.get("prompts", []))
|
||||
use_delta = child_set.get("include_scene_delta", True)
|
||||
parts = [base_prompts, extra]
|
||||
if use_delta:
|
||||
parts.append(delta)
|
||||
merged[set_name] = {
|
||||
**(base_set if isinstance(base_set, dict) else {}),
|
||||
**{k: v for k, v in child_set.items() if k != "prompts"},
|
||||
"description": child_set.get(
|
||||
"description",
|
||||
f"extends base {set_name} + additions",
|
||||
),
|
||||
"prompts": _merge_prompt_lists(*parts),
|
||||
}
|
||||
|
||||
# If child has no compact at all, still attach delta
|
||||
if "compact" not in cfg and delta:
|
||||
bp = list(base.get("compact", {}).get("prompts", []))
|
||||
merged["compact"] = {
|
||||
**base.get("compact", {}),
|
||||
"description": "base compact + scene_delta",
|
||||
"prompts": _merge_prompt_lists(bp, delta),
|
||||
}
|
||||
|
||||
merged["inheritance_ko"] = (
|
||||
"1번 이미지 프롬프트 체계를 상속하고, 이 장면 전용 객체만 추가 확장."
|
||||
)
|
||||
return merged
|
||||
|
||||
|
||||
def resolve_prompts(
|
||||
cfg: dict[str, Any],
|
||||
tier: str,
|
||||
extra_prompts: list[str] | None,
|
||||
) -> list[str]:
|
||||
if extra_prompts:
|
||||
return [p.strip() for p in extra_prompts if p.strip()]
|
||||
|
||||
# scene_delta only (this image's new objects vs base)
|
||||
if tier in ("delta", "scene_delta", "new"):
|
||||
return list(cfg.get("scene_delta", {}).get("prompts", []))
|
||||
|
||||
# top-level named sets (compact / gap / improved / gap2...)
|
||||
if tier in cfg and isinstance(cfg[tier], dict) and "prompts" in cfg[tier]:
|
||||
return list(cfg[tier]["prompts"])
|
||||
|
||||
if tier == "all":
|
||||
seen: set[str] = set()
|
||||
out: list[str] = []
|
||||
for key in (
|
||||
"A_high",
|
||||
"B_facility",
|
||||
"C_detail",
|
||||
"D_rail_domain",
|
||||
"E_missed",
|
||||
):
|
||||
block = cfg.get("tiers", {}).get(key, {})
|
||||
for p in block.get("prompts", []):
|
||||
if p not in seen:
|
||||
seen.add(p)
|
||||
out.append(p)
|
||||
# include scene_delta
|
||||
for p in cfg.get("scene_delta", {}).get("prompts", []):
|
||||
if p not in seen:
|
||||
seen.add(p)
|
||||
out.append(p)
|
||||
return out
|
||||
|
||||
if tier in cfg.get("tiers", {}):
|
||||
return list(cfg["tiers"][tier]["prompts"])
|
||||
|
||||
# allow short aliases
|
||||
aliases = {
|
||||
"a": "A_high",
|
||||
"A": "A_high",
|
||||
"b": "B_facility",
|
||||
"B": "B_facility",
|
||||
"c": "C_detail",
|
||||
"C": "C_detail",
|
||||
"d": "D_rail_domain",
|
||||
"D": "D_rail_domain",
|
||||
"e": "E_missed",
|
||||
"E": "E_missed",
|
||||
"missed": "E_missed",
|
||||
"gap_fill": "gap",
|
||||
}
|
||||
if tier in aliases:
|
||||
key = aliases[tier]
|
||||
if key in cfg.get("tiers", {}):
|
||||
return list(cfg["tiers"][key]["prompts"])
|
||||
if key in cfg and "prompts" in cfg[key]:
|
||||
return list(cfg[key]["prompts"])
|
||||
|
||||
raise ValueError(
|
||||
f"Unknown tier '{tier}'. Use compact | gap | gap2 | improved | all | "
|
||||
f"delta | A_high | B_facility | C_detail | D_rail_domain | E_missed | or --prompts"
|
||||
)
|
||||
|
||||
|
||||
def resolve_image(path: Path | None, cfg: dict[str, Any]) -> Path:
|
||||
candidates: list[Path] = []
|
||||
if path is not None:
|
||||
candidates.append(path)
|
||||
candidates.append(ROOT / cfg.get("image", "data/DJI_20260306100802_0016.JPG"))
|
||||
candidates.append(DEFAULT_IMAGE)
|
||||
candidates.append(SAMPLE_FALLBACK)
|
||||
abs_hint = cfg.get("image_abs_hint")
|
||||
if abs_hint:
|
||||
candidates.append(Path(abs_hint))
|
||||
|
||||
for c in candidates:
|
||||
if c and Path(c).is_file():
|
||||
return Path(c).resolve()
|
||||
raise FileNotFoundError(
|
||||
"Image not found. Place DJI JPG under data/ or pass --image. Tried:\n "
|
||||
+ "\n ".join(str(c) for c in candidates)
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
p = argparse.ArgumentParser(
|
||||
description="Multi-prompt SamGeo3 segmentation (Grok+Gemini prompt sets)"
|
||||
)
|
||||
p.add_argument(
|
||||
"--prompts-json",
|
||||
type=Path,
|
||||
default=DEFAULT_PROMPTS_JSON,
|
||||
help="JSON with tiers/compact prompts",
|
||||
)
|
||||
p.add_argument(
|
||||
"--tier",
|
||||
default="compact",
|
||||
help=(
|
||||
"compact | gap | gap2 | improved | all | "
|
||||
"A_high | B_facility | C_detail | D_rail_domain | E_missed"
|
||||
),
|
||||
)
|
||||
p.add_argument(
|
||||
"--prompts",
|
||||
default=None,
|
||||
help='Comma-separated overrides, e.g. "building,tree,car"',
|
||||
)
|
||||
p.add_argument("--image", type=Path, default=None)
|
||||
p.add_argument("--model-id", default="facebook/sam3.1")
|
||||
p.add_argument("--backend", default="meta", choices=["meta", "transformers"])
|
||||
p.add_argument("--confidence", type=float, default=0.3)
|
||||
p.add_argument("--mask-threshold", type=float, default=0.5)
|
||||
p.add_argument("--resolution", type=int, default=1008)
|
||||
p.add_argument("--min-size", type=int, default=0)
|
||||
p.add_argument("--max-size", type=int, default=None)
|
||||
p.add_argument("--device", default=None)
|
||||
p.add_argument(
|
||||
"--checkpoint",
|
||||
default=os.environ.get("SAM3_CHECKPOINT_PATH"),
|
||||
)
|
||||
p.add_argument(
|
||||
"--output-dir",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="Default: output/<image_stem>_<tier>/",
|
||||
)
|
||||
p.add_argument("--no-viz", action="store_true")
|
||||
p.add_argument(
|
||||
"--skip-empty",
|
||||
action="store_true",
|
||||
default=True,
|
||||
help="Do not write mask files when 0 objects (default true)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--keep-empty",
|
||||
action="store_true",
|
||||
help="Opposite of --skip-empty",
|
||||
)
|
||||
p.add_argument(
|
||||
"--list-only",
|
||||
action="store_true",
|
||||
help="Print resolved prompts and exit (no model load)",
|
||||
)
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
cfg_path = args.prompts_json.resolve()
|
||||
if not cfg_path.is_file():
|
||||
print(f"ERROR: prompts json not found: {cfg_path}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
cfg = load_prompt_config(cfg_path)
|
||||
extra = None
|
||||
if args.prompts:
|
||||
extra = [x.strip() for x in args.prompts.split(",") if x.strip()]
|
||||
|
||||
try:
|
||||
prompts = resolve_prompts(cfg, args.tier, extra)
|
||||
except ValueError as e:
|
||||
print(f"ERROR: {e}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
# de-dupe preserve order
|
||||
seen: set[str] = set()
|
||||
prompts = [p for p in prompts if not (p in seen or seen.add(p))]
|
||||
|
||||
if args.list_only:
|
||||
print(f"prompts_json: {cfg_path}")
|
||||
print(f"tier: {args.tier if not extra else 'custom'}")
|
||||
print(f"count: {len(prompts)}")
|
||||
for i, pr in enumerate(prompts, 1):
|
||||
print(f" {i:02d}. {pr}")
|
||||
return 0
|
||||
|
||||
try:
|
||||
image_path = resolve_image(args.image, cfg)
|
||||
except FileNotFoundError as e:
|
||||
print(f"ERROR: {e}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if args.backend == "transformers" and "sam3.1" in args.model_id:
|
||||
print(
|
||||
"ERROR: facebook/sam3.1 requires backend='meta'.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
tier_tag = "custom" if extra else args.tier
|
||||
out_dir = (
|
||||
args.output_dir.resolve()
|
||||
if args.output_dir
|
||||
else (ROOT / "output" / f"{image_path.stem}_{tier_tag}").resolve()
|
||||
)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
skip_empty = not args.keep_empty
|
||||
|
||||
print("=== SamGeo3 multi-prompt segmentation ===")
|
||||
print(f"image: {image_path}")
|
||||
print(f"prompts_json:{cfg_path}")
|
||||
print(f"tier: {tier_tag}")
|
||||
print(f"n_prompts: {len(prompts)}")
|
||||
print(f"model_id: {args.model_id}")
|
||||
print(f"backend: {args.backend}")
|
||||
print(f"confidence: {args.confidence}")
|
||||
print(f"output_dir: {out_dir}")
|
||||
print("prompts:")
|
||||
for i, pr in enumerate(prompts, 1):
|
||||
print(f" {i:02d}. {pr}")
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from samgeo import SamGeo3
|
||||
|
||||
if args.device is None:
|
||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
else:
|
||||
device = args.device
|
||||
|
||||
print(f"\ndevice: {device} | cuda={torch.cuda.is_available()}")
|
||||
if device == "cuda" and not torch.cuda.is_available():
|
||||
print("ERROR: --device cuda but CUDA is not available", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
init_kwargs: dict[str, Any] = dict(
|
||||
backend=args.backend,
|
||||
model_id=args.model_id,
|
||||
device=device,
|
||||
confidence_threshold=args.confidence,
|
||||
mask_threshold=args.mask_threshold,
|
||||
resolution=args.resolution,
|
||||
enable_segmentation=True,
|
||||
enable_inst_interactivity=False,
|
||||
)
|
||||
if args.checkpoint and os.path.isfile(args.checkpoint):
|
||||
init_kwargs["checkpoint_path"] = args.checkpoint
|
||||
init_kwargs["load_from_HF"] = False
|
||||
print(f"checkpoint: {args.checkpoint}")
|
||||
|
||||
t0 = time.perf_counter()
|
||||
print("\nLoading model once...")
|
||||
sam = SamGeo3(**init_kwargs)
|
||||
print("set_image once...")
|
||||
sam.set_image(str(image_path))
|
||||
t_load = time.perf_counter() - t0
|
||||
print(f"load+set_image: {t_load:.1f}s\n")
|
||||
|
||||
results: list[dict[str, Any]] = []
|
||||
stem = image_path.stem
|
||||
|
||||
for i, prompt in enumerate(prompts, 1):
|
||||
t1 = time.perf_counter()
|
||||
print(f"[{i}/{len(prompts)}] generate_masks({prompt!r})...")
|
||||
gen_kwargs: dict[str, Any] = {"min_size": args.min_size, "quiet": True}
|
||||
if args.max_size is not None:
|
||||
gen_kwargs["max_size"] = args.max_size
|
||||
|
||||
try:
|
||||
sam.generate_masks(prompt, **gen_kwargs)
|
||||
except Exception as e:
|
||||
print(f" FAIL: {e}")
|
||||
results.append(
|
||||
{
|
||||
"prompt": prompt,
|
||||
"n_objects": 0,
|
||||
"error": str(e),
|
||||
"elapsed_s": round(time.perf_counter() - t1, 3),
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
n = len(sam.masks) if getattr(sam, "masks", None) is not None else 0
|
||||
score_vals: list[float] = []
|
||||
scores = getattr(sam, "scores", None)
|
||||
if scores is not None and len(scores):
|
||||
try:
|
||||
score_vals = [
|
||||
float(s.item() if hasattr(s, "item") else s) for s in scores
|
||||
]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
rec: dict[str, Any] = {
|
||||
"prompt": prompt,
|
||||
"n_objects": n,
|
||||
"scores_min": min(score_vals) if score_vals else None,
|
||||
"scores_max": max(score_vals) if score_vals else None,
|
||||
"scores_mean": float(np.mean(score_vals)) if score_vals else None,
|
||||
"elapsed_s": round(time.perf_counter() - t1, 3),
|
||||
"mask": None,
|
||||
"ann": None,
|
||||
"scores_npy": None,
|
||||
}
|
||||
|
||||
if n == 0:
|
||||
print(f" -> 0 objects ({rec['elapsed_s']}s)")
|
||||
results.append(rec)
|
||||
continue
|
||||
|
||||
print(
|
||||
f" -> {n} objects | score "
|
||||
f"{rec['scores_min']:.3f}~{rec['scores_max']:.3f} "
|
||||
f"({rec['elapsed_s']}s)"
|
||||
)
|
||||
|
||||
if skip_empty and n == 0:
|
||||
results.append(rec)
|
||||
continue
|
||||
|
||||
tag = safe_name(prompt)
|
||||
mask_path = out_dir / f"{stem}_{tag}_mask.png"
|
||||
ann_path = out_dir / f"{stem}_{tag}_ann.png"
|
||||
scores_path = out_dir / f"{stem}_{tag}_scores.npy"
|
||||
|
||||
try:
|
||||
sam.save_masks(str(mask_path), unique=True)
|
||||
rec["mask"] = str(mask_path)
|
||||
except Exception as e:
|
||||
print(f" [WARN] save_masks: {e}")
|
||||
|
||||
if score_vals:
|
||||
np.save(str(scores_path), np.array(score_vals, dtype=np.float32))
|
||||
rec["scores_npy"] = str(scores_path)
|
||||
|
||||
if not args.no_viz:
|
||||
try:
|
||||
sam.show_anns(output=str(ann_path))
|
||||
rec["ann"] = str(ann_path)
|
||||
except Exception as e:
|
||||
print(f" [WARN] show_anns: {e}")
|
||||
|
||||
results.append(rec)
|
||||
|
||||
summary = {
|
||||
"image": str(image_path),
|
||||
"prompts_json": str(cfg_path),
|
||||
"tier": tier_tag,
|
||||
"model_id": args.model_id,
|
||||
"backend": args.backend,
|
||||
"confidence": args.confidence,
|
||||
"min_size": args.min_size,
|
||||
"device": device,
|
||||
"load_set_image_s": round(t_load, 3),
|
||||
"total_s": round(time.perf_counter() - t0, 3),
|
||||
"n_prompts": len(prompts),
|
||||
"n_with_objects": sum(1 for r in results if r.get("n_objects", 0) > 0),
|
||||
"results": results,
|
||||
"sources": cfg.get("sources"),
|
||||
"scene_notes_ko": cfg.get("scene_notes_ko"),
|
||||
}
|
||||
summary_path = out_dir / "summary.json"
|
||||
with open(summary_path, "w", encoding="utf-8") as f:
|
||||
json.dump(summary, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print("\n=== summary ===")
|
||||
print(f"with objects: {summary['n_with_objects']}/{summary['n_prompts']}")
|
||||
print(f"total time: {summary['total_s']}s")
|
||||
print(f"summary: {summary_path}")
|
||||
for r in results:
|
||||
n = r.get("n_objects", 0)
|
||||
mark = "OK" if n else "--"
|
||||
print(f" [{mark}] {r['prompt']!r:30s} n={n}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,189 @@
|
||||
"""Single-image text-prompt segmentation with SamGeo3 (meta backend).
|
||||
|
||||
Usage (from project root, with venv active):
|
||||
python scripts/text_segment.py
|
||||
python scripts/text_segment.py --image data/test_image.jpg --prompt person
|
||||
python scripts/text_segment.py --model-id facebook/sam3.1 --confidence 0.4
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Project root = parent of scripts/
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_DATA = ROOT / "data"
|
||||
DEFAULT_OUTPUT = ROOT / "output"
|
||||
SAMPLE_URL = (
|
||||
"https://raw.githubusercontent.com/facebookresearch/sam3/"
|
||||
"refs/heads/main/assets/images/test_image.jpg"
|
||||
)
|
||||
|
||||
|
||||
def ensure_sample_image(path: Path) -> Path:
|
||||
if path.is_file():
|
||||
return path
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
print(f"Downloading sample image -> {path}")
|
||||
try:
|
||||
from samgeo import download_file
|
||||
|
||||
download_file(SAMPLE_URL, str(path))
|
||||
except Exception:
|
||||
import urllib.request
|
||||
|
||||
urllib.request.urlretrieve(SAMPLE_URL, str(path))
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"failed to obtain image: {path}")
|
||||
return path
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
p = argparse.ArgumentParser(description="SamGeo3 text segmentation smoke test")
|
||||
p.add_argument("--image", type=Path, default=DEFAULT_DATA / "test_image.jpg")
|
||||
p.add_argument("--prompt", default="person", help="Text prompt for grounding")
|
||||
p.add_argument("--model-id", default="facebook/sam3.1")
|
||||
p.add_argument("--backend", default="meta", choices=["meta", "transformers"])
|
||||
# sam3.1 meta scores on this sample often peak ~0.3–0.4; 0.5 can yield zero masks
|
||||
p.add_argument("--confidence", type=float, default=0.3)
|
||||
p.add_argument("--mask-threshold", type=float, default=0.5)
|
||||
p.add_argument("--resolution", type=int, default=1008)
|
||||
p.add_argument("--min-size", type=int, default=0, help="Filter tiny masks (pixels)")
|
||||
p.add_argument("--max-size", type=int, default=None)
|
||||
p.add_argument("--device", default=None, help="cuda | cpu (default: auto)")
|
||||
p.add_argument(
|
||||
"--checkpoint",
|
||||
default=os.environ.get("SAM3_CHECKPOINT_PATH"),
|
||||
help="Local .pt path (or set SAM3_CHECKPOINT_PATH)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--output-dir",
|
||||
type=Path,
|
||||
default=DEFAULT_OUTPUT,
|
||||
help="Directory for mask / annotation outputs",
|
||||
)
|
||||
p.add_argument(
|
||||
"--no-viz",
|
||||
action="store_true",
|
||||
help="Skip matplotlib annotation PNG (mask file still saved)",
|
||||
)
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
|
||||
if args.backend == "transformers" and "sam3.1" in args.model_id:
|
||||
print(
|
||||
"ERROR: facebook/sam3.1 requires backend='meta'. "
|
||||
"Use model-id facebook/sam3 for transformers.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
image_path = ensure_sample_image(args.image.resolve())
|
||||
out_dir = args.output_dir.resolve()
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
stem = image_path.stem
|
||||
safe_prompt = "".join(c if c.isalnum() or c in "-_" else "_" for c in args.prompt)
|
||||
mask_path = out_dir / f"{stem}_{safe_prompt}_mask.png"
|
||||
ann_path = out_dir / f"{stem}_{safe_prompt}_ann.png"
|
||||
scores_path = out_dir / f"{stem}_{safe_prompt}_scores.npy"
|
||||
|
||||
print("=== SamGeo3 text segmentation ===")
|
||||
print(f"image: {image_path}")
|
||||
print(f"prompt: {args.prompt}")
|
||||
print(f"model_id: {args.model_id}")
|
||||
print(f"backend: {args.backend}")
|
||||
print(f"output_dir: {out_dir}")
|
||||
|
||||
import torch
|
||||
from samgeo import SamGeo3
|
||||
|
||||
if args.device is None:
|
||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
else:
|
||||
device = args.device
|
||||
|
||||
print(f"device: {device} | cuda={torch.cuda.is_available()}")
|
||||
if device == "cuda" and not torch.cuda.is_available():
|
||||
print("ERROR: --device cuda but CUDA is not available", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
init_kwargs = dict(
|
||||
backend=args.backend,
|
||||
model_id=args.model_id,
|
||||
device=device,
|
||||
confidence_threshold=args.confidence,
|
||||
mask_threshold=args.mask_threshold,
|
||||
resolution=args.resolution,
|
||||
enable_segmentation=True,
|
||||
enable_inst_interactivity=False,
|
||||
)
|
||||
if args.checkpoint and os.path.isfile(args.checkpoint):
|
||||
init_kwargs["checkpoint_path"] = args.checkpoint
|
||||
init_kwargs["load_from_HF"] = False
|
||||
print(f"checkpoint: {args.checkpoint}")
|
||||
|
||||
print("\nLoading model...")
|
||||
sam = SamGeo3(**init_kwargs)
|
||||
|
||||
print("set_image...")
|
||||
sam.set_image(str(image_path))
|
||||
|
||||
print(f'generate_masks("{args.prompt}")...')
|
||||
gen_kwargs = {"min_size": args.min_size}
|
||||
if args.max_size is not None:
|
||||
gen_kwargs["max_size"] = args.max_size
|
||||
sam.generate_masks(args.prompt, **gen_kwargs)
|
||||
|
||||
n = len(sam.masks) if getattr(sam, "masks", None) is not None else 0
|
||||
if n == 0:
|
||||
print("No masks found. Try another prompt or lower --confidence.")
|
||||
return 0
|
||||
|
||||
scores = getattr(sam, "scores", None)
|
||||
if scores is not None and len(scores):
|
||||
try:
|
||||
vals = [float(s.item() if hasattr(s, "item") else s) for s in scores]
|
||||
print(f"scores (n={len(vals)}): min={min(vals):.3f} max={max(vals):.3f}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f"Saving masks -> {mask_path}")
|
||||
# PNG cannot store float score maps; save mask first, scores as .npy
|
||||
sam.save_masks(str(mask_path), unique=True)
|
||||
|
||||
scores = getattr(sam, "scores", None)
|
||||
if scores is not None and len(scores):
|
||||
import numpy as np
|
||||
|
||||
score_vals = np.array(
|
||||
[float(s.item() if hasattr(s, "item") else s) for s in scores],
|
||||
dtype=np.float32,
|
||||
)
|
||||
np.save(str(scores_path), score_vals)
|
||||
print(f"Saved per-object scores -> {scores_path}")
|
||||
|
||||
if not args.no_viz:
|
||||
try:
|
||||
print(f"Saving annotations -> {ann_path}")
|
||||
sam.show_anns(output=str(ann_path))
|
||||
except Exception as e:
|
||||
print(f"[WARN] show_anns failed: {e}")
|
||||
|
||||
print(f"\nDone. Found {n} object(s).")
|
||||
print(f" mask: {mask_path}")
|
||||
if scores_path.is_file():
|
||||
print(f" scores: {scores_path}")
|
||||
if ann_path.is_file():
|
||||
print(f" ann: {ann_path}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user