Spaces:
Sleeping
Sleeping
Update verifier.py
Browse files- verifier.py +76 -11
verifier.py
CHANGED
|
@@ -7,14 +7,30 @@ warnings.filterwarnings('ignore')
|
|
| 7 |
|
| 8 |
class AIFRKTPVerification:
|
| 9 |
def __init__(self):
|
|
|
|
|
|
|
|
|
|
| 10 |
print("π Initializing AIFR KTP Verification System...")
|
|
|
|
| 11 |
self.app = FaceAnalysis(name='buffalo_l',
|
| 12 |
root='/tmp',
|
| 13 |
providers=['CPUExecutionProvider'])
|
| 14 |
self.app.prepare(ctx_id=0, det_size=(640, 640))
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def preprocess_ktp_image(self, image):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
|
| 19 |
l, a, b = cv2.split(lab)
|
| 20 |
a = cv2.normalize(a, None, 0, 255, cv2.NORM_MINMAX)
|
|
@@ -27,38 +43,87 @@ class AIFRKTPVerification:
|
|
| 27 |
return corrected
|
| 28 |
|
| 29 |
def extract_age_invariant_features(self, image):
|
|
|
|
|
|
|
|
|
|
| 30 |
faces = self.app.get(image)
|
| 31 |
if len(faces) == 0:
|
| 32 |
print("β οΈ No face detected!")
|
| 33 |
return None, None
|
|
|
|
| 34 |
face = faces[0]
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
def verify_with_images(self, ktp_image, selfie_image):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
print("\nπ§ Preprocessing images for API call...")
|
| 42 |
ktp_processed = self.preprocess_ktp_image(ktp_image)
|
| 43 |
selfie_processed = cv2.normalize(selfie_image, None, 0, 255, cv2.NORM_MINMAX)
|
| 44 |
|
| 45 |
print("π§ Extracting age-invariant features...")
|
| 46 |
-
ktp_embedding,
|
| 47 |
-
selfie_embedding,
|
| 48 |
|
| 49 |
if ktp_embedding is None or selfie_embedding is None:
|
| 50 |
return {'error': 'Could not detect a face in one or both images.'}
|
| 51 |
|
| 52 |
print("π Calculating similarity...")
|
| 53 |
-
similarity, deep_similarity = self.calculate_similarity(
|
|
|
|
|
|
|
| 54 |
|
| 55 |
normal_threshold = self.thresholds['normal']
|
| 56 |
verified = bool(similarity > normal_threshold)
|
| 57 |
|
| 58 |
-
print(f"β
Verification complete. Score: {similarity:.4f}")
|
| 59 |
return {
|
| 60 |
'verified': verified,
|
| 61 |
-
'similarity_score': float(similarity),
|
| 62 |
-
'deep_feature_similarity': float(deep_similarity),
|
| 63 |
'threshold': normal_threshold
|
| 64 |
-
}
|
|
|
|
| 7 |
|
| 8 |
class AIFRKTPVerification:
|
| 9 |
def __init__(self):
|
| 10 |
+
"""
|
| 11 |
+
Initialize Age-Invariant Face Recognition system specifically for KTP verification
|
| 12 |
+
"""
|
| 13 |
print("π Initializing AIFR KTP Verification System...")
|
| 14 |
+
# Initialize InsightFace with ArcFace model (best for age-invariant features)
|
| 15 |
self.app = FaceAnalysis(name='buffalo_l',
|
| 16 |
root='/tmp',
|
| 17 |
providers=['CPUExecutionProvider'])
|
| 18 |
self.app.prepare(ctx_id=0, det_size=(640, 640))
|
| 19 |
+
# Thresholds specifically calibrated for ID-to-selfie comparison
|
| 20 |
+
self.thresholds = {
|
| 21 |
+
'strict': 0.35,
|
| 22 |
+
'normal': 0.45,
|
| 23 |
+
'relaxed': 0.55,
|
| 24 |
+
'very_relaxed': 0.65
|
| 25 |
+
}
|
| 26 |
|
| 27 |
def preprocess_ktp_image(self, image):
|
| 28 |
+
"""
|
| 29 |
+
Special preprocessing for Indonesian KTP photos
|
| 30 |
+
- Remove red/orange tint
|
| 31 |
+
- Enhance contrast
|
| 32 |
+
- Normalize lighting
|
| 33 |
+
"""
|
| 34 |
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
|
| 35 |
l, a, b = cv2.split(lab)
|
| 36 |
a = cv2.normalize(a, None, 0, 255, cv2.NORM_MINMAX)
|
|
|
|
| 43 |
return corrected
|
| 44 |
|
| 45 |
def extract_age_invariant_features(self, image):
|
| 46 |
+
"""
|
| 47 |
+
Extracts both deep embedding and geometric features from the face.
|
| 48 |
+
"""
|
| 49 |
faces = self.app.get(image)
|
| 50 |
if len(faces) == 0:
|
| 51 |
print("β οΈ No face detected!")
|
| 52 |
return None, None
|
| 53 |
+
|
| 54 |
face = faces[0]
|
| 55 |
+
embedding = face.normed_embedding # 512-dimensional embedding
|
| 56 |
+
landmarks = face.kps # Keypoints for geometric features
|
| 57 |
+
|
| 58 |
+
geometric_features = None
|
| 59 |
+
if landmarks is not None and len(landmarks) >= 5:
|
| 60 |
+
try:
|
| 61 |
+
# Eye distance ratio (doesn't change much with age)
|
| 62 |
+
eye_distance = np.linalg.norm(landmarks[0] - landmarks[1])
|
| 63 |
+
# Nose to eye center distance
|
| 64 |
+
eye_center = (landmarks[0] + landmarks[1]) / 2
|
| 65 |
+
nose_distance = np.linalg.norm(landmarks[2] - eye_center)
|
| 66 |
+
# Face width estimation (using jawline points if available)
|
| 67 |
+
face_width = np.linalg.norm(landmarks[3] - landmarks[4])
|
| 68 |
+
|
| 69 |
+
# Create a stable geometric feature vector
|
| 70 |
+
if face_width > 0 and nose_distance > 0:
|
| 71 |
+
geometric_features = np.array([
|
| 72 |
+
eye_distance / face_width,
|
| 73 |
+
nose_distance / face_width,
|
| 74 |
+
eye_distance / nose_distance
|
| 75 |
+
])
|
| 76 |
+
except Exception as e:
|
| 77 |
+
print(f"Could not calculate geometric features: {e}")
|
| 78 |
+
geometric_features = None
|
| 79 |
|
| 80 |
+
return embedding, geometric_features
|
| 81 |
+
|
| 82 |
+
def calculate_similarity(self, embed1, embed2, geom1=None, geom2=None):
|
| 83 |
+
"""
|
| 84 |
+
Calculates similarity using a weighted average of deep and geometric features.
|
| 85 |
+
"""
|
| 86 |
+
# Deep feature similarity (main metric)
|
| 87 |
+
deep_similarity = 1 - cosine(embed1, embed2)
|
| 88 |
+
|
| 89 |
+
# Geometric similarity (supplementary)
|
| 90 |
+
if geom1 is not None and geom2 is not None:
|
| 91 |
+
geom_similarity = 1 - cosine(geom1, geom2)
|
| 92 |
+
# Weighted combination (80% deep, 20% geometric)
|
| 93 |
+
final_similarity = 0.8 * deep_similarity + 0.2 * geom_similarity
|
| 94 |
+
else:
|
| 95 |
+
final_similarity = deep_similarity
|
| 96 |
+
|
| 97 |
+
return final_similarity, deep_similarity
|
| 98 |
|
| 99 |
def verify_with_images(self, ktp_image, selfie_image):
|
| 100 |
+
"""
|
| 101 |
+
Main verification function that accepts image objects instead of file paths.
|
| 102 |
+
This is the method the API will call.
|
| 103 |
+
"""
|
| 104 |
print("\nπ§ Preprocessing images for API call...")
|
| 105 |
ktp_processed = self.preprocess_ktp_image(ktp_image)
|
| 106 |
selfie_processed = cv2.normalize(selfie_image, None, 0, 255, cv2.NORM_MINMAX)
|
| 107 |
|
| 108 |
print("π§ Extracting age-invariant features...")
|
| 109 |
+
ktp_embedding, ktp_geom = self.extract_age_invariant_features(ktp_processed)
|
| 110 |
+
selfie_embedding, selfie_geom = self.extract_age_invariant_features(selfie_processed)
|
| 111 |
|
| 112 |
if ktp_embedding is None or selfie_embedding is None:
|
| 113 |
return {'error': 'Could not detect a face in one or both images.'}
|
| 114 |
|
| 115 |
print("π Calculating similarity...")
|
| 116 |
+
similarity, deep_similarity = self.calculate_similarity(
|
| 117 |
+
ktp_embedding, selfie_embedding, ktp_geom, selfie_geom
|
| 118 |
+
)
|
| 119 |
|
| 120 |
normal_threshold = self.thresholds['normal']
|
| 121 |
verified = bool(similarity > normal_threshold)
|
| 122 |
|
| 123 |
+
print(f"β
Verification complete. Weighted Score: {similarity:.4f}")
|
| 124 |
return {
|
| 125 |
'verified': verified,
|
| 126 |
+
'similarity_score': float(similarity), # This is the weighted score
|
| 127 |
+
'deep_feature_similarity': float(deep_similarity), # This is the pure AI score
|
| 128 |
'threshold': normal_threshold
|
| 129 |
+
}
|