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 functionsPyQt5: GUI implementationopencv-python: camera-stream processingPillow: image processingnumpy: array and matrix operations
3. System Structure
3.1 Overall structure
I divided the program into three main classes:
AttendanceSystem: main-window classAttendanceTab: attendance-checking tabManagementTab: 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:
- Process recognition every three frames and resize each frame to one quarter size.
- Use HOG-based detection for CPU-friendly recognition and vectorized distance calculations.
- Release unnecessary image objects promptly to keep memory use under control.
- Keep the UI responsive through asynchronous work and lightweight animations.
3.6 Error Handling
For real use, I also needed to handle failures:
- Try several camera indexes and show a useful message when connection fails.
- Degrade gracefully when recognition fails, while logging errors and giving user feedback.
- Check file permissions and handle duplicate files.
- Prevent leaks when processing large images and release resources correctly.
4. How to Use the System
4.1 Initial setup
- Run the program.
- Register faces in the management tab.
- Open the attendance tab and start the system.
4.2 Daily use
- Start the program.
- Click Start Attendance.
- Let the system recognize faces and record attendance automatically.
- Export attendance records when needed.
4.3 Management
- Register new faces.
- Manage existing face information.
- 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.