juras3k's picture
fix parameter in blocking function
db7dad9
Raw
History Blame Contribute Delete
2.76 kB
import numpy as np
import cv2
from dataclasses import dataclass
from typing import Optional
from src.geometry import FaceGeometry
@dataclass
class QualityReport:
passed: bool
score: float
warnings: list[str]
blocking: Optional[str] = None
blocking_code: Optional[str] = None
def _blocking(code: str, message: str) -> QualityReport:
return QualityReport(
passed=False,
score=0.0,
warnings=[],
blocking=message,
blocking_code=code,
)
def assess_quality(landmarks, img) -> QualityReport:
h, w = img.shape[:2]
geo = FaceGeometry(landmarks)
warnings = []
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
brightness = np.mean(gray)
if brightness < 40:
warnings.append("Image is too dark — try better lighting")
elif brightness > 230:
warnings.append("Image is overexposed — try softer lighting")
face_width_px = geo.face_width()
min_face_px = min(w, h) * 0.15
if face_width_px < min_face_px:
return _blocking(
"FACE_TOO_SMALL",
"Face too small — move closer to the camera",
)
nose_x = geo.nose()[0]
left_x = geo.left_eye()[0]
right_x = geo.right_eye()[0]
eye_mid_x = (left_x + right_x) / 2
eye_dist = abs(left_x - right_x)
yaw_offset = abs(nose_x - eye_mid_x) / eye_dist if eye_dist > 0 else 0
if yaw_offset > 0.35:
return _blocking(
"FACE_ROTATED",
"Please face the camera directly",
)
elif yaw_offset > 0.25:
warnings.append("Head is turned — results may be less accurate")
chin_y = geo.chin()[1]
forehead_y = geo.forehead_top()[1]
face_h = abs(chin_y - forehead_y)
nose_y = geo.nose()[1]
expected_y = (chin_y + forehead_y) / 2
pitch_offset = abs(nose_y - expected_y) / face_h if face_h > 0 else 0
if pitch_offset > 0.25:
return _blocking(
"FACE_TILTED",
"Head is tilted too much — try a straight-on photo",
)
elif pitch_offset > 0.15:
warnings.append("Head is tilted up or down - try a straight-on photo")
left_cheek_x = geo.left_cheek()[0]
right_cheek_x = geo.right_cheek()[0]
mid_x = (left_cheek_x + right_cheek_x) / 2
nose_offset = abs(geo.nose()[0] - mid_x) / face_width_px
if nose_offset > 0.20:
return _blocking(
"POOR_ALIGNMENT",
"Could not align landmarks — try a clearer, well-lit photo",
)
if nose_offset > 0.12:
warnings.append("Unusual landmark alignment - lighting or angle may be off")
score = max(0.1, 1.0 - len(warnings) * 0.2)
return QualityReport(passed=True, score=round(score, 2), warnings=warnings)