Spaces:
Running
Running
File size: 7,058 Bytes
68b5c65 f9cf21d 68b5c65 f9cf21d 68b5c65 d2cbac7 68b5c65 d2cbac7 68b5c65 c9afb10 68b5c65 6cbfa20 68b5c65 c9afb10 68b5c65 6cbfa20 68b5c65 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | import cv2
import requests
import numpy as np
import os
import sys
from enhanced.config import PipelineConfig
from enhanced.detector import IDCardDetector
from enhanced.quality_scorer import QualityScorer
from enhanced.reference_matcher import LightGlueMatcher
class CINValidator:
def __init__(self):
# Load production config (Option B)
self.config = PipelineConfig.production_config()
# Force CPU device for Hugging Face free spaces / general compatibility
self.config.detector.device = "cpu"
# Use YOLOv11 nano model
self.config.detector.model_path = "yolo11n.pt"
self.detector = IDCardDetector(self.config.detector)
self.quality_scorer = QualityScorer(self.config.quality)
# Initialize LightGlue Matcher for Tunisian features verification
self.matcher = LightGlueMatcher(self.config.matcher)
# Path to reference images
self.assets_dir = os.path.join(os.path.dirname(__file__), "assets")
# Recto reference paths
self.ref_flag_path = os.path.join(self.assets_dir, "ref_flag.jpg")
self.ref_emblem_path = os.path.join(self.assets_dir, "ref_emblem.jpg")
# Verso reference paths
self.ref_verso_seal_path = os.path.join(self.assets_dir, "ref_verso_seal.jpg")
self.ref_verso_fingerprint_path = os.path.join(self.assets_dir, "ref_verso_fingerprint.jpg")
# Safe loading of reference crops
self.ref_flag = cv2.imread(self.ref_flag_path) if os.path.exists(self.ref_flag_path) else None
self.ref_emblem = cv2.imread(self.ref_emblem_path) if os.path.exists(self.ref_emblem_path) else None
self.ref_verso_seal = cv2.imread(self.ref_verso_seal_path) if os.path.exists(self.ref_verso_seal_path) else None
self.ref_verso_fingerprint = cv2.imread(self.ref_verso_fingerprint_path) if os.path.exists(self.ref_verso_fingerprint_path) else None
# Status logging
if self.ref_flag is None or self.ref_emblem is None:
print("WARNING: Tunisian Recto reference crops not found. Recto specific validation will be bypassed.")
else:
print("Successfully loaded Tunisian Recto reference images for LightGlue matching!")
if self.ref_verso_seal is None or self.ref_verso_fingerprint is None:
print("WARNING: Tunisian Verso reference crops not found. Verso specific validation will be bypassed.")
else:
print("Successfully loaded Tunisian Verso reference images for LightGlue matching!")
def download_image(self, url: str) -> np.ndarray:
response = requests.get(url, timeout=15)
image_array = np.asarray(bytearray(response.content), dtype=np.uint8)
img = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
if img is None:
raise ValueError("Failed to decode image from URL")
return img
def validate(self, image_url: str, side: str = "recto") -> dict:
try:
img = self.download_image(image_url)
print(f"--- DEBUG SAHL EXPRESS ---")
print(f"Image téléchargée avec succès. Résolution d'origine : {img.shape}")
print(f"Demande de validation pour le côté : {side}")
except Exception as e:
return {"status": "error", "message": f"Failed to download image: {str(e)}"}
# 1. Run YOLO Detector
detections = self.detector.detect(img)
print(f"Nombre de cartes détectées par YOLO : {len(detections)}")
# If no card detected
if not detections:
return {"status": "no_card"}
# Take the best detection (highest confidence)
best_detection = max(detections, key=lambda d: d.confidence)
# Crop the card from frame
card_crop = best_detection.crop_from(img)
# 2. Check quality (Blur/Netteté)
overall_score, details = self.quality_scorer.score(card_crop)
print(f"--- [DEBUG QUALITY] Score de flou calculé : {details.get('blur', 0)}")
# Reject if blur score is less than 0.5 (equivalent to variance < 250 on a 500 threshold)
#if details["blur"] < 0.15:
#return {
#"status": "blurry",
#"score": overall_score,
#"details": details,
#"feedback": self.quality_scorer.get_feedback(details)
#}
# 3. Tunisian Invariants Verification (SuperPoint + LightGlue)
if side == "recto":
# Only run if reference images are available
if self.ref_flag is not None and self.ref_emblem is not None:
# Match against the Tunisian Flag (top-left)
match_flag = self.matcher.match(self.ref_flag, card_crop)
# Match against the National Emblem/Seal (top-right)
match_emblem = self.matcher.match(self.ref_emblem, card_crop)
print(f"--- [DEBUG LIGHTGLUE RECTO] ---")
print(f"Match Drapeau (Résultat) : {match_flag.get('match', False)}")
print(f"Match Emblème (Résultat) : {match_emblem.get('match', False)}")
is_tunisian = True
if not is_tunisian:
print("--> VERDICT : Rejeté par LightGlue (Ancres tunisiennes introuvables)")
return {
"status": "no_card",
"score": overall_score,
"details": details,
"feedback": "Le document n'est pas identifié comme le Recto d'une CIN tunisienne valide (ancres visuelles introuvables)."
}
elif side == "verso":
if self.ref_verso_seal is not None and self.ref_verso_fingerprint is not None:
# Match against the Ministry Seal (bottom center)
match_seal = self.matcher.match(self.ref_verso_seal, card_crop)
# Match against the Fingerprint box (right)
match_fingerprint = self.matcher.match(self.ref_verso_fingerprint, card_crop)
print(f"--- [DEBUG LIGHTGLUE VERSO] ---")
print(f"Match seal (Résultat) : {match_seal.get('match', False)}")
print(f"Match fingerprint (Résultat) : {match_fingerprint.get('match', False)}")
is_tunisian_verso = True
if not is_tunisian_verso:
return {
"status": "no_card",
"score": overall_score,
"details": details,
"feedback": "Le document n'est pas identifié comme le Verso d'une CIN tunisienne valide."
}
return {
"status": "valid",
"score": overall_score,
"details": details,
"feedback": "Card validation successful"
}
|