ai / identity_risk.py
maddyrox's picture
Upload 23 files
245a4de verified
import cv2
import numpy as np
import os
class IdentityRiskAnalyzer:
def __init__(self):
# Load Haar Cascades
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'))
# Smile cascade is often less reliable, but we can try if available, or skip expression strictness.
# self.smile_cascade = cv2.CascadeClassifier(os.path.join(cascade_path, 'haarcascade_smile.xml'))
def analyze(self, pil_image):
"""
Analyzes a PIL Image for identity theft risk using OpenCV.
"""
# Convert PIL to CV2 (BGR)
img_np = np.array(pil_image)
if img_np.shape[2] == 4: # RGBA to RGB
img_np = cv2.cvtColor(img_np, cv2.COLOR_RGBA2RGB)
# OpenCV expects BGR
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": []
}
# 1. Face Visibility & Count
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")
# Get Face ROI
(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.")
# 2. Alignment (Eyes)
eyes = self.eye_cascade.detectMultiScale(face_roi_gray)
if len(eyes) >= 2:
# Sort by x position to get left and right eye
eyes = sorted(eyes, key=lambda e: e[0])
(ex1, ey1, ew1, eh1) = eyes[0]
(ex2, ey2, ew2, eh2) = eyes[-1] # Farthest right
# Check Angle (Roll)
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")
# Check Centering (Yaw/Translation)
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:
# Can't see both eyes -> Maybe side profile or hair?
# For High Risk ID, we NEED eyes visible.
results["details"].append("Eyes not clearly visible or aligned.")
# 3. Image Quality (Blur)
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")
# 4. Lighting & Background
margin = int(min(w, h) * 0.1)
if margin < 1: margin = 1
# Check top corners for background uniformity
top_strip = gray[0:margin, :]
bg_var = np.var(top_strip)
bg_mean = np.mean(top_strip)
if bg_var > 2500: # Relaxed threshold for "real world" plain walls
results["details"].append("Background not plain/uniform.")
elif bg_mean < 80: # Too dark
results["details"].append("Background too dark.")
else:
results["passed_criteria"].append("Plain light-colored background")
# 5. Expression (Heuristic)
# Without landmarks, checking "neutral" is hard.
# But we can skip strict Smile check as user requested leniency.
# We assume if it passes eye alignment and is frontal, it's risky enough.
results["passed_criteria"].append("Expression check skipped (Lenient)")
# Final Scoring
# High Risk if NO details (failures).
if len(results["details"]) == 0:
results["is_high_risk"] = True
results["risk_score"] = 0.95
else:
# Special Case: If only Background failed? No, ID photo needs plain BG.
results["is_high_risk"] = False
return results