Back to Blog

Building a Face-Recognition Attendance System with Python

Build Log6 min

Sungbin Kim

1. Project Overview

This is a face-recognition attendance system built with Python's face_recognition library and PyQt5. I wanted to process attendance automatically with a camera rather than a QR code. While building it, I learned that the surrounding features—UI, exception handling, and bulk registration—mattered even more than face recognition itself.

Features

  • Real-time face recognition and attendance checking
  • Registering and managing new faces
  • Saving and exporting attendance records
  • An intuitive graphical user interface
  • Bulk face registration

2. Development Environment

pip install face_recognition
pip install PyQt5
pip install opencv-python
pip install numpy
pip install Pillow

The main libraries are:

  • face_recognition: core face-recognition functions
  • PyQt5: GUI implementation
  • opencv-python: camera-stream processing
  • Pillow: image processing
  • numpy: array and matrix operations

3. System Structure

3.1 Overall structure

I divided the program into three main classes:

  1. AttendanceSystem: main-window class
  2. AttendanceTab: attendance-checking tab
  3. ManagementTab: face-management tab

Each class has a relatively focused responsibility.

3.2 AttendanceSystem

This class owns the main window and the overall UI style.

class AttendanceSystem(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.showMaximized()  # Start maximized

    def initUI(self):
        self.setWindowTitle('Face Recognition Attendance System')
        self.setStyleSheet("""
            QMainWindow { background-color: #f0f0f0; }
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 12px 24px;
                border-radius: 6px;
                font-size: 14px;
                min-width: 120px;
            }
        """)

3.3 AttendanceTab

This is the core class for attendance. It handles the live camera and face-recognition loop.

3.3.1 Initialization and UI

class AttendanceTab(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.initUI()
        self.camera = None
        self.timer = QTimer()
        self.timer.timeout.connect(self.update_frame)
        self.is_running = False
        self.frame_count = 0
        self.known_face_encodings = []
        self.known_face_names = []
        self.load_known_faces()
        self.present_students = set()

3.3.2 Real-time recognition

Frame processing and recognition run in this method. I process every third frame and resize it to one quarter of the original size to improve speed.

def update_frame(self):
    ret, frame = self.camera.read()
    if ret:
        process_this_frame = self.frame_count % 3 == 0
        self.frame_count += 1

        if process_this_frame:
            height, width = frame.shape[:2]
            small_frame = cv2.resize(frame, (width//4, height//4))
            rgb_small_frame = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB)

            try:
                face_locations = face_recognition.face_locations(
                    rgb_small_frame, model="hog"
                )
                if face_locations:
                    face_encodings = face_recognition.face_encodings(
                        rgb_small_frame, face_locations
                    )
                    for (top, right, bottom, left), face_encoding in zip(
                        face_locations, face_encodings
                    ):
                        if self.known_face_encodings:
                            face_distances = face_recognition.face_distance(
                                self.known_face_encodings, face_encoding
                            )
                            best_match_index = np.argmin(face_distances)
                            if face_distances[best_match_index] < 0.6:
                                name = self.known_face_names[best_match_index]
                                self.record_attendance(name)
                                self.show_notification(name)
            except Exception as e:
                print(f"Recognition error: {str(e)}")

3.3.3 Recording attendance

The attendance method saves a record and updates the interface. It also prevents duplicate records during the same session.

def record_attendance(self, name):
    try:
        current_time = datetime.datetime.now()
        time_string = current_time.strftime('%H:%M:%S')
        if name in self.present_students:
            return

        faces_dir = "faces"
        image_path = os.path.join(faces_dir, f"{name}.jpg")
        if os.path.exists(image_path):
            pixmap = QPixmap(image_path)
            pixmap = pixmap.scaled(
                250, 250, Qt.KeepAspectRatio, Qt.SmoothTransformation
            )
            self.recent_face_label.setPixmap(pixmap)
            self.status_label.setText(f'✓ Attendance confirmed for {name}.')
            self.update_attendance_table(name, time_string, pixmap)
            self.save_attendance_record(name, time_string)
            self.present_students.add(name)
            self.update_absent_list()
    except Exception as e:
        print(f"Error recording attendance: {str(e)}")

3.4 ManagementTab

This class handles face registration and management. I implemented both single registration and bulk registration.

3.4.1 Registering one face

def register_face(self):
    try:
        file_name, _ = QFileDialog.getOpenFileName(
            self, "Choose a face image", "",
            "Image files (*.jpg *.jpeg *.png)"
        )
        if file_name:
            name, ok = QInputDialog.getText(
                self, 'Enter a name', 'Enter the name to register:'
            )
            if ok and name:
                faces_dir = "faces"
                if not os.path.exists(faces_dir):
                    os.makedirs(faces_dir)
                new_path = os.path.join(faces_dir, f"{name}.jpg")
                counter = 1
                while os.path.exists(new_path):
                    new_path = os.path.join(faces_dir, f"{name}_{counter}.jpg")
                    counter += 1
                image = Image.open(file_name).convert('RGB')
                image.save(new_path, 'JPEG', quality=95)
                self.known_faces.append((name, new_path))
                self.update_face_grid()
                QMessageBox.information(
                    self, 'Registration complete', f'Face registered for {name}.'
                )
    except Exception as e:
        QMessageBox.warning(self, 'Error', f'Registration failed: {str(e)}')

3.4.2 Bulk registration

Bulk registration walks through a folder, skips duplicates, checks whether a face can be detected, and reports successful, skipped, and failed files.

def bulk_register_faces(self):
    try:
        folder_path = QFileDialog.getExistingDirectory(
            self, "Choose a folder containing images"
        )
        if folder_path:
            success_count = 0
            skip_count = 0
            fail_count = 0
            error_files = []
            valid_extensions = ('.jpg', '.jpeg', '.png')

            for filename in os.listdir(folder_path):
                if filename.lower().endswith(valid_extensions):
                    try:
                        file_path = os.path.join(folder_path, filename)
                        name = os.path.splitext(filename)[0]
                        existing_path = os.path.join(faces_dir, f"{name}.jpg")
                        if os.path.exists(existing_path):
                            skip_count += 1
                            continue
                        image = face_recognition.load_image_file(file_path)
                        face_locations = face_recognition.face_locations(image)
                        if face_locations:
                            img = Image.open(file_path).convert('RGB')
                            img.save(new_path, 'JPEG', quality=95)
                            success_count += 1
                        else:
                            fail_count += 1
                            error_files.append(f"{filename} (no face detected)")
                    except Exception as e:
                        fail_count += 1
                        error_files.append(f"{filename} (error: {str(e)})")
            result_message = (
                f"Registration complete:\n\n"
                f"Success: {success_count}\n"
                f"Skipped: {skip_count}\n"
                f"Failed: {fail_count}"
            )
            QMessageBox.information(self, 'Bulk registration complete', result_message)
    except Exception as e:
        QMessageBox.warning(self, 'Error', f'Bulk registration failed: {str(e)}')

3.5 Performance Optimization

Real-time camera processing was heavier than expected, so I added several optimizations:

  1. Process recognition every three frames and resize each frame to one quarter size.
  2. Use HOG-based detection for CPU-friendly recognition and vectorized distance calculations.
  3. Release unnecessary image objects promptly to keep memory use under control.
  4. Keep the UI responsive through asynchronous work and lightweight animations.

3.6 Error Handling

For real use, I also needed to handle failures:

  1. Try several camera indexes and show a useful message when connection fails.
  2. Degrade gracefully when recognition fails, while logging errors and giving user feedback.
  3. Check file permissions and handle duplicate files.
  4. Prevent leaks when processing large images and release resources correctly.

4. How to Use the System

4.1 Initial setup

  1. Run the program.
  2. Register faces in the management tab.
  3. Open the attendance tab and start the system.

4.2 Daily use

  1. Start the program.
  2. Click Start Attendance.
  3. Let the system recognize faces and record attendance automatically.
  4. Export attendance records when needed.

4.3 Management

  1. Register new faces.
  2. Manage existing face information.
  3. Manage and back up attendance records.

5. Limitations and Improvement Plan

5.1 Features

  • Improve recognition accuracy with a deep-learning model
  • Add real-time statistics and charts
  • Connect a database
  • Add a web interface

5.2 Performance

  • Add GPU acceleration
  • Improve multithreaded processing
  • Optimize memory use

5.3 User experience

  • Make the UI/UX more intuitive
  • Add multilingual support
  • Add customization options

The full source is available on GitHub. If I rebuilt it now, I would separate the code more carefully and design more thorough recognition tests and registration flows. This project taught me that computer vision is not only a model problem; the real usage flow has to be designed too.