| import cv2
|
| import numpy as np
|
| import os
|
|
|
| class IdentityRiskAnalyzer:
|
| def __init__(self):
|
|
|
| cascade_path = cv2.data.haarcascades
|
| self.face_cascade = cv2.CascadeClassifier(os.path.join(cascade_path, 'haarcascade_frontalface_default.xml'))
|
| self.eye_cascade = cv2.CascadeClassifier(os.path.join(cascade_path, 'haarcascade_eye.xml'))
|
|
|
|
|
|
|
| def analyze(self, pil_image):
|
| """
|
| Analyzes a PIL Image for identity theft risk using OpenCV.
|
| """
|
|
|
| img_np = np.array(pil_image)
|
| if img_np.shape[2] == 4:
|
| img_np = cv2.cvtColor(img_np, cv2.COLOR_RGBA2RGB)
|
|
|
|
|
| img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
|
| gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
|
| h, w = gray.shape
|
|
|
| results = {
|
| "is_high_risk": False,
|
| "risk_score": 0.0,
|
| "details": [],
|
| "passed_criteria": []
|
| }
|
|
|
|
|
| faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
|
|
|
| if len(faces) == 0:
|
| results["details"].append("No face detected.")
|
| return results
|
|
|
| if len(faces) > 1:
|
| results["details"].append("Multiple faces detected.")
|
| return results
|
|
|
| results["passed_criteria"].append("Single face visible")
|
|
|
|
|
| (x, y, fw, fh) = faces[0]
|
| face_roi_gray = gray[y:y+fh, x:x+fw]
|
|
|
| face_ratio = (fw * fh) / (w * h)
|
| if face_ratio < 0.05:
|
| results["details"].append("Face too small relative to image.")
|
|
|
|
|
| eyes = self.eye_cascade.detectMultiScale(face_roi_gray)
|
|
|
| if len(eyes) >= 2:
|
|
|
| eyes = sorted(eyes, key=lambda e: e[0])
|
| (ex1, ey1, ew1, eh1) = eyes[0]
|
| (ex2, ey2, ew2, eh2) = eyes[-1]
|
|
|
|
|
| dy = (ey2 + eh2/2) - (ey1 + eh1/2)
|
| dx = (ex2 + ew2/2) - (ex1 + ew1/2)
|
| angle = np.degrees(np.arctan2(dy, dx))
|
|
|
| if abs(angle) > 10:
|
| results["details"].append(f"Face tilted (Angle: {angle:.1f}°).")
|
| else:
|
| results["passed_criteria"].append("Face vertically aligned")
|
|
|
|
|
| face_center_x = x + fw/2
|
| img_center_x = w/2
|
| deviation = abs(face_center_x - img_center_x)
|
|
|
| if deviation > w * 0.15:
|
| results["details"].append("Face not centered.")
|
| else:
|
| results["passed_criteria"].append("Face centered")
|
| else:
|
|
|
|
|
| results["details"].append("Eyes not clearly visible or aligned.")
|
|
|
|
|
| laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
|
| if laplacian_var < 50:
|
| results["details"].append("Image too blurry.")
|
| else:
|
| results["passed_criteria"].append("High resolution/sharp")
|
|
|
|
|
| margin = int(min(w, h) * 0.1)
|
| if margin < 1: margin = 1
|
|
|
|
|
| top_strip = gray[0:margin, :]
|
| bg_var = np.var(top_strip)
|
| bg_mean = np.mean(top_strip)
|
|
|
| if bg_var > 2500:
|
| results["details"].append("Background not plain/uniform.")
|
| elif bg_mean < 80:
|
| results["details"].append("Background too dark.")
|
| else:
|
| results["passed_criteria"].append("Plain light-colored background")
|
|
|
|
|
|
|
|
|
|
|
| results["passed_criteria"].append("Expression check skipped (Lenient)")
|
|
|
|
|
|
|
|
|
| if len(results["details"]) == 0:
|
| results["is_high_risk"] = True
|
| results["risk_score"] = 0.95
|
| else:
|
|
|
| results["is_high_risk"] = False
|
|
|
| return results
|
|
|