AhmedAdamu commited on
Commit
a3fcb74
·
verified ·
1 Parent(s): 1890212

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +225 -180
app.py CHANGED
@@ -1,199 +1,244 @@
1
- # SecureFace ID – FIXED VERSION
2
  import os
3
  import cv2
4
  import numpy as np
5
  import gradio as gr
 
6
  from ultralytics import YOLO
7
- from huggingface_hub import hf_hub_download
8
- import insightface
9
  from insightface.app import FaceAnalysis
10
- import faiss
11
-
12
- # ==================== PATHS ====================
13
- KNOWN_EMBS_PATH = "known_embeddings.npy"
14
- KNOWN_NAMES_PATH = "known_names.npy"
15
-
16
- # ==================== MODELS ====================
17
- # Load YOLO for fast detection
18
- print("Loading YOLOv8...")
19
- model_path = hf_hub_download(repo_id="arnabdhar/YOLOv8-Face-Detection", filename="model.pt")
20
- detector = YOLO(model_path)
21
-
22
- # Load InsightFace for embedding extraction
23
- print("Loading InsightFace...")
24
- recognizer = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider'])
25
- recognizer.prepare(ctx_id=0, det_size=(640,640))
26
-
27
- # FAISS index setup
28
- index = faiss.IndexHNSWFlat(512, 32)
29
- index.hnsw.efSearch = 16
30
- known_names = []
31
-
32
- # Load database at startup
33
- if os.path.exists(KNOWN_EMBS_PATH) and os.path.exists(KNOWN_NAMES_PATH):
34
- try:
35
- embs = np.load(KNOWN_EMBS_PATH)
36
- known_names = np.load(KNOWN_NAMES_PATH, allow_pickle=True).tolist()
37
- if embs.shape[0] > 0:
38
- index.add(embs.astype('float32'))
39
- print(f"✅ Loaded {len(known_names)} identities from disk.")
40
- except Exception as e:
41
- print(f"⚠️ Database Error: {e}")
42
 
43
- # ==================== PROCESS FRAME ====================
44
- def process_frame(frame, blur_type="gaussian", intensity=50, expand=1.4, show_labels=True):
45
- if frame is None: return None
46
-
47
- img = frame.copy()
48
- h, w = img.shape[:2]
49
-
50
- # 1. Detect Faces with YOLO
51
- results = detector(img, conf=0.4, verbose=False)[0]
52
 
53
- for box in results.boxes:
54
- x1, y1, x2, y2 = map(int, box.xyxy[0])
 
55
 
56
- # Calculate expanded crop for recognition context
57
- ew = int((x2-x1)*(expand-1)/2)
58
- eh = int((y2-y1)*(expand-1)/2)
59
- cx1 = max(0, x1-ew); cy1 = max(0, y1-eh)
60
- cx2 = min(w, x2+ew); cy2 = min(h, y2+eh)
61
-
62
- # 2. Recognition Logic
63
- # We crop the face and convert to BGR (InsightFace expects BGR)
64
- crop = cv2.cvtColor(img[cy1:cy2, cx1:cx2], cv2.COLOR_RGB2BGR)
65
 
66
- # Run InsightFace on the crop
67
- faces = recognizer.get(crop)
68
-
69
- name = "Unknown"
70
- match_found = False
71
 
72
- if faces and index.ntotal > 0:
73
- # Take the largest face in the crop (usually the correct one)
74
- main_face = max(faces, key=lambda x: (x.bbox[2]-x.bbox[0]) * (x.bbox[3]-x.bbox[1]))
75
-
76
- emb = main_face.normed_embedding.reshape(1, -1).astype('float32')
77
- D, I = index.search(emb, k=1)
78
-
79
- # Threshold: Lower is better for L2 distance.
80
- # 0.8 is a safe balance; 0.6 is very strict.
81
- if D[0][0] < 0.8:
82
- name = known_names[I[0][0]]
83
- match_found = True
84
-
85
- # 3. Blur Logic (Privacy)
86
- if blur_type != "none":
87
- face_region = img[y1:y2, x1:x2] # Blur only the tight box, not expanded
88
- if blur_type == "gaussian":
89
- k = max(21, int(min(x2-x1, y2-y1) * intensity / 100) | 1)
90
- blurred = cv2.GaussianBlur(face_region, (k,k), 0)
91
- img[y1:y2, x1:x2] = blurred
92
- elif blur_type == "pixelate":
93
- # Map intensity 10-100 to pixel block size 20-3
94
- block_size = max(3, int(20 * (1 - intensity/120)))
95
- small = cv2.resize(face_region, (max(1, (x2-x1)//block_size), max(1, (y2-y1)//block_size)))
96
- blurred = cv2.resize(small, (x2-x1, y2-y1), interpolation=cv2.INTER_NEAREST)
97
- img[y1:y2, x1:x2] = blurred
98
- elif blur_type == "solid":
99
- cv2.rectangle(img, (x1,y1), (x2,y2), (0,0,0), -1)
100
-
101
- # 4. Draw Labels (Identity)
102
- if show_labels:
103
- color = (0, 255, 0) if match_found else (0, 0, 255) # Green for known, Red for unknown
104
-
105
- # Draw box
106
- cv2.rectangle(img, (x1,y1), (x2,y2), color, 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
- # Draw label background and text
109
- label_str = name
110
- (tw, th), _ = cv2.getTextSize(label_str, cv2.FONT_HERSHEY_DUPLEX, 0.8, 2)
111
- cv2.rectangle(img, (x1, y1-30), (x1+tw+10, y1), color, -1)
112
- cv2.putText(img, label_str, (x1+5, y1-8), cv2.FONT_HERSHEY_DUPLEX, 0.8, (255,255,255), 2)
113
-
114
- return img
115
-
116
- # ==================== ENROLL FUNCTION ====================
117
- def enroll_person(name, face_image):
118
- global index, known_names
119
-
120
- # FIX 1: Proper None check for numpy array
121
- if face_image is None or not name or not name.strip():
122
- return "⚠️ Error: Please provide both a name and a photo."
123
 
124
- # Convert RGB (Gradio) to BGR (OpenCV/InsightFace)
125
- bgr = cv2.cvtColor(face_image, cv2.COLOR_RGB2BGR)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
- faces = recognizer.get(bgr)
 
 
 
 
 
128
 
129
- if not faces:
130
- return "⚠️ Error: No face detected. Please use a clear frontal photo."
131
-
132
- # Pick the largest face if multiple are found
133
- main_face = max(faces, key=lambda x: (x.bbox[2]-x.bbox[0]) * (x.bbox[3]-x.bbox[1]))
134
- new_emb = main_face.normed_embedding.reshape(1, 512)
135
-
136
- # Load existing data to ensure sync
137
- if os.path.exists(KNOWN_EMBS_PATH):
138
- embs = np.load(KNOWN_EMBS_PATH)
139
- # Ensure 2D array
140
- if len(embs.shape) == 1: embs = embs.reshape(1, -1)
141
- else:
142
- embs = np.empty((0,512))
143
-
144
- # Append new data
145
- embs = np.vstack([embs, new_emb])
146
- known_names.append(name)
147
-
148
- # Save to disk
149
- np.save(KNOWN_EMBS_PATH, embs)
150
- np.save(KNOWN_NAMES_PATH, np.array(known_names))
151
-
152
- # Rebuild Index
153
- index.reset()
154
- index.add(embs.astype('float32'))
155
-
156
- return f"✅ Success: **{name}** has been enrolled!"
157
-
158
- # ==================== GRADIO UI ====================
159
- with gr.Blocks(title="SecureFace ID", theme=gr.themes.Soft()) as demo:
160
- gr.Markdown("# 🛡️ SecureFace ID")
161
-
162
- with gr.Tab("📹 Live Surveillance"):
163
- with gr.Row():
164
- with gr.Column():
165
- cam = gr.Image(sources=["webcam"], streaming=True, label="Live Feed", height=450)
166
- with gr.Column():
167
- output = gr.Image(label="Protected Stream", height=450)
168
-
169
- with gr.Row():
170
- blur_type = gr.Radio(["gaussian", "pixelate", "solid", "none"], value="pixelate", label="Privacy Filter")
171
- intensity = gr.Slider(1, 100, 80, label="Blur Intensity")
172
- expand = gr.Slider(1.0, 2.0, 1.3, label="Context Area")
173
- show_names = gr.Checkbox(True, label="Show IDs Overlay")
174
 
175
- # Connect the stream
176
- cam.stream(process_frame, [cam, blur_type, intensity, expand, show_names], output)
177
-
178
- with gr.Tab("👤 Enroll Person"):
179
- with gr.Row():
180
- with gr.Column():
181
- name_input = gr.Textbox(label="Full Name / ID", placeholder="e.g. Agent Smith")
182
- photo_input = gr.Image(label="Reference Photo", sources=["upload", "webcam"], height=300)
183
- enroll_btn = gr.Button("Add to Database", variant="primary")
184
- with gr.Column():
185
- enroll_status = gr.Markdown("### Status: Waiting...")
186
- db_view = gr.Markdown()
187
-
188
- # Database viewer updater
189
- def get_db_status():
190
- if not known_names: return "Database is empty."
191
- return f"### 📂 Registered Users ({len(known_names)}):\n" + "\n".join([f"- {n}" for n in list(set(known_names))])
192
-
193
- # Event wiring
194
- enroll_btn.click(enroll_person, inputs=[name_input, photo_input], outputs=enroll_status)
195
- enroll_btn.click(get_db_status, outputs=db_view)
196
- demo.load(get_db_status, outputs=db_view)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
 
198
  if __name__ == "__main__":
199
  demo.launch()
 
 
1
  import os
2
  import cv2
3
  import numpy as np
4
  import gradio as gr
5
+ import json
6
  from ultralytics import YOLO
 
 
7
  from insightface.app import FaceAnalysis
8
+ from huggingface_hub import hf_hub_download
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ # ==========================================
11
+ # CONFIGURATION & STATE MANAGEMENT
12
+ # ==========================================
13
+ DB_FILE = "face_db.json"
14
+ EMBEDDINGS_FILE = "face_embeddings.npy"
 
 
 
 
15
 
16
+ class FaceSystem:
17
+ def __init__(self):
18
+ print("🚀 Initializing AI Models...")
19
 
20
+ # 1. Load YOLOv8-Face (Best for detection speed/accuracy)
21
+ # We download a specific version trained for faces
22
+ model_path = hf_hub_download(repo_id="arnabdhar/YOLOv8-Face-Detection", filename="model.pt")
23
+ self.detector = YOLO(model_path)
 
 
 
 
 
24
 
25
+ # 2. Load InsightFace (Best for recognition accuracy)
26
+ # 'buffalo_l' is the large model (higher accuracy). Use 'buffalo_s' if you need more speed.
27
+ self.recognizer = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider'])
28
+ self.recognizer.prepare(ctx_id=0, det_size=(640, 640))
 
29
 
30
+ # 3. Load Database
31
+ self.known_names = []
32
+ self.known_embeddings = np.empty((0, 512))
33
+ self.load_db()
34
+ print("✅ System Ready.")
35
+
36
+ def load_db(self):
37
+ """Load names and vector embeddings from disk."""
38
+ if os.path.exists(DB_FILE) and os.path.exists(EMBEDDINGS_FILE):
39
+ with open(DB_FILE, 'r') as f:
40
+ self.known_names = json.load(f)
41
+ self.known_embeddings = np.load(EMBEDDINGS_FILE)
42
+ print(f"📂 Loaded {len(self.known_names)} identities.")
43
+ else:
44
+ print("📂 Database empty. Starting fresh.")
45
+
46
+ def save_db(self):
47
+ """Save current memory to disk."""
48
+ with open(DB_FILE, 'w') as f:
49
+ json.dump(self.known_names, f)
50
+ np.save(EMBEDDINGS_FILE, self.known_embeddings)
51
+
52
+ def enroll_user(self, name, image):
53
+ """Analyzes an image and adds the person to the database."""
54
+ if image is None or name.strip() == "":
55
+ return "⚠️ Error: Missing name or photo."
56
+
57
+ # InsightFace expects BGR format (OpenCV standard)
58
+ img_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
59
+
60
+ # Detect face for enrollment
61
+ faces = self.recognizer.get(img_bgr)
62
+
63
+ if len(faces) == 0:
64
+ return "⚠️ Error: No face detected. Please use a clear, front-facing photo."
65
+
66
+ # Get the largest face (in case there are others in background)
67
+ # We sort by area (width * height)
68
+ face = sorted(faces, key=lambda x: (x.bbox[2]-x.bbox[0]) * (x.bbox[3]-x.bbox[1]))[-1]
69
+
70
+ # Extract embedding (512-dimensional vector)
71
+ embedding = face.normed_embedding.reshape(1, -1)
72
+
73
+ # Add to memory
74
+ if self.known_embeddings.shape[0] == 0:
75
+ self.known_embeddings = embedding
76
+ else:
77
+ self.known_embeddings = np.vstack([self.known_embeddings, embedding])
78
 
79
+ self.known_names.append(name)
80
+
81
+ # Save to disk
82
+ self.save_db()
83
+
84
+ return f"✅ Success: '{name}' added to database."
 
 
 
 
 
 
 
 
 
85
 
86
+ def recognize_and_process(self, frame, blur_intensity=20, threshold=0.5):
87
+ """
88
+ The Core Loop: Detect -> Identify -> Anonymize
89
+ """
90
+ if frame is None: return None
91
+
92
+ img_vis = frame.copy()
93
+ h, w = img_vis.shape[:2]
94
+
95
+ # 1. DETECT (YOLOv8)
96
+ # conf=0.5 reduces false positives
97
+ results = self.detector(img_vis, conf=0.5, verbose=False)
98
+
99
+ for result in results:
100
+ boxes = result.boxes
101
+ for box in boxes:
102
+ x1, y1, x2, y2 = map(int, box.xyxy[0])
103
+
104
+ # Add margin to the face crop for better recognition
105
+ margin = 0
106
+ cx1 = max(0, x1 - margin)
107
+ cy1 = max(0, y1 - margin)
108
+ cx2 = min(w, x2 + margin)
109
+ cy2 = min(h, y2 + margin)
110
+
111
+ face_crop = img_vis[cy1:cy2, cx1:cx2]
112
+
113
+ # 2. IDENTIFY (InsightFace + Vector Math)
114
+ name = "Unknown"
115
+ color = (200, 0, 0) # Red for unknown
116
+
117
+ if self.known_embeddings.shape[0] > 0 and face_crop.size > 0:
118
+ # Convert crop to BGR for InsightFace
119
+ face_crop_bgr = cv2.cvtColor(face_crop, cv2.COLOR_RGB2BGR)
120
+
121
+ # Extract embedding
122
+ analysis = self.recognizer.get(face_crop_bgr)
123
+
124
+ if len(analysis) > 0:
125
+ # Get embedding of the main face in the crop
126
+ target_emb = analysis[0].normed_embedding
127
+
128
+ # Calculate Cosine Similarity against ALL known faces at once
129
+ # (Dot product of normalized vectors = Cosine Similarity)
130
+ similarities = np.dot(self.known_embeddings, target_emb)
131
+
132
+ # Find best match
133
+ best_idx = np.argmax(similarities)
134
+ best_score = similarities[best_idx]
135
+
136
+ if best_score > threshold:
137
+ name = self.known_names[best_idx]
138
+ color = (0, 255, 0) # Green for known
139
+ # Optional: Show confidence score
140
+ # name += f" ({int(best_score*100)}%)"
141
+
142
+ # 3. PRIVACY (Pixelation)
143
+ # We extract the ROI again (strictly inside the box)
144
+ roi = img_vis[y1:y2, x1:x2]
145
+ if roi.size > 0:
146
+ # Pixelation logic: Downscale -> Upscale
147
+ # Map intensity (10-100) to a factor. Lower factor = bigger blocks.
148
+ # Intensity 10 = Block size 20px
149
+ # Intensity 100 = Block size 3px (barely blurred)
150
+ block_size = max(3, int(30 - (blur_intensity / 4)))
151
+
152
+ h_roi, w_roi = roi.shape[:2]
153
+
154
+ # Downscale
155
+ small = cv2.resize(roi, (max(1, w_roi // block_size), max(1, h_roi // block_size)), interpolation=cv2.INTER_LINEAR)
156
+ # Upscale (Nearest Neighbor creates the pixel effect)
157
+ pixelated = cv2.resize(small, (w_roi, h_roi), interpolation=cv2.INTER_NEAREST)
158
+
159
+ # Apply back to image
160
+ img_vis[y1:y2, x1:x2] = pixelated
161
+
162
+ # 4. OVERLAY (ID Label)
163
+ # We draw the label ON TOP of the blurred face
164
+
165
+ # Box
166
+ cv2.rectangle(img_vis, (x1, y1), (x2, y2), color, 2)
167
+
168
+ # Text Background
169
+ label_size, baseline = cv2.getTextSize(name, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)
170
+ cv2.rectangle(img_vis, (x1, y1 - label_size[1] - 10), (x1 + label_size[0] + 10, y1), color, -1)
171
+
172
+ # Text
173
+ cv2.putText(img_vis, name, (x1 + 5, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
174
+
175
+ return img_vis
176
+
177
+ # Initialize System
178
+ system = FaceSystem()
179
+
180
+ # ==========================================
181
+ # GRADIO INTERFACE
182
+ # ==========================================
183
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="emerald", secondary_hue="zinc")) as demo:
184
 
185
+ gr.Markdown(
186
+ """
187
+ # 👁️ SecureVision Pro
188
+ **Enterprise-Grade Identity Protection & Recognition System**
189
+ """
190
+ )
191
 
192
+ with gr.Tabs():
193
+ # TAB 1: MONITOR
194
+ with gr.Tab("📹 Live Monitor"):
195
+ with gr.Row():
196
+ with gr.Column(scale=2):
197
+ input_feed = gr.Image(sources=["webcam"], streaming=True, label="Camera Feed")
198
+ with gr.Column(scale=2):
199
+ output_feed = gr.Image(label="Processed Stream (Privacy + ID)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
+ with gr.Accordion("⚙️ System Settings", open=True):
202
+ blur_slider = gr.Slider(1, 100, value=50, label="Privacy Level (Pixelation Strength)")
203
+ conf_slider = gr.Slider(0.1, 0.9, value=0.5, label="Recognition Strictness (Threshold)")
204
+
205
+ # Connect the stream
206
+ input_feed.stream(
207
+ fn=system.recognize_and_process,
208
+ inputs=[input_feed, blur_slider, conf_slider],
209
+ outputs=output_feed
210
+ )
211
+
212
+ # TAB 2: DATABASE
213
+ with gr.Tab("👤 Database Management"):
214
+ with gr.Row():
215
+ with gr.Column():
216
+ gr.Markdown("### Enroll New Personnel")
217
+ new_name = gr.Textbox(label="Full Name / ID")
218
+ new_photo = gr.Image(sources=["upload", "webcam"], label="Reference Photo")
219
+ add_btn = gr.Button("Enroll User", variant="primary")
220
+ status_msg = gr.Markdown("")
221
+
222
+ with gr.Column():
223
+ gr.Markdown("### Database Status")
224
+ # A function to list current users
225
+ def get_user_list():
226
+ if not system.known_names: return "No users enrolled."
227
+ return "\n".join([f"• {n}" for n in system.known_names])
228
+
229
+ user_list = gr.Markdown(get_user_list)
230
+ refresh_btn = gr.Button("Refresh List")
231
+
232
+ # Enroll Logic
233
+ add_btn.click(
234
+ fn=system.enroll_user,
235
+ inputs=[new_name, new_photo],
236
+ outputs=status_msg
237
+ )
238
+ # Refresh Logic
239
+ refresh_btn.click(fn=get_user_list, outputs=user_list)
240
+ # Auto-refresh list on enroll
241
+ add_btn.click(fn=get_user_list, outputs=user_list)
242
 
243
  if __name__ == "__main__":
244
  demo.launch()