Files
minsung 4e5173522d @
feat: SAM3.1 다중 프롬프트 배치 검출 및 라벨 병합 후처리

sam3_multi_prompt.py: 프롬프트 N개를 forward 1회에 배치 처리한다.
서버(프롬프트당 forward 1회) 대비 forward 횟수가 1/N로 줄고, 이미지
임베딩과 텍스트 인코딩을 타일 전체에서 재사용한다. RTX 3060 12GB
기준 배치 16이 최적(32 이상은 VRAM 압박으로 4배 이상 느려짐).

타일보다 큰 대상(숲, 도로, 주차장)은 타일 경계에서 잘려 사각형
마스크가 되므로 --wide-prompts 로 지정해 이미지 전체를 한 장으로
처리한다.

merge_labels.py: 검출 결과 후처리. 같은 병합 그룹에 속한 라벨끼리
외곽선이 gap px 이내로 인접하면 하나로 합치고 대표 라벨을 붙인다
(building + building rooftop + blue roof -> building). 병합 전
claim 규칙으로 소유권을 재배정해, 차량 위에 잡힌 *roof 폴리곤이
건물이 아니라 차량에 합쳐지도록 한다.

make_viewer.py: 라벨별 on/off 가능한 단독 HTML 뷰어 생성.
프롬프트 파일의 그룹 주석 기준으로 라벨을 묶어 나열하고, 줌/팬과
선택 상태 저장을 지원한다.

cut_tiles.py: R{행}C{열} 격자 오버레이 + 원본 배율 타일 저장.

전역 규칙 반영:
- CUDA 사용 불가 시 CPU로 폴백하지 않고 즉시 에러
- set_per_process_memory_fraction 으로 드라이버의 시스템 메모리
  폴백(10배 이상 느려짐) 전에 OOM 발생
- 체크포인트/BPE 경로 하드코딩 제거. CLI 인자 -> 환경변수 ->
  sam31server 설정파일 순으로 해결

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@
2026-08-20 10:05:23 +09:00

76 lines
2.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
이미지를 cols×rows 타일로 잘라 파일로 저장하고, R{행}C{열} 라벨을 찍은 격자 오버레이도 생성.
사용법:
python tools/cut_tiles.py --input <이미지> --cols 9 --rows 6
출력:
output/tiles/<이미지명>/R1C1.jpg ... (원본 배율)
output/tiles/<이미지명>_grid.jpg (격자 오버레이, 최대 4096px)
"""
import argparse
from pathlib import Path
import cv2
import numpy as np
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--input", required=True)
ap.add_argument("--outdir", default="output/tiles")
ap.add_argument("--cols", type=int, default=9)
ap.add_argument("--rows", type=int, default=6)
args = ap.parse_args()
img_path = Path(args.input)
buf = np.fromfile(str(img_path), dtype=np.uint8)
img = cv2.imdecode(buf, cv2.IMREAD_COLOR)
if img is None:
print(f"이미지 로드 실패: {img_path}")
return
H, W = img.shape[:2]
base_w = W / args.cols
base_h = H / args.rows
print(f"이미지 {W}×{H}{args.cols}×{args.rows} 타일 {base_w:.0f}×{base_h:.0f}")
tile_dir = Path(args.outdir) / img_path.stem
tile_dir.mkdir(parents=True, exist_ok=True)
vis = img.copy()
font_scale = base_h / 200.0
thickness = max(2, int(font_scale * 2))
for r in range(args.rows):
for c in range(args.cols):
x0, x1 = int(c * base_w), int((c + 1) * base_w)
y0, y1 = int(r * base_h), int((r + 1) * base_h)
name = f"R{r+1}C{c+1}"
cv2.imencode(".jpg", img[y0:y1, x0:x1],
[cv2.IMWRITE_JPEG_QUALITY, 95])[1].tofile(
str(tile_dir / f"{name}.jpg"))
cv2.rectangle(vis, (x0, y0), (x1, y1), (0, 200, 255), 4)
(tw, th), _ = cv2.getTextSize(name, cv2.FONT_HERSHEY_SIMPLEX,
font_scale, thickness)
tx, ty = x0 + 12, y0 + th + 12
cv2.rectangle(vis, (tx - 6, ty - th - 6), (tx + tw + 6, ty + 8),
(0, 0, 0), -1)
cv2.putText(vis, name, (tx, ty), cv2.FONT_HERSHEY_SIMPLEX,
font_scale, (0, 200, 255), thickness, cv2.LINE_AA)
h, w = vis.shape[:2]
if max(h, w) > 4096:
s = 4096 / max(h, w)
vis = cv2.resize(vis, (int(w * s), int(h * s)))
grid_path = Path(args.outdir) / f"{img_path.stem}_grid.jpg"
cv2.imencode(".jpg", vis, [cv2.IMWRITE_JPEG_QUALITY, 92])[1].tofile(str(grid_path))
print(f"타일 {args.cols * args.rows}개 → {tile_dir}")
print(f"격자 오버레이 → {grid_path}")
if __name__ == "__main__":
main()