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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +127 -73
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # SecureFace ID – FINAL VERSION (Enroll button fixed + recognition works)
2
  import os
3
  import cv2
4
  import numpy as np
@@ -14,62 +14,102 @@ KNOWN_EMBS_PATH = "known_embeddings.npy"
14
  KNOWN_NAMES_PATH = "known_names.npy"
15
 
16
  # ==================== MODELS ====================
 
 
17
  model_path = hf_hub_download(repo_id="arnabdhar/YOLOv8-Face-Detection", filename="model.pt")
18
  detector = YOLO(model_path)
19
 
 
 
20
  recognizer = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider'])
21
  recognizer.prepare(ctx_id=0, det_size=(640,640))
22
 
23
- # FAISS index
24
  index = faiss.IndexHNSWFlat(512, 32)
25
  index.hnsw.efSearch = 16
26
  known_names = []
27
 
28
  # Load database at startup
29
- if os.path.exists(KNOWN_EMBS_PATH) and os.path.getsize(KNOWN_EMBS_PATH) > 0:
30
- embs = np.load(KNOWN_EMBS_PATH)
31
- known_names = np.load(KNOWN_NAMES_PATH, allow_pickle=True).tolist()
32
- index.add(embs.astype('float32'))
 
 
 
 
 
33
 
34
  # ==================== PROCESS FRAME ====================
35
  def process_frame(frame, blur_type="gaussian", intensity=50, expand=1.4, show_labels=True):
 
 
36
  img = frame.copy()
37
  h, w = img.shape[:2]
38
- results = detector(img, conf=0.35)[0]
 
 
39
 
40
  for box in results.boxes:
41
  x1, y1, x2, y2 = map(int, box.xyxy[0])
 
 
42
  ew = int((x2-x1)*(expand-1)/2)
43
  eh = int((y2-y1)*(expand-1)/2)
44
- x1 = max(0, x1-ew); y1 = max(0, y1-eh)
45
- x2 = min(w, x2+ew); y2 = min(h, y2+eh)
46
 
47
- crop = cv2.cvtColor(img[y1:y2, x1:x2], cv2.COLOR_RGB2BGR)
48
- faces = recognizer.get(crop, max_num=1)
 
 
 
 
49
 
50
  name = "Unknown"
 
 
51
  if faces and index.ntotal > 0:
52
- emb = faces[0].normed_embedding.reshape(1, -1).astype('float32')
53
- D, I = index.search(emb, k=1) # ← fixed: k=1
54
- if D[0][0] < 0.6:
 
 
 
 
 
 
55
  name = known_names[I[0][0]]
56
-
57
- # Blur
58
- face_region = img[y1:y2, x1:x2]
59
- if blur_type == "gaussian":
60
- k = max(21, int(min(x2-x1, y2-y1) * intensity / 100) | 1)
61
- blurred = cv2.GaussianBlur(face_region, (k,k), 0)
62
- elif blur_type == "pixelate":
63
- small = cv2.resize(face_region, (20,20))
64
- blurred = cv2.resize(small, (x2-x1, y2-y1), interpolation=cv2.INTER_NEAREST)
65
- else:
66
- blurred = np.zeros_like(face_region)
67
- img[y1:y2, x1:x2] = blurred
68
-
 
 
 
 
 
 
69
  if show_labels:
70
- color = (0,255,0) if name != "Unknown" else (0,255,255)
71
- cv2.rectangle(img, (x1,y1), (x2,y2), color, 3)
72
- cv2.putText(img, name, (x1, y1-12), cv2.FONT_HERSHEY_DUPLEX, 1.1, color, 2)
 
 
 
 
 
 
 
73
 
74
  return img
75
 
@@ -77,69 +117,83 @@ def process_frame(frame, blur_type="gaussian", intensity=50, expand=1.4, show_la
77
  def enroll_person(name, face_image):
78
  global index, known_names
79
 
80
- if not face_image or not name.strip():
81
- return "Please provide name and photo"
 
82
 
 
83
  bgr = cv2.cvtColor(face_image, cv2.COLOR_RGB2BGR)
84
- faces = recognizer.get(bgr, max_num=1)
 
 
85
  if not faces:
86
- return "No face detected try a clear frontal photo"
87
 
88
- new_emb = faces[0].normed_embedding.reshape(1, 512)
 
 
89
 
90
- if os.path.exists(KNOWN_EMBS_PATH) and os.path.getsize(KNOWN_EMBS_PATH) > 0:
 
91
  embs = np.load(KNOWN_EMBS_PATH)
92
- names = np.load(KNOWN_NAMES_PATH, allow_pickle=True).tolist()
 
93
  else:
94
  embs = np.empty((0,512))
95
- names = []
96
 
 
97
  embs = np.vstack([embs, new_emb])
98
- names.append(name)
99
 
 
100
  np.save(KNOWN_EMBS_PATH, embs)
101
- np.save(KNOWN_NAMES_PATH, np.array(names))
 
 
102
  index.reset()
103
  index.add(embs.astype('float32'))
104
- known_names = names
105
 
106
- return f"**{name}** successfully enrolled and instantly recognized!"
107
 
108
  # ==================== GRADIO UI ====================
109
- with gr.Blocks() as demo:
110
- gr.Markdown("# SecureFace ID — Final Working Version")
111
 
112
- with gr.Tab("Live Recognition"):
113
  with gr.Row():
114
- cam = gr.Image(sources=["webcam"], streaming=True, height=500)
115
- upload = gr.Image(sources=["upload"], height=500)
116
- output = gr.Image(height=600)
 
 
117
  with gr.Row():
118
- blur_type = gr.Radio(["gaussian", "pixelate", "solid"], value="gaussian", label="Blur Type")
119
- intensity = gr.Slider(10, 100, 50, label="Blur Strength")
120
- expand = gr.Slider(1.0, 2.0, 1.4, label="Blur Area")
121
- show_names = gr.Checkbox(True, label="Show Names")
 
 
122
  cam.stream(process_frame, [cam, blur_type, intensity, expand, show_names], output)
123
- upload.change(process_frame, [upload, blur_type, intensity, expand, show_names], output)
124
-
125
- with gr.Tab("Enroll New Person"):
126
- gr.Markdown("### Add someone to the database permanently")
127
- name_input = gr.Textbox(label="Name or ID", placeholder="John Doe")
128
- photo_input = gr.Image(label="Clear face photo", sources=["upload", "webcam"], height=400)
129
- enroll_btn = gr.Button("Enroll Person", variant="primary", size="lg")
130
- enroll_status = gr.Markdown()
131
-
132
- with gr.Tab("Database"):
133
- db_list = gr.Markdown()
134
- def update_db():
135
- if not os.path.exists(KNOWN_NAMES_PATH):
136
- return "Database empty"
137
- names = np.load(KNOWN_NAMES_PATH, allow_pickle=True).tolist()
138
- return f"**{len(names)} people enrolled:**\n" + "\n".join(f"• {n}" for n in sorted(names))
139
- demo.load(update_db, outputs=db_list)
140
- enroll_btn.click(update_db, outputs=db_list)
141
-
142
- # ←←← THIS WAS THE MISSING LINE ←←←
143
  enroll_btn.click(enroll_person, inputs=[name_input, photo_input], outputs=enroll_status)
 
 
144
 
145
- demo.launch()
 
 
1
+ # SecureFace ID – FIXED VERSION
2
  import os
3
  import cv2
4
  import numpy as np
 
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
 
 
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()