#!/usr/bin/env python """검출이 화면을 얼마나 덮는지 잰다 — 미검출을 찾는 단계의 자다. prompt_stats.py 의 면적은 다각형 넓이의 단순 합이라 겹치면 부풀고 100%를 넘는다. 여기서는 마스크를 굽고 합집합을 세므로 "안 잡힌 곳"이 그대로 나온다. 무라벨% = 100 - (어떤 라벨이든 덮은 화소 / 전체 화소) 집합별 합집합도 같이 낸다. 집합끼리는 겹칠 수 있으므로 합이 100을 넘을 수 있다. 전체 해상도로 구우면 느리므로 --scale 로 줄여서 잰다 (기본 1/4). """ import argparse import glob import json import os import time import cv2 import numpy as np def load_groups(path): mapping, cur = {}, None for line in open(path, encoding="utf-8"): s = line.strip() if s.startswith("#"): b = s.lstrip("#").strip() cur = b[1:-1].strip() if b.startswith("[") and b.endswith("]") else None elif s and cur: mapping[s] = cur return mapping def main(): ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("--run-dir", required=True) ap.add_argument("--pattern", default="*_multi.json") ap.add_argument("--groups", required=True) ap.add_argument("--size", required=True, help="원본 영상 'W,H'") ap.add_argument("--scale", type=float, default=0.25) ap.add_argument("--out-json") args = ap.parse_args() W, H = (int(x) for x in args.size.split(",")) k = args.scale w, h = int(W * k), int(H * k) grp = load_groups(args.groups) files = sorted(glob.glob(os.path.join(args.run_dir, args.pattern))) if not files: raise FileNotFoundError(f"{args.pattern} 없음: {args.run_dir}") reps = sorted(set(grp.values())) + ["없음"] per_photo, gsum = [], {g: 0.0 for g in reps} px = w * h t0 = time.time() for i, p in enumerate(files, 1): with open(p, encoding="utf-8") as fh: segs = json.load(fh)["segments"] anym = np.zeros((h, w), np.uint8) masks = {} for s in segs: lb = s["label"] g = lb if lb in grp.values() else grp.get(lb, "없음") poly = (np.asarray(s["points"], np.float32) * k).astype(np.int32) cv2.fillPoly(anym, [poly], 255) m = masks.get(g) if m is None: m = masks[g] = np.zeros((h, w), np.uint8) cv2.fillPoly(m, [poly], 255) cov = np.count_nonzero(anym) / px row = { "photo": os.path.basename(p), "segments": len(segs), "covered": cov, "groups": {g: np.count_nonzero(m) / px for g, m in masks.items()}, } for g, v in row["groups"].items(): gsum[g] = gsum.get(g, 0.0) + v per_photo.append(row) print(f" [{i}/{len(files)}] {row['photo']:<44} 덮음 {100 * cov:5.1f}% " f"· 무라벨 {100 * (1 - cov):5.1f}% · 폴리곤 {len(segs):,}") cov = np.array([r["covered"] for r in per_photo]) print(f"\n사진 {len(files)}장 · {time.time() - t0:.0f}초 · 척도 1/{1 / k:.0f} " f"({w}x{h})") print(f"덮음 평균 {100 * cov.mean():.1f}% 중앙 {100 * np.median(cov):.1f}% " f"최소 {100 * cov.min():.1f}% 최대 {100 * cov.max():.1f}%") print(f"무라벨 평균 {100 * (1 - cov.mean()):.1f}% " f"최악 {100 * (1 - cov.min()):.1f}% ({per_photo[int(cov.argmin())]['photo']})") print(f"\n집합별 평균 합집합 화면%") for g, v in sorted(gsum.items(), key=lambda t: -t[1]): if v: print(f" {g:<20}{100 * v / len(files):>7.2f}%") print(f"\n무라벨 큰 사진 5장") for r in sorted(per_photo, key=lambda r: r["covered"])[:5]: print(f" {r['photo']:<44}{100 * (1 - r['covered']):>6.1f}%") if args.out_json: with open(args.out_json, "w", encoding="utf-8") as fh: json.dump({ "run_dir": os.path.abspath(args.run_dir), "pattern": args.pattern, "scale": k, "size": [W, H], "photos": len(files), "covered_mean": float(cov.mean()), "covered_min": float(cov.min()), "group_mean": {g: v / len(files) for g, v in gsum.items() if v}, "per_photo": per_photo, }, fh, ensure_ascii=False, indent=2) print(f"\n저장: {args.out_json}") if __name__ == "__main__": main()