99 lines
3.5 KiB
Python
99 lines
3.5 KiB
Python
"""품질 검증기 - 생성된 이미지가 기준을 충족하는지 자동 검사한다."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
import numpy as np
|
|
|
|
try:
|
|
import cv2
|
|
from PIL import Image
|
|
except ImportError:
|
|
raise ImportError("opencv-python, Pillow이 필요합니다: pip install opencv-python Pillow")
|
|
|
|
|
|
@dataclass
|
|
class ValidationResult:
|
|
passed: bool
|
|
score: float # 0.0 ~ 1.0 종합 품질 점수
|
|
resolution_ok: bool
|
|
sharpness_ok: bool
|
|
color_diversity_ok: bool
|
|
messages: List[str]
|
|
|
|
@property
|
|
def summary(self) -> str:
|
|
status = "PASS" if self.passed else "FAIL"
|
|
return f"[{status}] score={self.score:.2f} | " + " | ".join(self.messages)
|
|
|
|
|
|
class QualityValidator:
|
|
"""이미지 품질을 자동으로 검증한다."""
|
|
|
|
def __init__(
|
|
self,
|
|
min_resolution: int = 2048,
|
|
sharpness_threshold: float = 100.0,
|
|
color_diversity_threshold: float = 0.15,
|
|
):
|
|
self.min_resolution = min_resolution
|
|
self.sharpness_threshold = sharpness_threshold
|
|
self.color_diversity_threshold = color_diversity_threshold
|
|
|
|
def validate(self, image_path: Path) -> ValidationResult:
|
|
if not image_path.exists():
|
|
return ValidationResult(
|
|
passed=False, score=0.0,
|
|
resolution_ok=False, sharpness_ok=False, color_diversity_ok=False,
|
|
messages=["파일이 존재하지 않음"],
|
|
)
|
|
|
|
img_pil = Image.open(image_path).convert("RGB")
|
|
img_cv = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
|
|
|
|
resolution_ok, res_msg = self._check_resolution(img_pil)
|
|
sharpness_ok, sharp_score, sharp_msg = self._check_sharpness(img_cv)
|
|
color_ok, color_msg = self._check_color_diversity(img_cv)
|
|
|
|
scores = [
|
|
1.0 if resolution_ok else 0.0,
|
|
min(sharp_score / (self.sharpness_threshold * 3), 1.0),
|
|
1.0 if color_ok else 0.3,
|
|
]
|
|
overall_score = float(np.mean(scores))
|
|
passed = resolution_ok and sharpness_ok and color_ok
|
|
|
|
return ValidationResult(
|
|
passed=passed,
|
|
score=overall_score,
|
|
resolution_ok=resolution_ok,
|
|
sharpness_ok=sharpness_ok,
|
|
color_diversity_ok=color_ok,
|
|
messages=[res_msg, sharp_msg, color_msg],
|
|
)
|
|
|
|
def _check_resolution(self, img: Image.Image):
|
|
w, h = img.size
|
|
ok = w >= self.min_resolution and h >= self.min_resolution
|
|
msg = f"해상도={w}x{h} {'OK' if ok else f'(최소 {self.min_resolution} 필요)'}"
|
|
return ok, msg
|
|
|
|
def _check_sharpness(self, img_cv):
|
|
gray = cv2.cvtColor(img_cv, cv2.COLOR_BGR2GRAY)
|
|
lap_var = cv2.Laplacian(gray, cv2.CV_64F).var()
|
|
ok = lap_var >= self.sharpness_threshold
|
|
msg = f"선명도={lap_var:.1f} {'OK' if ok else f'(임계값 {self.sharpness_threshold})'}"
|
|
return ok, float(lap_var), msg
|
|
|
|
def _check_color_diversity(self, img_cv):
|
|
"""색상 다양성 검사 - 단색 평면 출력을 탐지한다."""
|
|
hsv = cv2.cvtColor(img_cv, cv2.COLOR_BGR2HSV)
|
|
s_channel = hsv[:, :, 1].astype(np.float32) / 255.0
|
|
mean_saturation = float(s_channel.mean())
|
|
ok = mean_saturation >= self.color_diversity_threshold
|
|
msg = f"색상다양성={mean_saturation:.3f} {'OK' if ok else f'(임계값 {self.color_diversity_threshold})'}"
|
|
return ok, msg
|