diff --git a/data/videos/input_video.mp4 b/data/videos/input_video.mp4 deleted file mode 100644 index 8b13789..0000000 --- a/data/videos/input_video.mp4 +++ /dev/null @@ -1 +0,0 @@ - diff --git a/requirements.txt b/requirements.txt index aa918a0..4517414 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/src/config/config.py b/src/config/config.py index 65fb6fe..66ba455 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -4,10 +4,10 @@ DATA_PATH = os.path.join(BASE_DIR, "data") PROCESSED_PATH = os.path.join(DATA_PATH, "processed") EMBEDDINGS_PATH = os.path.join( DATA_PATH, "embeddings", "all_embeddings.pkl") -VIDEO_PATH = os.path.join(DATA_PATH, "videos", "input_video.mp4") +VIDEO_PATH = os.path.join(DATA_PATH, "videos", "taylor.mp4") RESULTS_PATH = os.path.join(DATA_PATH, "results") TEST = os.path.join(DATA_PATH, "raw") THRESHOLD = 0.6 # Ngưỡng so sánh embeddings VIDEO_RESOLUTION = (640, 480) - +FACE_DETECTION_CONFIDENCE = 0.54 diff --git a/src/detection/detect_faces.py b/src/detection/detect_faces.py index eb2b68d..40941a6 100644 --- a/src/detection/detect_faces.py +++ b/src/detection/detect_faces.py @@ -3,11 +3,15 @@ from ultralytics import YOLO import time import os +import sys -from src.recognition.recognize_faces import load_reference_embeddings, recognize_faces +# Thêm đường dẫn gốc vào sys.path +base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(f'{base_dir}/src') +from recognition.recognize_faces import load_reference_embeddings, recognize_faces -MODEL_PATH = 'yolov11n-face.pt' +MODEL_PATH = os.path.join(base_dir, 'yolov11n-face.pt') try: print(f"Đang tải mô hình YOLO từ '{MODEL_PATH}', vui lòng đợi...") diff --git a/src/detection/utils.py b/src/detection/utils.py deleted file mode 100644 index 8b13789..0000000 --- a/src/detection/utils.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/detection/vanuoc.py b/src/detection/vanuoc.py deleted file mode 100644 index 517701f..0000000 --- a/src/detection/vanuoc.py +++ /dev/null @@ -1,204 +0,0 @@ -# --- PHẦN 1: IMPORT CÁC THƯ VIỆN CẦN THIẾT --- -import cv2 # Thư viện OpenCV, chuyên dùng cho các tác vụ xử lý ảnh và video. -import numpy as np # Thư viện NumPy, dùng để làm việc hiệu quả với dữ liệu dạng mảng (ảnh cũng là một dạng mảng). -from ultralytics import YOLO # Import lớp YOLO từ thư viện của Ultralytics để sử dụng mô hình. -import time # Thư viện Time, dùng để đo thời gian xử lý và tính toán FPS (khung hình trên giây). - -# --- PHẦN 2: TẢI MODEL VÀ CÁC THIẾT LẬP BAN ĐẦU --- - -# Đường dẫn tới file model đã được huấn luyện. Bạn có thể thay đổi đường dẫn này. -# Gợi ý: Dùng model 'n' (nano) như 'yolov8n-face.pt' sẽ cho tốc độ nhanh hơn trên video/webcam. -MODEL_PATH = 'yolov11n-face.pt' - -# Sử dụng khối try...except để bắt lỗi nếu không tìm thấy file model hoặc file bị hỏng. -try: - # In thông báo cho người dùng biết quá trình tải model đang diễn ra. - print(f"Đang tải mô hình YOLO từ '{MODEL_PATH}', vui lòng đợi...") - # Khởi tạo đối tượng YOLO từ file model. Đây là bước tốn thời gian nhất và chỉ nên làm một lần. - YOLO_MODEL = YOLO(MODEL_PATH) - # Thông báo khi tải thành công. - print("Tải mô hình YOLO thành công.") -except Exception as e: - # Nếu có lỗi (ví dụ: sai đường dẫn, file hỏng), in ra thông báo lỗi chi tiết. - print(f"Lỗi nghiêm trọng khi tải mô hình: {e}") - print("Hãy chắc chắn rằng tệp model có trong thư mục. Chương trình sẽ không thể hoạt động.") - # Gán YOLO_MODEL là None để các hàm sau biết rằng model chưa sẵn sàng. - YOLO_MODEL = None - - -# --- PHẦN 3: CÁC HÀM XỬ LÝ --- - -def detect_and_process_faces(image_input, flag: int): - """ - Hàm cốt lõi: Phát hiện và xử lý các khuôn mặt trong một ảnh hoặc một khung hình. - - Args: - image_input (str hoặc np.ndarray): Đầu vào có thể là: - - Một chuỗi (str) chứa đường dẫn tới file ảnh. - - Một mảng NumPy (np.ndarray) đại diện cho ảnh (ví dụ: một khung hình từ video). - flag (int): Một con số để điều khiển chức năng và kết quả trả về. - - 1: Chỉ lấy tọa độ các khuôn mặt. - - 2: Lấy tọa độ VÀ ảnh đã được vẽ các ô vuông quanh khuôn mặt. - - 3: Chỉ lấy các ảnh khuôn mặt đã được cắt ra. - - 4: Lấy tất cả các kết quả trên. - Returns: - Kết quả trả về phụ thuộc vào 'flag'. Nếu có lỗi, trả về (None, "Thông báo lỗi"). - """ - # --- Bước 3.1: Kiểm tra các điều kiện đầu vào --- - - # Kiểm tra xem model đã được tải thành công ở bước trên chưa. - if YOLO_MODEL is None: - return None, "Lỗi: Mô hình YOLO chưa được tải thành công." - - # Kiểm tra xem tham số 'flag' có hợp lệ không. - if flag not in [1, 2, 3, 4]: - return None, f"Lỗi: Tham số cờ 'flag' không hợp lệ." - - # Kiểm tra loại dữ liệu đầu vào. - if isinstance(image_input, str): # Nếu đầu vào là một chuỗi (đường dẫn file) - img_original = cv2.imread(image_input) # Dùng OpenCV để đọc ảnh từ đường dẫn. - if img_original is None: # Nếu không đọc được ảnh (ví dụ: đường dẫn sai). - return None, f"Lỗi: Không thể đọc ảnh từ '{image_input}'." - elif isinstance(image_input, np.ndarray): # Nếu đầu vào đã là một ảnh (mảng NumPy). - img_original = image_input.copy() # Tạo một bản sao để không làm thay đổi ảnh gốc. - else: # Nếu đầu vào không phải chuỗi hay mảng NumPy. - return None, "Lỗi: Đầu vào không phải là đường dẫn hoặc mảng NumPy." - - # --- Bước 3.2: Thực hiện nhận diện --- - - # Đây là dòng lệnh quan trọng nhất: đưa ảnh vào model để xử lý. - # verbose=False để model không in ra các thông tin thừa trong quá trình chạy. - results = YOLO_MODEL(img_original, verbose=False) - # Lấy kết quả các hộp giới hạn (bounding boxes) và chuyển sang dạng mảng NumPy trên CPU cho dễ xử lý. - boxes = results[0].boxes.cpu().numpy() - - # --- Bước 3.3: Chuẩn bị các biến để lưu kết quả --- - - dict_coords = {} # Dictionary để lưu tọa độ các khuôn mặt. - dict_cropped_faces = {} # Dictionary để lưu các ảnh khuôn mặt đã được cắt. - # Nếu flag yêu cầu vẽ ảnh (2 hoặc 4), tạo một bản sao của ảnh gốc để vẽ lên đó. - image_with_boxes = img_original.copy() if flag in [2, 4] else None - - # --- Bước 3.4: Lặp qua từng khuôn mặt đã phát hiện để xử lý --- - - # `enumerate` để lấy cả chỉ số (i) và đối tượng (box) trong mỗi vòng lặp. - for i, box in enumerate(boxes): - # Lấy tọa độ (x1, y1) là góc trên bên trái và (x2, y2) là góc dưới bên phải. - # `map(int, ...)` để chuyển các tọa độ từ số thực sang số nguyên. - x1, y1, x2, y2 = map(int, box.xyxy[0]) - # Tạo một key duy nhất cho mỗi người, ví dụ: "person_1", "person_2",... - person_key = f"person_{i + 1}" - - # Tùy theo giá trị của 'flag', thực hiện các hành động tương ứng. - if flag in [1, 2, 4]: # Nếu cần lấy tọa độ. - dict_coords[person_key] = np.array([x1, y1, x2, y2]) - if flag in [2, 4]: # Nếu cần vẽ ô vuông. - cv2.rectangle(image_with_boxes, (x1, y1), (x2, y2), (0, 255, 0), 2) # Vẽ hình chữ nhật màu xanh lá. - if flag in [3, 4]: # Nếu cần cắt khuôn mặt. - # Cắt một vùng chữ nhật từ ảnh gốc dựa trên tọa độ đã phát hiện. - # `max(0, ...)` để đảm bảo tọa độ không bị âm, tránh lỗi. - cropped_face = img_original[max(0, y1):y2, max(0, x1):x2] - dict_cropped_faces[person_key] = cropped_face - - # --- Bước 3.5: Trả về kết quả cuối cùng theo 'flag' --- - if flag == 1: return dict_coords - if flag == 2: return dict_coords, image_with_boxes - if flag == 3: return dict_cropped_faces - if flag == 4: return dict_coords, image_with_boxes, dict_cropped_faces - - -def process_video(video_path: str): - """Hàm ứng dụng: Dùng hàm `detect_and_process_faces` để xử lý một file video.""" - # Mở file video bằng OpenCV. `cap` là đối tượng video capture. - cap = cv2.VideoCapture(video_path) - # Kiểm tra xem video có được mở thành công không. - if not cap.isOpened(): - print(f"Lỗi: Không thể mở file video tại '{video_path}'") - return - - print("Đang xử lý video... Nhấn 'q' trên cửa sổ video để thoát.") - # Bắt đầu vòng lặp vô hạn để đọc từng khung hình của video. - while cap.isOpened(): - # Ghi lại thời gian bắt đầu xử lý khung hình. - start_time = time.time() - # Đọc một khung hình. `ret` là True nếu đọc thành công, `frame` là chính khung hình đó. - ret, frame = cap.read() - # Nếu `ret` là False, có nghĩa là đã hết video. - if not ret: - print("Đã xử lý xong video.") - break - - # Gọi hàm xử lý cốt lõi cho khung hình hiện tại. Dùng flag=2 để vẽ ô vuông. - result = detect_and_process_faces(frame, flag=2) - - # Kiểm tra kết quả trả về. Nếu là tuple, nghĩa là xử lý thành công. - if isinstance(result, tuple): - _, drawn_frame = result # Chỉ lấy ảnh đã vẽ, bỏ qua dict tọa độ. - else: - drawn_frame = frame # Nếu có lỗi, hiển thị khung hình gốc. - - # Tính toán thời gian xử lý và FPS (số khung hình trên giây). - processing_time = time.time() - start_time - fps = 1 / processing_time if processing_time > 0 else 0 - - # Viết chữ FPS lên góc trên bên trái của khung hình. - cv2.putText(drawn_frame, f"FPS: {int(fps)}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) - # Hiển thị khung hình đã xử lý trong một cửa sổ có tên 'Video Face Detection'. - cv2.imshow('Video Face Detection', drawn_frame) - - # Chờ 1 mili giây. Nếu trong lúc đó người dùng nhấn phím 'q', vòng lặp sẽ dừng. - if cv2.waitKey(1) & 0xFF == ord('q'): - break - - # Sau khi vòng lặp kết thúc, giải phóng đối tượng video. - cap.release() - # Đóng tất cả các cửa sổ của OpenCV. - cv2.destroyAllWindows() - - -def process_webcam(): - """Hàm ứng dụng: Dùng hàm `detect_and_process_faces` để xử lý hình ảnh từ webcam.""" - # Mở webcam mặc định của máy tính. `0` là chỉ số của webcam đầu tiên. - cap = cv2.VideoCapture(0) - # Kiểm tra xem webcam có kết nối được không. - if not cap.isOpened(): - print("Lỗi: Không thể kết nối với webcam.") - return - - print("Đã kết nối webcam. Nhấn 'q' trên cửa sổ video để thoát.") - # Bắt đầu vòng lặp để lấy hình ảnh từ webcam. - while True: - start_time = time.time() - # Đọc một khung hình từ webcam. - ret, frame = cap.read() - if not ret: - print("Lỗi: Mất kết nối với webcam.") - break - - # Lật ảnh webcam theo chiều ngang (trục y, số 1). - # Điều này tạo hiệu ứng giống như nhìn vào gương, tự nhiên hơn cho người dùng. - frame = cv2.flip(frame, 1) - - # Gọi hàm xử lý cốt lõi, logic y hệt như xử lý video. - result = detect_and_process_faces(frame, flag=2) - - if isinstance(result, tuple): - _, drawn_frame = result - else: - drawn_frame = frame - - # Tính toán và hiển thị FPS. - processing_time = time.time() - start_time - fps = 1 / processing_time if processing_time > 0 else 0 - cv2.putText(drawn_frame, f"FPS: {int(fps)}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) - - # Hiển thị kết quả lên màn hình. - cv2.imshow('Webcam Face Detection', drawn_frame) - - # Chờ nhấn phím 'q' để thoát. - if cv2.waitKey(1) & 0xFF == ord('q'): - break - - # Giải phóng webcam và đóng cửa sổ. - cap.release() - cv2.destroyAllWindows() \ No newline at end of file diff --git a/src/embeddings/compare_embeddings.py b/src/embeddings/compare_embeddings.py deleted file mode 100644 index 02ebc8c..0000000 --- a/src/embeddings/compare_embeddings.py +++ /dev/null @@ -1,20 +0,0 @@ -import numpy as np -from scipy.spatial.distance import cosine -from config import THRESHOLD - -def compare_embeddings(input_embedding, stored_embeddings, stored_labels, threshold=THRESHOLD): - - min_dist = float("inf") - best_match = "Unknown" - - for emb, label in zip(stored_embeddings, stored_labels): - dist = cosine(input_embedding, emb) - if dist < min_dist: - min_dist = dist - best_match = label - - if min_dist > threshold: - best_match = "Unknown" - - return best_match, min_dist - diff --git a/src/embeddings/extract_embeddings.py b/src/embeddings/extract_embeddings.py index ed4a7ab..d95f728 100644 --- a/src/embeddings/extract_embeddings.py +++ b/src/embeddings/extract_embeddings.py @@ -1,18 +1,31 @@ import os import numpy as np import pickle -from deepface import DeepFace from tqdm import tqdm import sys +import cv2 +from facenet_pytorch import MTCNN, InceptionResnetV1 +from PIL import Image + +# Thêm đường dẫn đến thư mục chứa config base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -sys.path.append(f'{base_dir}/src') # Thêm đúng thư mục chứa module -from config.config import PROCESSED_PATH, EMBEDDINGS_PATH - -def extract_all_embeddings(processed_root=PROCESSED_PATH, save_path=EMBEDDINGS_PATH, model_name="Facenet"): - """ - Duyệt qua tất cả thư mục con (mỗi người), trích xuất embedding từ từng ảnh, - gắn nhãn tương ứng và lưu vào file pickle. - """ +sys.path.append(f'{base_dir}/src') +try: + from config.config import PROCESSED_PATH, EMBEDDINGS_PATH +except ImportError as e: + print(f"Lỗi khi import config: {e}") + sys.exit(1) + +# Khởi tạo MTCNN (phát hiện khuôn mặt) và InceptionResnetV1 (trích xuất embedding) +try: + mtcnn = MTCNN(image_size=160, margin=0, min_face_size=20, device='cpu') + resnet = InceptionResnetV1(pretrained='vggface2').eval().to('cpu') +except Exception as e: + print(f"Lỗi khi khởi tạo mô hình: {e}") + sys.exit(1) + + +def extract_all_embeddings(processed_root=PROCESSED_PATH, save_path=EMBEDDINGS_PATH): all_embeddings = [] labels = [] @@ -25,18 +38,31 @@ def extract_all_embeddings(processed_root=PROCESSED_PATH, save_path=EMBEDDINGS_P for img_file in tqdm(image_files, desc=f"📸 {person}", leave=False): img_path = os.path.join(person_path, img_file) try: - embedding = DeepFace.represent(img_path=img_path, model_name=model_name)[0]["embedding"] - all_embeddings.append(embedding) - labels.append(person) + # Đọc và chuyển ảnh sang định dạng PIL + img = Image.open(img_path).convert('RGB') + + # Phát hiện khuôn mặt bằng MTCNN + boxes, probs = mtcnn.detect(img) + if boxes is not None and probs[0] > 0.9: # Chỉ lấy khuôn mặt có độ tin cậy cao + # Trích xuất khuôn mặt và embedding + face = mtcnn(img) # Tự động căn chỉnh khuôn mặt + if face is not None: + embedding = resnet(face.unsqueeze(0)).detach().cpu().numpy()[0] + all_embeddings.append(embedding) + labels.append(person) + else: + print(f"⚠️ Không trích xuất được khuôn mặt từ {img_file}") + else: + print(f"⚠️ Không tìm thấy khuôn mặt trong {img_file}") except Exception as e: print(f"⚠️ Lỗi khi xử lý ảnh {img_file} của {person}: {e}") - # Lưu kết quả + # Lưu kết quả vào file .pkl with open(save_path, "wb") as f: pickle.dump({"embeddings": all_embeddings, "labels": labels}, f) print(f"\n✅ Đã lưu {len(all_embeddings)} embeddings vào '{save_path}'") -# Cho phép chạy trực tiếp + +# Chạy trực tiếp if __name__ == "__main__": extract_all_embeddings() - diff --git a/src/embeddings/test.py b/src/embeddings/test.py index 9d30e85..51dc8d0 100644 --- a/src/embeddings/test.py +++ b/src/embeddings/test.py @@ -1,9 +1,9 @@ import os import pickle from extract_embeddings import extract_all_embeddings - +base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # Đường dẫn tương đối từ src/embeddings -pkl_file = os.path.join("..", "..", "data", "embeddings", "all_embeddings.pkl") +pkl_file = os.path.join(base_dir, "data", "embeddings", "all_embeddings.pkl") print("Thư mục làm việc hiện tại:", os.getcwd()) print("Đường dẫn file:", os.path.abspath(pkl_file)) diff --git a/src/gui/main_app.py b/src/gui/main_app.py index 8b13789..997744d 100644 --- a/src/gui/main_app.py +++ b/src/gui/main_app.py @@ -1 +1,47 @@ +import sys +import os +import cv2 +import numpy as np +base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(f'{base_dir}/src') +from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QFileDialog, QHBoxLayout +from video_processing.process_video import process_webcam, process_video_file +class VideoProcessingApp(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Face Recognition GUI") + self.setGeometry(200, 200, 400, 200) + + self.label = QLabel("Chọn chế độ xử lý video", self) + + self.btn_webcam = QPushButton("Xử lý từ Webcam", self) + self.btn_video = QPushButton("Xử lý từ File Video", self) + + self.btn_webcam.clicked.connect(self.run_webcam) + self.btn_video.clicked.connect(self.select_video_file) + + layout = QVBoxLayout() + layout.addWidget(self.label) + layout.addWidget(self.btn_webcam) + layout.addWidget(self.btn_video) + + self.setLayout(layout) + + def run_webcam(self): + self.label.setText("Đang chạy webcam...") + process_webcam() + self.label.setText("Xong webcam.") + + def select_video_file(self): + file_name, _ = QFileDialog.getOpenFileName(self, "Chọn file video", "", "Video Files (*.mp4 *.avi)") + if file_name: + self.label.setText(f"Đang xử lý: {file_name}") + process_video_file(file_name) + self.label.setText("Xong video.") + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = VideoProcessingApp() + window.show() + sys.exit(app.exec_()) diff --git a/src/gui/test.py b/src/gui/test.py new file mode 100644 index 0000000..92d6193 --- /dev/null +++ b/src/gui/test.py @@ -0,0 +1,82 @@ +import cv2 +import numpy as np +import tkinter as tk +from PIL import Image, ImageTk +import time +import os, sys +base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(f'{base_dir}/src') +from recognition.recognize_faces import load_reference_embeddings, extract_embedding, recognize_faces +from detection.detect_faces import detect_and_process_faces +from config.config import THRESHOLD + +class FaceRecognitionApp: + def __init__(self, root): + self.root = root + self.root.title("Webcam Face Recognition") + self.video_label = tk.Label(root) + self.video_label.pack() + + self.cap = cv2.VideoCapture(0) + if not self.cap.isOpened(): + print("Không thể mở webcam") + return + + self.reference_embeddings, self.reference_labels = load_reference_embeddings() + self.update_frame() + + def update_frame(self): + ret, frame = self.cap.read() + if not ret: + print("Không lấy được frame từ webcam") + self.root.after(10, self.update_frame) + return + + frame = cv2.flip(frame, 1) + result = detect_and_process_faces(frame, flag=4) + + if result is not None: + coords_dict, frame_with_boxes, faces_dict = result + recog_results = {} + + for face_id, face_img in faces_dict.items(): + embedding = extract_embedding(face_img) + if embedding is not None: + name, score = recognize_faces(embedding, self.reference_embeddings, self.reference_labels) + else: + name, score = "Unknown", 0.0 + recog_results[face_id] = (name, score) + + for face_id, (name, score) in recog_results.items(): + if face_id not in coords_dict: + continue + x1, y1, x2, y2 = coords_dict[face_id] + color = (0, 255, 0) if name != "Unknown" else (0, 0, 255) + label = f"{name} ({score:.1f}%)" if name != "Unknown" else "Unknown" + + cv2.rectangle(frame_with_boxes, (x1, y1), (x2, y2), color, 2) + text_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2)[0] + cv2.rectangle(frame_with_boxes, (x1, y1 - text_size[1] - 10), + (x1 + text_size[0], y1), color, -1) + cv2.putText(frame_with_boxes, label, (x1, y1 - 5), + cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 2) + else: + frame_with_boxes = frame + + # Convert frame to ImageTk format + rgb_frame = cv2.cvtColor(frame_with_boxes, cv2.COLOR_BGR2RGB) + img = Image.fromarray(rgb_frame) + imgtk = ImageTk.PhotoImage(image=img) + + self.video_label.imgtk = imgtk + self.video_label.configure(image=imgtk) + self.root.after(10, self.update_frame) + + def __del__(self): + if self.cap.isOpened(): + self.cap.release() + +if __name__ == "__main__": + root = tk.Tk() + app = FaceRecognitionApp(root) + root.mainloop() diff --git a/src/gui/test2.py b/src/gui/test2.py new file mode 100644 index 0000000..ee72ca9 --- /dev/null +++ b/src/gui/test2.py @@ -0,0 +1,110 @@ +import tkinter as tk +from tkinter import filedialog +from PIL import Image, ImageTk +import threading +import cv2 +import time +import os, sys +base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(f'{base_dir}/src') +# Giả sử bạn đã có các hàm này +from recognition.recognize_faces import load_reference_embeddings, extract_embedding, recognize_faces +from detection.detect_faces import detect_and_process_faces + +class FaceRecognitionApp: + def __init__(self, root): + self.root = root + self.root.title("Face Recognition GUI") + + self.video_source = None + self.cap = None + self.running = False + + # Load embeddings + self.reference_embeddings, self.reference_labels = load_reference_embeddings() + + # Tạo canvas hiển thị video + self.canvas = tk.Label(self.root) + self.canvas.pack() + + # Nút điều khiển + btn_frame = tk.Frame(self.root) + btn_frame.pack(pady=10) + + tk.Button(btn_frame, text="Start Webcam", command=self.start_webcam).pack(side=tk.LEFT, padx=5) + tk.Button(btn_frame, text="Browse Video", command=self.browse_video).pack(side=tk.LEFT, padx=5) + tk.Button(btn_frame, text="Stop", command=self.stop).pack(side=tk.LEFT, padx=5) + + def start_webcam(self): + self.stop() + self.cap = cv2.VideoCapture(0) + self.running = True + self.process_frame() + + def browse_video(self): + file_path = filedialog.askopenfilename(filetypes=[("Video files", "*.mp4;*.avi")]) + if file_path: + self.stop() + self.cap = cv2.VideoCapture(file_path) + self.running = True + self.process_frame() + + def stop(self): + self.running = False + if self.cap: + self.cap.release() + self.cap = None + + def process_frame(self): + if not self.running or self.cap is None: + return + + ret, frame = self.cap.read() + if not ret: + self.stop() + return + + frame = cv2.flip(frame, 1) + + result = detect_and_process_faces(frame, flag=4) + if result: + coords_dict, frame_with_boxes, faces_dict = result + + recog_results = {} + for face_id, face_img in faces_dict.items(): + embedding = extract_embedding(face_img) + if embedding is not None: + name, score = recognize_faces(embedding, self.reference_embeddings, self.reference_labels) + else: + name, score = "Unknown", 0.0 + recog_results[face_id] = (name, score) + + for face_id, (name, score) in recog_results.items(): + if face_id in coords_dict: + x1, y1, x2, y2 = coords_dict[face_id] + color = (0, 255, 0) if name != "Unknown" else (0, 0, 255) + label = f"{name} ({score:.1f}%)" if name != "Unknown" else "Unknown" + cv2.rectangle(frame_with_boxes, (x1, y1), (x2, y2), color, 2) + cv2.putText(frame_with_boxes, label, (x1, y1 - 10), + cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2) + else: + frame_with_boxes = frame + + # Hiển thị ảnh + img = cv2.cvtColor(frame_with_boxes, cv2.COLOR_BGR2RGB) + img = cv2.resize(img, (800, 600)) + img = Image.fromarray(img) + imgtk = ImageTk.PhotoImage(image=img) + + self.canvas.imgtk = imgtk + self.canvas.config(image=imgtk) + + # Tiếp tục đọc frame + self.root.after(10, self.process_frame) + + +if __name__ == "__main__": + root = tk.Tk() + app = FaceRecognitionApp(root) + root.protocol("WM_DELETE_WINDOW", app.stop) + root.mainloop() diff --git a/src/preprocessing/yolov11n-face.pt b/src/preprocessing/yolov11n-face.pt deleted file mode 100644 index 2059cd2..0000000 Binary files a/src/preprocessing/yolov11n-face.pt and /dev/null differ diff --git a/src/recognition/recognize_faces.py b/src/recognition/recognize_faces.py index 3f5a8f4..309b5d3 100644 --- a/src/recognition/recognize_faces.py +++ b/src/recognition/recognize_faces.py @@ -1,6 +1,9 @@ import numpy as np from deepface import DeepFace import os +import pickle +import logging +import cv2 # Đường dẫn tuyệt đối tới thư mục embeddings @@ -9,53 +12,169 @@ # EMBEDDINGS_DIR = "data/embeddings/" MODEL_NAME = "Facenet" -SIMILARITY_THRESHOLD = 0.6 # Điều chỉnh theo độ chính xác mong muốn +SIMILARITY_THRESHOLD = 0.75 # Tăng ngưỡng vì khoảng cách lớn hơn 0.8 def load_reference_embeddings(): - embeddings = {} - for file in os.listdir(EMBEDDINGS_DIR): - if file.endswith(".npz"): - data = np.load(os.path.join(EMBEDDINGS_DIR, file)) - for key in data.files: - person_name = os.path.splitext(file)[0] + "_" + key # ví dụ: caonam_0.jpg - embeddings[person_name] = data[key] - return embeddings + """ + Load embeddings và labels từ file pickle + Returns: + tuple: (embeddings, labels) hoặc (None, None) nếu có lỗi + """ + try: + # Đường dẫn tới file embeddings + embeddings_file = os.path.join(EMBEDDINGS_DIR, "all_embeddings.pkl") + + if not os.path.exists(embeddings_file): + logging.error(f"Không tìm thấy file embeddings tại: {embeddings_file}") + return None, None + + with open(embeddings_file, 'rb') as f: + data = pickle.load(f) + + if 'embeddings' not in data or 'labels' not in data: + logging.error("File embeddings không đúng định dạng") + return None, None + + # Chuẩn hóa tất cả embeddings + embeddings = np.array(data['embeddings']) + embeddings = embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True) + + logging.info(f"Đã load {len(embeddings)} embeddings mẫu") + return embeddings, data['labels'] + + except Exception as e: + logging.error(f"Lỗi khi load embeddings: {str(e)}") + return None, None + +def preprocess_face(face_img): + """ + Tiền xử lý ảnh khuôn mặt trước khi trích xuất embedding + """ + try: + # Chuyển về grayscale nếu là ảnh màu + if len(face_img.shape) == 3: + face_img = cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB) + + # Resize về kích thước chuẩn + face_img = cv2.resize(face_img, (160, 160)) + + # Chuẩn hóa pixel values về [-1, 1] + face_img = face_img.astype(np.float32) / 255.0 + face_img = (face_img - 0.5) * 2 + + return face_img + except Exception as e: + logging.error(f"Lỗi khi tiền xử lý ảnh: {e}") + return None + + +# def extract_embedding(face_image): +# """ +# Trích xuất embedding từ ảnh đầu vào (ảnh mặt đã cắt) +# """ +# try: +# # Tiền xử lý ảnh +# processed_face = preprocess_face(face_image) +# if processed_face is None: +# return None + +# # Trích xuất embedding +# result = DeepFace.represent( +# processed_face, +# model_name=MODEL_NAME, +# enforce_detection=False, +# detector_backend='skip' # Bỏ qua bước detect vì ảnh đã được cắt +# ) + +# if result and isinstance(result, list) and len(result) > 0: +# embedding = np.array(result[0]['embedding']) + +# # Chuẩn hóa embedding +# embedding = embedding / np.linalg.norm(embedding) + +# logging.info(f"Trích xuất embedding thành công, shape: {embedding.shape}") +# return embedding +# else: +# logging.warning("Không thể trích xuất embedding từ ảnh") +# return None + +# except Exception as e: +# logging.error(f"Lỗi khi trích xuất embedding: {e}") +# return None +MODEL_NAME = "Facenet512" # Hoặc "ArcFace" nếu bạn dùng model khác def extract_embedding(face_image): - #Trích xuất embedding từ ảnh đầu vào (ảnh mặt đã cắt) + """ + Trích xuất embedding từ ảnh đầu vào (ảnh mặt đã cắt) + """ try: - result = DeepFace.represent(face_image, model_name=MODEL_NAME, enforce_detection=False) - if result: - return np.array(result[0]['embedding']) + processed_face = preprocess_face(face_image) + if processed_face is None: + return None + + result = DeepFace.represent( + processed_face, + model_name=MODEL_NAME, + enforce_detection=False, + detector_backend='skip' + ) + + if result and isinstance(result, list) and len(result) > 0: + embedding = np.array(result[0]['embedding']) + + # Chuẩn hóa embedding + embedding = embedding / np.linalg.norm(embedding) + + logging.info(f"Trích xuất embedding thành công, shape: {embedding.shape}") + return embedding else: + logging.warning("Không thể trích xuất embedding từ ảnh") return None + except Exception as e: - print(f"[RECOGNITION] Lỗi khi trích xuất embedding: {e}") + logging.error(f"Lỗi khi trích xuất embedding: {e}") return None -def recognize_faces(cropped_faces_dict, reference_embeddings): - #So sánh các khuôn mặt đã cắt với embeddings mẫu. Trả về dict kết quả: {person_1: (name, % giống nhau), ...} - results = {} - - for face_id, face_image in cropped_faces_dict.items(): - embedding = extract_embedding(face_image) - if embedding is None: - results[face_id] = ("unknown", 0.0) - continue - - best_match = "unknown" - best_score = float("inf") +def cosine_similarity(a, b): + """ + Tính độ tương đồng cosine giữa hai vector + """ + return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) - for name, ref_emb in reference_embeddings.items(): - distance = np.linalg.norm(embedding - ref_emb) - if distance < SIMILARITY_THRESHOLD and distance < best_score: - best_match = name - best_score = distance - similarity = max(0, 100 * (1 - best_score)) if best_match != "unknown" else 0.0 - results[face_id] = (best_match, round(similarity, 1)) +def recognize_faces(embedding, reference_embeddings, reference_labels): + """ + So sánh embedding với các embeddings mẫu + Args: + embedding: embedding cần so sánh + reference_embeddings: list các embeddings mẫu + reference_labels: list các nhãn tương ứng + Returns: + tuple: (tên người, độ tương đồng) + """ + if embedding is None or reference_embeddings is None or reference_labels is None: + return "Unknown", 0.0 - return results + # Tính độ tương đồng cosine với tất cả embeddings mẫu + similarities = np.dot(reference_embeddings, embedding) + + # Tìm độ tương đồng lớn nhất và index tương ứng + max_similarity_idx = np.argmax(similarities) + max_similarity = similarities[max_similarity_idx] + + # Chuyển đổi độ tương đồng thành phần trăm + similarity_percent = max_similarity * 100 + + # Log thông tin debug + logging.info(f"Độ tương đồng lớn nhất: {max_similarity:.4f}, Ngưỡng: {SIMILARITY_THRESHOLD}") + logging.info(f"Label tương ứng: {reference_labels[max_similarity_idx]}") + + # Nếu độ tương đồng lớn hơn ngưỡng + if max_similarity > SIMILARITY_THRESHOLD: + return reference_labels[max_similarity_idx], round(similarity_percent, 1) + + # Nếu không nhận diện được, vẫn trả về Unknown với độ tương đồng + return "Unknown", round(similarity_percent, 1) diff --git a/src/video_processing/analyze_results.py b/src/video_processing/analyze_results.py index ff2c7cc..e69de29 100644 --- a/src/video_processing/analyze_results.py +++ b/src/video_processing/analyze_results.py @@ -1,207 +0,0 @@ -import csv -import json -import time -from typing import Dict, List, Optional -import pandas as pd -import numpy as np -from datetime import datetime -import os - -class ResultAnalyzer: - def __init__(self, output_dir: str = "data/results"): - """ - Khởi tạo ResultAnalyzer - - Args: - output_dir: Thư mục lưu kết quả - """ - self.output_dir = output_dir - self.current_session = None - self.session_start_time = None - self.session_data = { - 'faces': {}, # Lưu thông tin theo dõi khuôn mặt - 'frames': [], # Lưu kết quả từng frame - 'statistics': { # Thống kê tổng hợp - 'total_frames': 0, - 'total_faces_detected': 0, - 'unique_faces': 0, - 'start_time': None, - 'end_time': None, - 'duration': 0 - } - } - - # Tạo thư mục output nếu chưa tồn tại - os.makedirs(output_dir, exist_ok=True) - - def start_session(self): - """Bắt đầu một phiên phân tích mới""" - self.current_session = datetime.now().strftime("%Y%m%d_%H%M%S") - self.session_start_time = time.time() - self.session_data = { - 'faces': {}, - 'frames': [], - 'statistics': { - 'total_frames': 0, - 'total_faces_detected': 0, - 'unique_faces': 0, - 'start_time': self.session_start_time, - 'end_time': None, - 'duration': 0 - } - } - - def end_session(self): - """Kết thúc phiên phân tích và lưu kết quả""" - if not self.current_session: - return - - # Cập nhật thống kê - self.session_data['statistics']['end_time'] = time.time() - self.session_data['statistics']['duration'] = ( - self.session_data['statistics']['end_time'] - - self.session_data['statistics']['start_time'] - ) - self.session_data['statistics']['unique_faces'] = len(self.session_data['faces']) - - # Lưu kết quả - self._save_results() - - # Reset session - self.current_session = None - self.session_start_time = None - - def update_frame(self, frame_results: Dict): - """ - Cập nhật kết quả từ một frame - - Args: - frame_results: Kết quả từ VideoProcessor.process_frame - """ - if not self.current_session: - self.start_session() - - # Cập nhật thống kê - self.session_data['statistics']['total_frames'] += 1 - self.session_data['statistics']['total_faces_detected'] += len(frame_results['faces']) - - # Lưu kết quả frame - frame_data = { - 'timestamp': frame_results['timestamp'], - 'faces': frame_results['faces'] - } - self.session_data['frames'].append(frame_data) - - # Cập nhật thông tin khuôn mặt - for face in frame_results['faces']: - face_id = face['id'] - if face_id not in self.session_data['faces']: - self.session_data['faces'][face_id] = { - 'id': face_id, - 'label': face['label'], - 'first_seen': frame_results['timestamp'], - 'last_seen': frame_results['timestamp'], - 'appearances': 1, - 'boxes': [face['box']], - 'confidences': [face['confidence']] - } - else: - face_data = self.session_data['faces'][face_id] - face_data['last_seen'] = frame_results['timestamp'] - face_data['appearances'] += 1 - face_data['boxes'].append(face['box']) - face_data['confidences'].append(face['confidence']) - - def get_statistics(self) -> Dict: - """ - Lấy thống kê hiện tại - - Returns: - Dict chứa thống kê - """ - return self.session_data['statistics'] - - def get_face_statistics(self) -> Dict: - """ - Lấy thống kê về các khuôn mặt - - Returns: - Dict chứa thống kê khuôn mặt - """ - return { - face_id: { - 'label': data['label'], - 'appearances': data['appearances'], - 'duration': data['last_seen'] - data['first_seen'], - 'avg_confidence': np.mean(data['confidences']) - } - for face_id, data in self.session_data['faces'].items() - } - - def _save_results(self): - """Lưu kết quả phân tích""" - if not self.current_session: - return - - # Tạo thư mục cho session - session_dir = os.path.join(self.output_dir, self.current_session) - os.makedirs(session_dir, exist_ok=True) - - # Lưu thống kê - with open(os.path.join(session_dir, 'statistics.json'), 'w') as f: - json.dump(self.session_data['statistics'], f, indent=4) - - # Lưu thông tin khuôn mặt - with open(os.path.join(session_dir, 'faces.json'), 'w') as f: - json.dump(self.session_data['faces'], f, indent=4) - - # Lưu kết quả frame dưới dạng CSV - frames_df = pd.DataFrame([ - { - 'timestamp': frame['timestamp'], - 'face_id': face['id'], - 'label': face['label'], - 'confidence': face['confidence'], - 'box_x1': face['box'][0], - 'box_y1': face['box'][1], - 'box_x2': face['box'][2], - 'box_y2': face['box'][3] - } - for frame in self.session_data['frames'] - for face in frame['faces'] - ]) - frames_df.to_csv(os.path.join(session_dir, 'frames.csv'), index=False) - - def generate_report(self) -> str: - """ - Tạo báo cáo tổng hợp - - Returns: - String chứa nội dung báo cáo - """ - if not self.current_session: - return "No active session" - - stats = self.session_data['statistics'] - face_stats = self.get_face_statistics() - - report = [ - "=== Face Recognition Analysis Report ===", - f"Session: {self.current_session}", - f"Duration: {stats['duration']:.2f} seconds", - f"Total Frames: {stats['total_frames']}", - f"Total Faces Detected: {stats['total_faces_detected']}", - f"Unique Faces: {stats['unique_faces']}", - "\nFace Statistics:", - ] - - for face_id, data in face_stats.items(): - report.extend([ - f"\nFace {face_id}:", - f" Label: {data['label']}", - f" Appearances: {data['appearances']}", - f" Duration: {data['duration']:.2f} seconds", - f" Average Confidence: {data['avg_confidence']:.2f}" - ]) - - return "\n".join(report) diff --git a/src/video_processing/process_video.py b/src/video_processing/process_video.py index e54bee5..cc3a6da 100644 --- a/src/video_processing/process_video.py +++ b/src/video_processing/process_video.py @@ -1,234 +1,187 @@ +import os +import sys import cv2 import numpy as np -from typing import Tuple, List, Dict, Optional import time -from .utils import draw_face_box, draw_fps, calculate_iou, update_tracking -from .analyze_results import ResultAnalyzer - -class VideoProcessor: - def __init__(self): - """Khởi tạo VideoProcessor với các thành phần cần thiết""" - self.face_detector = None # Sẽ được khởi tạo từ module detection - self.face_recognizer = None # Sẽ được khởi tạo từ module recognition - self.tracking_results = {} # Lưu kết quả theo dõi - self.is_processing = False # Flag để kiểm soát quá trình xử lý - self.frame_count = 0 # Đếm số frame đã xử lý - self.fps = 0 # FPS hiện tại - self.next_face_id = 0 # ID cho khuôn mặt mới - self.tracking_threshold = 0.5 # Ngưỡng IoU để tracking - self.result_analyzer = ResultAnalyzer() # Phân tích kết quả - - def start_processing(self): - """Bắt đầu xử lý video""" - self.is_processing = True - self.frame_count = 0 - self.fps = 0 - self.tracking_results = {} # Reset tracking results - self.next_face_id = 0 - self.result_analyzer.start_session() # Bắt đầu phiên phân tích - - def stop_processing(self): - """Dừng xử lý video""" - self.is_processing = False - self.result_analyzer.end_session() # Kết thúc phiên phân tích - - def process_camera(self, camera_id: int = 0, callback=None): - """ - Xử lý video từ camera - - Args: - camera_id: ID của camera (mặc định là 0 - webcam) - callback: Hàm callback để cập nhật UI - """ - cap = cv2.VideoCapture(camera_id) - if not cap.isOpened(): - raise ValueError(f"Không thể mở camera với ID {camera_id}") - - self.start_processing() +from datetime import datetime +import logging +from collections import defaultdict + +base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(f'{base_dir}/src') + +from recognition.recognize_faces import load_reference_embeddings, extract_embedding, recognize_faces +from detection.detect_faces import detect_and_process_faces +from config.config import THRESHOLD, FACE_DETECTION_CONFIDENCE, VIDEO_PATH, RESULTS_PATH + +# Thiết lập logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +def process_webcam(): + """Kết hợp phát hiện + nhận diện khuôn mặt realtime từ webcam""" + cap = cv2.VideoCapture(0) + if not cap.isOpened(): + logger.error("Lỗi: Không thể kết nối với webcam.") + return + + logger.info("Webcam đang chạy... Nhấn 'q' để thoát.") + + reference_embeddings, reference_labels = load_reference_embeddings() + if reference_embeddings is None or reference_labels is None: + logger.error("Không thể load reference embeddings") + return + + while True: start_time = time.time() + ret, frame = cap.read() + if not ret: + logger.error("Lỗi: Mất kết nối với webcam.") + break + + frame = cv2.flip(frame, 1) + + result = detect_and_process_faces(frame, flag=4) + if result is None: + continue + + coords_dict, frame_with_boxes, faces_dict = result + logger.info(f"Phát hiện được {len(faces_dict)} khuôn mặt") + + # Xử lý từng khuôn mặt riêng biệt + recog_results = {} + for face_id, face_img in faces_dict.items(): + embedding = extract_embedding(face_img) + if embedding is not None: + name, score = recognize_faces(embedding, reference_embeddings, reference_labels) + logger.info(f"Khuôn mặt {face_id}: {name} ({score:.1f}%)") + else: + name, score = "Unknown", 0.0 + logger.warning(f"Không thể trích xuất embedding cho khuôn mặt {face_id}") + recog_results[face_id] = (name, score) + + for face_id, (name, score) in recog_results.items(): + if face_id not in coords_dict: + continue + + x1, y1, x2, y2 = coords_dict[face_id] + color = (0, 255, 0) if name != "Unknown" else (0, 0, 255) + label = f"{name} ({score:.1f}%)" if name != "Unknown" else "Unknown" + + cv2.rectangle(frame_with_boxes, (x1, y1), (x2, y2), color, 2) + text_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2)[0] + cv2.rectangle(frame_with_boxes, (x1, y1 - text_size[1] - 10), + (x1 + text_size[0], y1), color, -1) + cv2.putText(frame_with_boxes, label, (x1, y1 - 5), + cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 2) + + # Tính FPS + processing_time = time.time() - start_time + fps = 1 / processing_time if processing_time > 0 else 0 - try: - while self.is_processing: - ret, frame = cap.read() - if not ret: - break - - # Xử lý frame - processed_frame, results = self.process_frame(frame) - - # Cập nhật FPS - self.frame_count += 1 - elapsed_time = time.time() - start_time - if elapsed_time >= 1.0: - self.fps = self.frame_count / elapsed_time - self.frame_count = 0 - start_time = time.time() - - # Gọi callback nếu có - if callback: - callback(processed_frame, results, self.fps) - - finally: - cap.release() - self.stop_processing() - - def process_video_file(self, video_path: str, callback=None): - """ - Xử lý video từ file - - Args: - video_path: Đường dẫn đến file video - callback: Hàm callback để cập nhật UI - """ - cap = cv2.VideoCapture(video_path) - if not cap.isOpened(): - raise ValueError(f"Không thể mở video file: {video_path}") - - self.start_processing() + # Vẽ FPS + fps_text = f"FPS: {int(fps)}" + cv2.putText(frame_with_boxes, fps_text, (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) + + # Hiển thị frame + cv2.imshow("Webcam Face Recognition", frame_with_boxes) + + if cv2.waitKey(1) & 0xFF == ord('q'): + break + + cap.release() + cv2.destroyAllWindows() + +def process_video_file(video_path): + # Load reference data + reference_embeddings, reference_labels = load_reference_embeddings() + + # Mở file video + cap = cv2.VideoCapture(video_path) + if not cap.isOpened(): + logging.error(f"Không thể mở file video: {video_path}") + return + + while True: + ret, frame = cap.read() + if not ret: + break # hết video + + # Có thể resize hoặc tăng tốc bằng cách skip frame: + # frame = cv2.resize(frame, (width, height)) + # if frame_idx % 3 != 0: continue + + # Cắt ảnh mặt (nếu cần) + result = detect_and_process_faces(frame, flag=4) # bạn cần viết/đã có hàm này + + if result is None: + continue + + coords_dict, frame_with_boxes, faces_dict = result + logger.info(f"Phát hiện được {len(faces_dict)} khuôn mặt") + + # Xử lý từng khuôn mặt riêng biệt + recog_results = {} + for face_id, face_img in faces_dict.items(): + embedding = extract_embedding(face_img) + if embedding is not None: + name, score = recognize_faces(embedding, reference_embeddings, reference_labels) + logger.info(f"Khuôn mặt {face_id}: {name} ({score:.1f}%)") + else: + name, score = "Unknown", 0.0 + logger.warning(f"Không thể trích xuất embedding cho khuôn mặt {face_id}") + recog_results[face_id] = (name, score) + + for face_id, (name, score) in recog_results.items(): + if face_id not in coords_dict: + continue + + x1, y1, x2, y2 = coords_dict[face_id] + color = (0, 255, 0) if name != "Unknown" else (0, 0, 255) + label = f"{name} ({score:.1f}%)" if name != "Unknown" else "Unknown" + + cv2.rectangle(frame_with_boxes, (x1, y1), (x2, y2), color, 2) + text_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2)[0] + cv2.rectangle(frame_with_boxes, (x1, y1 - text_size[1] - 10), + (x1 + text_size[0], y1), color, -1) + cv2.putText(frame_with_boxes, label, (x1, y1 - 5), + cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 2) + + # Tính FPS start_time = time.time() - - try: - while self.is_processing: - ret, frame = cap.read() - if not ret: - break - - # Xử lý frame - processed_frame, results = self.process_frame(frame) - - # Cập nhật FPS - self.frame_count += 1 - elapsed_time = time.time() - start_time - if elapsed_time >= 1.0: - self.fps = self.frame_count / elapsed_time - self.frame_count = 0 - start_time = time.time() - - # Gọi callback nếu có - if callback: - callback(processed_frame, results, self.fps) - - finally: - cap.release() - self.stop_processing() - - def process_frame(self, frame: np.ndarray) -> Tuple[np.ndarray, Dict]: - """ - Xử lý một frame video - - Args: - frame: Frame video cần xử lý - - Returns: - Tuple chứa frame đã xử lý và kết quả nhận diện - """ - if self.face_detector is None or self.face_recognizer is None: - raise ValueError("Face detector hoặc face recognizer chưa được khởi tạo") - - # Lưu frame gốc để vẽ kết quả - processed_frame = frame.copy() - - # Phát hiện khuôn mặt - face_boxes = self.face_detector.detect(frame) - - # Lưu kết quả nhận diện - results = { - 'faces': [], - 'timestamp': time.time() - } - - # Xử lý từng khuôn mặt - for box in face_boxes: - x1, y1, x2, y2 = box - - # Cắt khuôn mặt - face_img = frame[y1:y2, x1:x2] - - # Nhận diện khuôn mặt - label, confidence = self.face_recognizer.recognize(face_img) - - # Tracking khuôn mặt - face_id = self._track_face(box, label, confidence) - - # Vẽ kết quả - processed_frame = draw_face_box(processed_frame, box, label, confidence) - - # Lưu kết quả - results['faces'].append({ - 'id': face_id, - 'box': box, - 'label': label, - 'confidence': confidence - }) + processing_time = time.time() - start_time + fps = 1 / processing_time if processing_time > 0 else 0 # Vẽ FPS - processed_frame = draw_fps(processed_frame, self.fps) - - # Cập nhật kết quả phân tích - self.result_analyzer.update_frame(results) - - return processed_frame, results - - def _track_face(self, box: Tuple[int, int, int, int], - label: str, confidence: float) -> str: - """ - Tracking khuôn mặt dựa trên IoU - - Args: - box: Tọa độ khung - label: Nhãn của khuôn mặt - confidence: Độ tin cậy - - Returns: - ID của khuôn mặt - """ - timestamp = time.time() - - # Tìm khuôn mặt có IoU cao nhất - max_iou = 0 - best_match = None - - for face_id, data in self.tracking_results.items(): - last_box = data['boxes'][-1] - iou = calculate_iou(box, last_box) - - if iou > max_iou and iou > self.tracking_threshold: - max_iou = iou - best_match = face_id - - # Nếu tìm thấy match - if best_match is not None: - face_id = best_match - else: - # Tạo ID mới - face_id = f"face_{self.next_face_id}" - self.next_face_id += 1 - - # Cập nhật tracking - self.tracking_results = update_tracking( - self.tracking_results, - face_id, - box, - label, - confidence, - timestamp - ) - - return face_id - - def set_face_detector(self, detector): - """Set face detector từ module detection""" - self.face_detector = detector - - def set_face_recognizer(self, recognizer): - """Set face recognizer từ module recognition""" - self.face_recognizer = recognizer - - def get_analysis_report(self) -> str: - """ - Lấy báo cáo phân tích - - Returns: - String chứa nội dung báo cáo - """ - return self.result_analyzer.generate_report() + fps_text = f"FPS: {int(fps)}" + cv2.putText(frame_with_boxes, fps_text, (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) + + # Hiển thị frame + cv2.imshow("Webcam Face Recognition", frame_with_boxes) + + if cv2.waitKey(1) & 0xFF == ord('q'): + break + + cap.release() + cv2.destroyAllWindows() + + + + + + + + + + + + + + + + + +if __name__ == "__main__": + process_webcam() \ No newline at end of file diff --git a/src/video_processing/utils.py b/src/video_processing/utils.py index cca3f93..e69de29 100644 --- a/src/video_processing/utils.py +++ b/src/video_processing/utils.py @@ -1,131 +0,0 @@ -import cv2 -import numpy as np -from typing import Tuple, Dict, List -import time - -def draw_face_box(frame: np.ndarray, - box: Tuple[int, int, int, int], - label: str, - confidence: float, - color: Tuple[int, int, int] = (0, 255, 0)) -> np.ndarray: - """ - Vẽ khung và nhãn cho khuôn mặt - - Args: - frame: Frame cần vẽ - box: Tọa độ (x1, y1, x2, y2) của khung - label: Nhãn của khuôn mặt - confidence: Độ tin cậy của nhận diện - color: Màu của khung (BGR) - - Returns: - Frame đã vẽ - """ - x1, y1, x2, y2 = box - - # Vẽ khung - cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) - - # Tạo text với nhãn và độ tin cậy - text = f"{label} ({confidence:.2f})" - - # Tính toán vị trí text - text_size = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2)[0] - text_x = x1 - text_y = y1 - 10 if y1 - 10 > 10 else y1 + 10 - - # Vẽ background cho text - cv2.rectangle(frame, - (text_x, text_y - text_size[1] - 4), - (text_x + text_size[0], text_y + 4), - color, -1) - - # Vẽ text - cv2.putText(frame, text, (text_x, text_y), - cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2) - - return frame - -def draw_fps(frame: np.ndarray, fps: float) -> np.ndarray: - """ - Vẽ FPS lên frame - - Args: - frame: Frame cần vẽ - fps: FPS hiện tại - - Returns: - Frame đã vẽ - """ - text = f"FPS: {fps:.1f}" - cv2.putText(frame, text, (10, 30), - cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) - return frame - -def calculate_iou(box1: Tuple[int, int, int, int], - box2: Tuple[int, int, int, int]) -> float: - """ - Tính IoU (Intersection over Union) giữa 2 khung - - Args: - box1: Khung thứ nhất (x1, y1, x2, y2) - box2: Khung thứ hai (x1, y1, x2, y2) - - Returns: - Giá trị IoU - """ - # Tính tọa độ giao nhau - x1 = max(box1[0], box2[0]) - y1 = max(box1[1], box2[1]) - x2 = min(box1[2], box2[2]) - y2 = min(box1[3], box2[3]) - - # Tính diện tích giao nhau - intersection = max(0, x2 - x1) * max(0, y2 - y1) - - # Tính diện tích của mỗi khung - box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1]) - box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1]) - - # Tính diện tích hợp - union = box1_area + box2_area - intersection - - # Trả về IoU - return intersection / union if union > 0 else 0 - -def update_tracking(tracking_results: Dict, - face_id: str, - box: Tuple[int, int, int, int], - label: str, - confidence: float, - timestamp: float) -> Dict: - """ - Cập nhật kết quả tracking - - Args: - tracking_results: Dict chứa kết quả tracking - face_id: ID của khuôn mặt - box: Tọa độ khung - label: Nhãn của khuôn mặt - confidence: Độ tin cậy - timestamp: Thời điểm phát hiện - - Returns: - Dict tracking_results đã cập nhật - """ - if face_id not in tracking_results: - tracking_results[face_id] = { - 'label': label, - 'first_seen': timestamp, - 'last_seen': timestamp, - 'appearances': 1, - 'boxes': [box], - 'confidences': [confidence] - } - else: - tracking_results[face_id]['last_seen'] = timestamp - tracking_results[face_id]['appearances'] += 1 - tracking_results[face_id]['boxes'].append(box) - tracking_results[face_id]['confidences'].append(confidence) - - return tracking_results diff --git a/yolov8n.pt b/yolov8n.pt deleted file mode 100644 index d61ef50..0000000 Binary files a/yolov8n.pt and /dev/null differ