Add source code, design assets, and CAD samples
This commit is contained in:
98
harness/quality_validator.py
Normal file
98
harness/quality_validator.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""품질 검증기 - 생성된 이미지가 기준을 충족하는지 자동 검사한다."""
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user