프로젝트 분리 이동

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
minsung
2026-05-20 14:28:27 +09:00
commit ccba1266b5
24 changed files with 7900 additions and 0 deletions

90
tools/merge_tiles_vis.py Normal file
View File

@@ -0,0 +1,90 @@
"""
6×8 (또는 임의 그리드) 타일 라벨을 원본 이미지 좌표로 병합해 시각화.
사용:
python tools/merge_tiles_vis.py \
--orig data/역사이미지/slope/DJI_20260306113838_0004.JPG \
--labels output/autolabel/tile6x8/labels \
--output output/autolabel/tile6x8/merged_vis.jpg \
--cols 6 --rows 8
"""
import argparse
import cv2
import numpy as np
from pathlib import Path
CLASS_NAMES = ["catenary_pole", "bracket"]
CLASS_COLORS = [(0, 200, 255), (255, 130, 0)]
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--orig", required=True)
parser.add_argument("--labels", required=True)
parser.add_argument("--output", required=True)
parser.add_argument("--cols", type=int, default=6)
parser.add_argument("--rows", type=int, default=8)
parser.add_argument("--alpha", type=float, default=0.3)
args = parser.parse_args()
buf = np.fromfile(args.orig, dtype=np.uint8)
img = cv2.imdecode(buf, cv2.IMREAD_COLOR)
H, W = img.shape[:2]
tw, th = W // args.cols, H // args.rows
label_dir = Path(args.labels)
counts = [0] * len(CLASS_NAMES)
for r in range(args.rows):
for c in range(args.cols):
label_file = label_dir / f"tile_r{r+1:02d}_c{c+1:02d}.txt"
if not label_file.exists():
continue
x0 = c * tw
y0 = r * th
tile_w = tw if c < args.cols - 1 else W - x0
tile_h = th if r < args.rows - 1 else H - y0
text = label_file.read_text(encoding="utf-8").strip()
if not text:
continue
for line in text.splitlines():
parts = line.split()
if not parts:
continue
cls_id = int(parts[0])
coords = list(map(float, parts[1:]))
pts = np.array(
[[coords[i] * tile_w + x0, coords[i + 1] * tile_h + y0]
for i in range(0, len(coords), 2)],
dtype=np.int32,
)
color = CLASS_COLORS[cls_id % len(CLASS_COLORS)]
overlay = img.copy()
cv2.fillPoly(overlay, [pts], color)
cv2.addWeighted(overlay, args.alpha, img, 1 - args.alpha, 0, img)
cv2.polylines(img, [pts], True, color, 3)
if cls_id < len(counts):
counts[cls_id] += 1
# 범례
for i, name in enumerate(CLASS_NAMES):
y = 30 + i * 50
cv2.rectangle(img, (15, y - 20), (55, y + 10), CLASS_COLORS[i], -1)
cv2.putText(img, f"{name}: {counts[i]}",
(65, y), cv2.FONT_HERSHEY_SIMPLEX, 1.5, CLASS_COLORS[i], 3)
total = sum(counts)
print(f"{total}" + ", ".join(f"{CLASS_NAMES[i]}={counts[i]}" for i in range(len(counts))))
# 4096px 이하로 미리보기 저장
scale = min(1.0, 4096 / max(H, W))
vis = cv2.resize(img, (int(W * scale), int(H * scale)))
out_path = Path(args.output)
out_path.parent.mkdir(parents=True, exist_ok=True)
cv2.imencode(out_path.suffix, vis)[1].tofile(str(out_path))
print(f"저장: {out_path}")
if __name__ == "__main__":
main()