From 721e385e6507a7404b8af7b604a62eeb9e2476c8 Mon Sep 17 00:00:00 2001 From: GDjkhp Date: Tue, 10 Dec 2024 12:55:50 +0800 Subject: [PATCH] hi Signed-off-by: GDjkhp --- .gitignore | 5 ++ LICENSE | 13 +++ README.md | 60 +++++++++++++ mp_client.py | 150 +++++++++++++++++++++++++++++++ mp_deploy.py | 223 ++++++++++++++++++++++++++++++++++++++++++++++ mp_train.py | 86 ++++++++++++++++++ readdb.py | 36 ++++++++ requirements.txt | 9 ++ server_capture.py | 32 +++++++ 9 files changed, 614 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 mp_client.py create mode 100644 mp_deploy.py create mode 100644 mp_train.py create mode 100644 readdb.py create mode 100644 requirements.txt create mode 100644 server_capture.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45ebdd0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pyc +*.json +*.png +*.jpg +*.csv \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8cffccc --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad08e7d --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ + +
+ + +
+

fuck i need money so bad i need publish this disgusting paper tas need pa ma-bookbind kasi requirements potek nayan i hate college so much im dedicating this project sa lahat ng mga nasa kolehiyo pa kasi grabe dinanas ko >_<

+if you enjoyed the time you wasted then it's not a waste of time <3 +
+donate kayo if you find this repo helpful minimum lang sweldo ko sa trabaho :3 +
+halos lahat ng python script ay AI generated ni OpenAI at Claude, kaya kung may tanong kayo, wag sakin, tanong nyo sa kanila XD +

AMA Facial Recognition: Digital Records-logs Management System

+A Thesis Presented to the Faculty of AMA Computer +College Lucena Campus, Lucena City + +In Partial Fulfilment of the Requirements for the +Degree Bachelor of Science in Computer Science + +John Emmanuel S. Marco +
+John Kennedy H. Peña +

Copyright 2024, The Karakters Kompany

+ +lisensya? anong pinagsasasabi mo? ofc meron. need mo hunter [license](http://www.wtfpl.net/) OwO +
+

para saan to?

+para mas mapadali mag monitor ng mga tao sa paligid kasi may trust issues si eman (spy ng china) + +

mga use case:

+ +* pwedeng gawing biometrics at cctv surveillance +* nakakatamad magsulat sa logbook, merong pa bang contact-tracing? +* para mawalan ng trabaho si manong guard >:) + +

pano to gamitin?

+

madali lang to guys need mo lang neto:

+ +* computer na may internet connection (preferably windows pero pwede rin linux or apple) +* python na hindi kalumaan (need mo rin vs code para mas maganda mag edit) +* camera (mas malinaw mas maganda) +

kung ready na lahat, sundan mo to:

+ +1. install all reqiured libraries: requirements.txt or pwede rin mano-mano good luck (hint: pip install) +2. download mo lahat ng mga script: [click here to download](https://github.com/GDjkhp/ama-facial-recognition/archive/refs/heads/main.zip) +3. enroll mo mukha mo: set-up mo si server (server_capture.py) then picture picture (mp_client.py) +4. train mo si AI: extract mo lahat ng mga mukha (readdb.py) then train (mp_train.py) +5. finally try mo na sya congrats: (mp_deploy.py) +

format ng mga files at structure ng source code

+ +

/*.py

lahat ng logic nandito +

/requirements.txt

lahat ng required python libraries nandito +

/json/*.json

mga data ng mga mukha + other information na nakalap ni server +

/received_images/{name}/*.png or *.jpg

lahat ng mga mukha na ni train ni AI +

/face_embeddings.json

AI weight pagkatapos i-train +

/face_detection_logs.csv

dito nagtatala ng date and time at camera forda person +

/LICENSE

DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +

/README.md

itong binabasa mo + +

hindi rito kasama node.js server at client code, gamitin nyo nalang server_capture.py at mp_client.py :) pero kung need nyo to, chat nyo nalang si eman O_o

+
\ No newline at end of file diff --git a/mp_client.py b/mp_client.py new file mode 100644 index 0000000..21c287e --- /dev/null +++ b/mp_client.py @@ -0,0 +1,150 @@ +import cv2 +import sys +import base64 +import requests +import mediapipe as mp +from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout, QComboBox +from PyQt5.QtCore import QTimer, Qt +from PyQt5.QtGui import QImage, QPixmap + +class FaceDetectionApp(QWidget): + def __init__(self): + super().__init__() + self.mp_face_detection = mp.solutions.face_detection + self.mp_drawing = mp.solutions.drawing_utils + self.face_detection = self.mp_face_detection.FaceDetection(min_detection_confidence=0.5) + self.cameras = self.get_available_cameras() + self.current_camera = 0 + self.cap = cv2.VideoCapture(self.current_camera) + self.captured_images = [] + self.image_count = 0 + self.image_limit = 100 + self.initUI() + self.timer = QTimer(self) + self.timer.timeout.connect(self.update_frame) + self.timer.start(30) + + def get_available_cameras(self): + camera_indices = [] + for i in range(10): # Check first 10 indices + cap = cv2.VideoCapture(i) + if cap.isOpened(): + camera_indices.append(i) + cap.release() + return camera_indices + + def initUI(self): + self.setWindowTitle('Face Detection App') + self.setGeometry(100, 100, 400, 500) + + layout = QVBoxLayout() + + self.image_label = QLabel(self) + layout.addWidget(self.image_label) + + self.count_label = QLabel(f'Captured Images: 0 / {self.image_limit}', self) + layout.addWidget(self.count_label) + + self.name_input = QLineEdit(self) + self.name_input.setPlaceholderText('Enter your name') + layout.addWidget(self.name_input) + + self.usn_input = QLineEdit(self) + self.usn_input.setPlaceholderText('Enter your student number') + layout.addWidget(self.usn_input) + + button_layout = QHBoxLayout() + + self.submit_button = QPushButton('Submit', self) + self.submit_button.clicked.connect(self.submit_data) + button_layout.addWidget(self.submit_button) + + self.reset_button = QPushButton('Reset', self) + self.reset_button.clicked.connect(self.reset_form) + button_layout.addWidget(self.reset_button) + + layout.addLayout(button_layout) + + self.camera_combo = QComboBox(self) + self.camera_combo.addItems([f"Camera {i}" for i in self.cameras]) + self.camera_combo.currentIndexChanged.connect(self.switch_camera) + layout.addWidget(self.camera_combo) + + self.setLayout(layout) + + def update_frame(self): + ret, frame = self.cap.read() + if ret: + frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + results = self.face_detection.process(frame_rgb) + + if results.detections: + for detection in results.detections: + bboxC = detection.location_data.relative_bounding_box + ih, iw, _ = frame.shape + x, y, w, h = int(bboxC.xmin * iw), int(bboxC.ymin * ih), \ + int(bboxC.width * iw), int(bboxC.height * ih) + cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) + if self.image_count < self.image_limit: + self.capture_image(frame) + + rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + h, w, ch = rgb_image.shape + bytes_per_line = ch * w + convert_to_Qt_format = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) + p = convert_to_Qt_format.scaled(400, 300, Qt.KeepAspectRatio) + self.image_label.setPixmap(QPixmap.fromImage(p)) + + def capture_image(self, frame): + _, buffer = cv2.imencode('.png', frame) + encoded_string = base64.b64encode(buffer).decode('utf-8') + self.captured_images.append(encoded_string) + self.image_count += 1 + self.count_label.setText(f'Captured Images: {self.image_count} / {self.image_limit}') + + def submit_data(self): + name = self.name_input.text() + usn = self.usn_input.text() + + if not name or not usn: + print("Please enter both name and student number.") + return + + data = { + "name": name, + "usn": int(usn), + "images": self.captured_images + } + + server_url = "http://localhost:3000/api/submit" # Replace with your server details + + try: + response = requests.post(server_url, json=data) + if response.status_code == 200: + print("Data sent successfully!") + else: + print(f"Failed to send data. Status code: {response.status_code}") + except requests.exceptions.RequestException as e: + print(f"An error occurred: {e}") + + def reset_form(self): + self.name_input.clear() + self.usn_input.clear() + self.captured_images = [] + self.image_count = 0 + self.count_label.setText(f'Captured Images: 0 / {self.image_limit}') + + def switch_camera(self, index): + self.cap.release() + self.current_camera = self.cameras[index] + self.cap = cv2.VideoCapture(self.current_camera) + + def closeEvent(self, event): + self.cap.release() + self.face_detection.close() + +if __name__ == '__main__': + app = QApplication(sys.argv) + ex = FaceDetectionApp() + ex.show() + sys.exit(app.exec_()) \ No newline at end of file diff --git a/mp_deploy.py b/mp_deploy.py new file mode 100644 index 0000000..e241eaf --- /dev/null +++ b/mp_deploy.py @@ -0,0 +1,223 @@ +import sys +import cv2 +import os +import json +import numpy as np +from PyQt5.QtWidgets import QApplication, QMainWindow, QListView, QLabel, QVBoxLayout, QWidget, QHBoxLayout +from PyQt5.QtGui import QImage, QPixmap +from PyQt5.QtCore import QTimer, Qt, QStringListModel, QMetaObject, QCoreApplication +import datetime +import time +from imgbeddings import imgbeddings +from PIL import Image +import mediapipe as mp +import csv + +class Ui_MainWindow_List(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(1366, 768) + self.centralwidget = QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + + # Main layout + self.main_layout = QHBoxLayout(self.centralwidget) + + # Left side (camera) + self.camera_layout = QVBoxLayout() + self.camera_0 = QLabel() + self.camera_0.setObjectName("camera_0") + self.camera_0.setMinimumSize(890, 730) + self.camera_layout.addWidget(self.camera_0) + self.main_layout.addLayout(self.camera_layout) + + # Right side (logo and list) + self.right_layout = QVBoxLayout() + + # Logo + self.logo_label = QLabel() + self.logo_label.setObjectName("logo_label") + self.logo_label.setMinimumSize(450, 200) + self.logo_label.setAlignment(Qt.AlignCenter) + self.right_layout.addWidget(self.logo_label) + + # List + self.listView = QListView() + self.listView.setObjectName("listView") + self.listView.setMinimumSize(450, 500) + self.right_layout.addWidget(self.listView) + + self.main_layout.addLayout(self.right_layout) + + MainWindow.setCentralWidget(self.centralwidget) + + self.retranslateUi(MainWindow) + QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + + def set_logo(self, logo_path): + pixmap = QPixmap(logo_path) + self.logo_label.setPixmap(pixmap.scaled(self.logo_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) + +# Initialize MediaPipe Face Mesh +mp_face_mesh = mp.solutions.face_mesh +mp_drawing = mp.solutions.drawing_utils + +# Load the face embeddings +with open("face_embeddings.json", "r", encoding="utf-8") as f: + face_embeddings = json.load(f) + +# Initialize imgbeddings +ibed = imgbeddings() + +class MainWindow(QMainWindow, Ui_MainWindow_List): + def __init__(self): + super().__init__() + self.setupUi(self) + # self.set_logo("./amaLogo.png") + + self.caps = [ + cv2.VideoCapture(0), + cv2.VideoCapture(""), + ] + + self.timer = QTimer(self) + self.timer.timeout.connect(self.update_frames) + self.timer.start(60) + + self.log_model = QStringListModel() + self.listView.setModel(self.log_model) + self.log_list = [] + self.last_logged_time = {} + self.start_time = time.time() + self.frame_count = 0 + + # Initialize MediaPipe Face Mesh + self.face_mesh = mp_face_mesh.FaceMesh(max_num_faces=10, min_detection_confidence=0.5, min_tracking_confidence=0.5) + + # Initialize CSV logging + self.csv_file_path = "face_detection_logs.csv" + self.initialize_csv_log() + + def update_frames(self): + frames = [cap.read()[1] for cap in self.caps] + + for i, frame in enumerate(frames): + if frame is not None: + camera_label = f"camera_{i}" + self.process_frame(frame, self.camera_0, camera_label) + + self.frame_count += 1 + elapsed_time = time.time() - self.start_time + if elapsed_time > 1.0: + fps = self.frame_count / elapsed_time + self.setWindowTitle(f"Face Recognition - FPS: {fps:.2f}") + self.start_time = time.time() + self.frame_count = 0 + + def process_frame(self, frame, label, camera_label): + rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + results = self.face_mesh.process(rgb_frame) + + if results.multi_face_landmarks: + for face_landmarks in results.multi_face_landmarks: + # Get bounding box from landmarks + h, w, _ = frame.shape + x_min = int(min([landmark.x for landmark in face_landmarks.landmark]) * w) + x_max = int(max([landmark.x for landmark in face_landmarks.landmark]) * w) + y_min = int(min([landmark.y for landmark in face_landmarks.landmark]) * h) + y_max = int(max([landmark.y for landmark in face_landmarks.landmark]) * h) + + # Ensure the bounding box is within the frame + x_min, y_min = max(0, x_min), max(0, y_min) + x_max, y_max = min(w, x_max), min(h, y_max) + + # Check if the face region is valid + if x_min < x_max and y_min < y_max: + # Extract face image + face_img = frame[y_min:y_max, x_min:x_max] + + # Check if face_img is not empty + if face_img.size > 0: + pil_img = Image.fromarray(cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB)) + embedding = ibed.to_embeddings(pil_img)[0] + + name = self.recognize_face(embedding) + + # Draw bounding box and name + cv2.rectangle(frame, (x_min, y_min), (x_max, y_max), (0, 255, 0), 2) + cv2.rectangle(frame, (x_min, y_min-40), (x_max, y_min), (0, 255, 0), -1) + cv2.putText(frame, name, (x_min, y_min-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2) + + if name not in ["Unknown", "Loading"]: + self.log_face_detection(camera_label, name) + + self.display_frame(frame, label) + + def display_frame(self, frame, label): + rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + h, w, ch = rgb_image.shape + bytes_per_line = ch * w + qt_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) + + scaled_qt_image = qt_image.scaled(label.size(), Qt.AspectRatioMode.KeepAspectRatio) + label.setPixmap(QPixmap.fromImage(scaled_qt_image)) + + def recognize_face(self, embedding): + min_distance = float('inf') + recognized_name = "unknown" + + for name, stored_embedding in face_embeddings.items(): + distance = np.linalg.norm(np.array(embedding) - np.array(stored_embedding)) + if distance < min_distance: + min_distance = distance + recognized_name = name + + if min_distance > 10 and min_distance < 12: # Adjust this threshold as needed + recognized_name = "Loading" + elif min_distance > 12: + recognized_name = "Unknown" + + return recognized_name + + def log_face_detection(self, cam_label, name): + current_time = time.time() + if name not in self.last_logged_time or (current_time - self.last_logged_time[name]) >= 60: # 900 seconds = 15 minutes + self.last_logged_time[name] = current_time + self.add_log_entry(name, cam_label) + + def add_log_entry(self, name, cam_label): + timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + log_entry = f"{timestamp} - Face detected on {cam_label}: {name}" + self.log_list.append(log_entry) + self.log_model.setStringList(self.log_list) + self.listView.scrollToBottom() + self.log_to_csv(timestamp, cam_label, name) + + def initialize_csv_log(self): + if not os.path.exists(self.csv_file_path): + with open(self.csv_file_path, 'w', newline='') as csvfile: + csv_writer = csv.writer(csvfile) + csv_writer.writerow(['Timestamp', 'Camera', 'Name']) + + def log_to_csv(self, timestamp, camera, name): + with open(self.csv_file_path, 'a', newline='') as csvfile: + csv_writer = csv.writer(csvfile) + csv_writer.writerow([timestamp, camera, name]) + + def closeEvent(self, event): + for cap in self.caps: + cap.release() + self.face_mesh.close() + +def main_camera(): + app = QApplication(sys.argv) + window = MainWindow() + window.show() + sys.exit(app.exec_()) + +if __name__ == "__main__": + main_camera() diff --git a/mp_train.py b/mp_train.py new file mode 100644 index 0000000..2b57b25 --- /dev/null +++ b/mp_train.py @@ -0,0 +1,86 @@ +import os +import cv2 +import numpy as np +from imgbeddings import imgbeddings +from PIL import Image +import json +import mediapipe as mp + +def train_images(): + # Initialize MediaPipe Face Mesh + mp_face_mesh = mp.solutions.face_mesh + face_mesh = mp_face_mesh.FaceMesh(static_image_mode=True, max_num_faces=1, min_detection_confidence=0.5) + + # Create a dictionary to store name and embeddings + embeddings_dict = {} + + # Initialize imgbeddings + ibed = imgbeddings() + + # Iterate through the dataset directory + dataset_dir = 'received_images' + if not os.path.exists(dataset_dir): + print(f"Error: '{dataset_dir}' directory not found.") + exit(1) + + for name in os.listdir(dataset_dir): + print(name) + person_dir = os.path.join(dataset_dir, name) + if os.path.isdir(person_dir): + embeddings_list = [] + # Iterate through the image files in the person's directory + for image_file in os.listdir(person_dir): + image_path = os.path.join(person_dir, image_file) + # https://stackoverflow.com/questions/43185605/how-do-i-read-an-image-from-a-path-with-unicode-characters + stream = open(image_path, "rb") + bytes = bytearray(stream.read()) + numpyarray = np.asarray(bytes, dtype=np.uint8) + image = cv2.imdecode(numpyarray, cv2.IMREAD_UNCHANGED) + if image is None: + print(f"Warning: Failed to read image '{image_path}'") + continue + + # Convert the image to RGB for MediaPipe + rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + results = face_mesh.process(rgb_image) + + if results.multi_face_landmarks: + for face_landmarks in results.multi_face_landmarks: + # Get bounding box from landmarks + h, w, _ = image.shape + x_min = int(min([landmark.x for landmark in face_landmarks.landmark]) * w) + x_max = int(max([landmark.x for landmark in face_landmarks.landmark]) * w) + y_min = int(min([landmark.y for landmark in face_landmarks.landmark]) * h) + y_max = int(max([landmark.y for landmark in face_landmarks.landmark]) * h) + + # Ensure the bounding box is within the image + x_min, y_min = max(0, x_min), max(0, y_min) + x_max, y_max = min(w, x_max), min(h, y_max) + + # Check if the face region is valid + if x_min < x_max and y_min < y_max: + # Extract face image + face_img = image[y_min:y_max, x_min:x_max] + + # Check if face_img is not empty + if face_img.size > 0: + pil_img = Image.fromarray(cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB)) + embedding = ibed.to_embeddings(pil_img)[0] + embeddings_list.append(embedding.tolist()) + + # Store the average embedding for the person + if embeddings_list: + avg_embedding = np.mean(embeddings_list, axis=0) + embeddings_dict[name] = avg_embedding.tolist() + + # Save the embeddings to a JSON file + with open("face_embeddings.json", "w", encoding="utf-8") as f: + json.dump(embeddings_dict, f) + + print("Face embeddings saved successfully.") + + # Close the MediaPipe Face Mesh + face_mesh.close() + +if __name__ == "__main__": + train_images() \ No newline at end of file diff --git a/readdb.py b/readdb.py new file mode 100644 index 0000000..64450d1 --- /dev/null +++ b/readdb.py @@ -0,0 +1,36 @@ +import os, json +import base64 + +directory = "received_images" +if not os.path.exists(directory): os.makedirs(directory) + +dataset_dir = "json" +if not os.path.exists(dataset_dir): + print(f"Error: '{dataset_dir}' directory not found.") + exit(1) + +def read_json_file(file_path): + with open(file_path, 'r', encoding='utf-8') as json_file: + data = json.load(json_file) + return data + +def parse_json(): + for name in os.listdir(dataset_dir): + data = read_json_file(os.path.join(dataset_dir, name)) + name = data.get("name") + usn = data.get("usn") + images = data.get("images") + + image_folder = f'{directory}/{name}' + if not os.path.exists(image_folder): + os.makedirs(image_folder) + + for i, img_str in enumerate(images): + img_data = base64.b64decode(img_str) + file_path = os.path.join(image_folder, f"{name}_{usn}_{i}.jpg") + print(f"{name}_{usn}_{i}.jpg") + with open(file_path, 'wb') as img_file: + img_file.write(img_data) + +if __name__ == "__main__": + parse_json() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9d1ea8c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +imgbeddings +mediapipe +opencv-contrib-python +requests +flask +flask-cors +pyqt5 +numpy +pillow \ No newline at end of file diff --git a/server_capture.py b/server_capture.py new file mode 100644 index 0000000..5b61ef3 --- /dev/null +++ b/server_capture.py @@ -0,0 +1,32 @@ +from flask import Flask, request, jsonify +import json +from flask_cors import CORS +import os, os.path + +app = Flask(__name__) +CORS(app) # This allows all origins. For production, you might want to configure this more strictly. +directory = "json" + +@app.route('/api/submit', methods=['POST']) +def submit_entry(): + data = request.json + + if not data or 'name' not in data or 'usn' not in data or 'images' not in data: + return jsonify({"error": "Invalid data format"}), 400 + + new_entry = { + "name": data['name'], + "usn": data['usn'], + "images": data['images'] + } + + # Save to file + if not os.path.exists(directory): os.makedirs(directory) + DATA_FILE = f"{directory}/{data['name']}.json" + with open(DATA_FILE, 'w') as f: + json.dump(new_entry, f, indent=2) + + return jsonify({"message": "Entry added successfully"}), 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=3000, debug=True) \ No newline at end of file