| import cv2 |
| import numpy as np |
| from insightface.app import FaceAnalysis |
| import os |
| import json |
| from datetime import datetime, timezone |
|
|
| REAL_FACES_DB = "faces_db" |
| TEMP_DB_ROOT = "temp_face_database" |
| TEMP_EMB_ROOT = "temp_faces_db" |
|
|
| os.makedirs(TEMP_DB_ROOT, exist_ok=True) |
| os.makedirs(TEMP_EMB_ROOT, exist_ok=True) |
|
|
| def load_database(): |
| db = {} |
| if os.path.exists(REAL_FACES_DB): |
| for file in os.listdir(REAL_FACES_DB): |
| if file.endswith(".npy"): |
| name = file.replace(".npy", "") |
| db[name] = np.load(os.path.join(REAL_FACES_DB, file)) |
|
|
| if os.path.exists(TEMP_EMB_ROOT): |
| for file in os.listdir(TEMP_EMB_ROOT): |
| if file.endswith(".npy"): |
| name = file.replace(".npy", "") |
| db[name] = np.load(os.path.join(TEMP_EMB_ROOT, file)) |
| return db |
|
|
| def cosine_similarity(a, b): |
| return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) |
|
|
| def get_next_unknown_id(): |
| existing = [d for d in os.listdir(TEMP_DB_ROOT) |
| if os.path.isdir(os.path.join(TEMP_DB_ROOT, d)) and d.startswith("unknown_")] |
| if not existing: |
| return 1 |
| ids = [] |
| for d in existing: |
| try: |
| ids.append(int(d.split("_")[1])) |
| except (IndexError, ValueError): |
| pass |
| return max(ids) + 1 if ids else 1 |
|
|
| def log_interaction(interaction_graph, a, b, timestamp): |
| if a == b: |
| return |
| interaction_graph.setdefault(a, {}).setdefault(b, []).append({ |
| "timestamp": timestamp, |
| "camera": "cam_1" |
| }) |
| interaction_graph.setdefault(b, {}).setdefault(a, []).append({ |
| "timestamp": timestamp, |
| "camera": "cam_1" |
| }) |
|
|
| def build_levels(root, graph): |
| level_1 = set(graph.get(root, {}).keys()) |
|
|
| level_2 = set() |
| for p in level_1: |
| level_2.update(graph.get(p, {}).keys()) |
| level_2 -= level_1 |
| level_2.discard(root) |
|
|
| level_3 = set() |
| for p in level_2: |
| level_3.update(graph.get(p, {}).keys()) |
| level_3 -= level_2 |
| level_3 -= level_1 |
| level_3.discard(root) |
|
|
| return level_1, level_2, level_3 |
|
|
| def format_level(interaction_graph, level_set, via=None): |
| result = [] |
| for person in level_set: |
| entry = { |
| "name": person, |
| "interactions": interaction_graph.get(person, {}) |
| } |
| if via: |
| entry["interacted_via"] = via.get(person, "") |
| result.append(entry) |
| return result |
|
|
| def main(): |
| app = FaceAnalysis(name='buffalo_l', allowed_modules=['detection', 'recognition']) |
| app.prepare(ctx_id=-1, det_size=(640, 640)) |
|
|
| db = load_database() |
| root_person = input("Enter the name of the person to track: ").strip() |
|
|
| interaction_graph = {} |
| frame_id = 0 |
|
|
| cap = cv2.VideoCapture(0) |
| if not cap.isOpened(): |
| raise SystemExit("Could not open webcam.") |
|
|
| try: |
| while True: |
| ret, frame = cap.read() |
| if not ret: |
| break |
|
|
| frame_id += 1 |
| faces = app.get(frame) |
|
|
| detected_people = [] |
| bboxes = [] |
|
|
| for face in faces: |
| x1, y1, x2, y2 = face.bbox.astype(int) |
| emb = face.embedding |
|
|
| best_match = "Unknown" |
| best_score = 0.0 |
|
|
| for name, db_emb in db.items(): |
| if db_emb.ndim == 1: |
| score = cosine_similarity(emb, db_emb) |
| else: |
| scores = [cosine_similarity(emb, view) for view in db_emb] |
| score = max(scores) if scores else 0.0 |
|
|
| if score > best_score: |
| best_score = score |
| best_match = name |
|
|
| threshold = 0.35 if best_match.startswith("unknown") else 0.30 |
|
|
| if best_score > threshold: |
| label = best_match |
| color = (0, 255, 0) |
| else: |
| new_id = get_next_unknown_id() |
| best_match = f"unknown_{new_id}" |
| db[best_match] = np.array([emb]) |
| label = best_match |
| color = (0, 255, 0) |
|
|
| detected_people.append(best_match) |
| bboxes.append((x1, y1, x2, y2)) |
|
|
| cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) |
| cv2.putText(frame, label, (x1, y1 - 10), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2) |
|
|
| frame_width = frame.shape[1] |
| proximity_threshold = 0.25 * frame_width |
|
|
| seen_pairs = set() |
| timestamp = datetime.now(timezone.utc).isoformat() |
|
|
| for i in range(len(detected_people)): |
| for j in range(i + 1, len(detected_people)): |
| a = detected_people[i] |
| b = detected_people[j] |
|
|
| if a == b: |
| continue |
|
|
| (x1a, y1a, x2a, y2a) = bboxes[i] |
| (x1b, y1b, x2b, y2b) = bboxes[j] |
|
|
| center_a = ((x1a + x2a) / 2, (y1a + y2a) / 2) |
| center_b = ((x1b + x2b) / 2, (y1b + y2b) / 2) |
|
|
| distance = np.linalg.norm(np.array(center_a) - np.array(center_b)) |
|
|
| if distance < proximity_threshold: |
| pair = tuple(sorted([a, b])) |
| if pair not in seen_pairs: |
| log_interaction(interaction_graph, a, b, timestamp) |
| seen_pairs.add(pair) |
|
|
| cv2.imshow("Live Face Recognition", frame) |
|
|
| if cv2.waitKey(1) & 0xFF == ord('q'): |
| break |
| finally: |
| cap.release() |
| cv2.destroyAllWindows() |
|
|
| level_1, level_2, level_3 = build_levels(root_person, interaction_graph) |
|
|
| output = { |
| "root_person": root_person, |
| "contacts": { |
| "level_1": format_level(interaction_graph, level_1), |
| "level_2": format_level(interaction_graph, level_2), |
| "level_3": format_level(interaction_graph, level_3) |
| } |
| } |
|
|
| with open("interaction_output.json", "w") as f: |
| json.dump(output, f, indent=2) |
|
|
|
|
| if __name__ == '__main__': |
| main() |
|
|