Project Overview
Traditional attendance systems — RFID cards, PIN codes, manual registers — are vulnerable to proxy attendance (one person marking for another). This project replaces them with a biometric face recognition system that simultaneously verifies identity and detects spoofing attempts in real time.
Problem Statement
Proxy attendance is a widespread issue in institutions and workplaces. A colleague can swipe a card or enter a PIN on behalf of an absent person. Even early face recognition systems were fooled by simply holding a printed photo in front of the camera.
The solution needed to: (1) accurately identify faces, (2) detect if the face is real or a spoof (photo/screen/mask), and (3) run fast enough for a real-time attendance gate.
System Architecture
Face Detection with MTCNN
MTCNN runs three cascaded networks (P-Net → R-Net → O-Net) to detect faces at multiple scales. It outputs bounding boxes and 5 facial landmarks (eyes, nose, mouth corners) used for alignment before embedding.
from mtcnn import MTCNN
import cv2, numpy as np
detector = MTCNN()
def detect_and_align(frame):
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = detector.detect_faces(rgb)
faces = []
for r in results:
x, y, w, h = r['box']
x, y = max(0, x), max(0, y)
face = rgb[y:y+h, x:x+w]
face = cv2.resize(face, (160, 160)) # FaceNet input size
faces.append((face, r['box'], r['confidence']))
return facesFace Recognition with FaceNet
FaceNet maps face images to a 128-dimensional embedding space where same-person faces cluster together. We use cosine similarity against stored embeddings — a threshold of 0.6 gives the best precision/recall tradeoff on our dataset.
from keras_facenet import FaceNet
from sklearn.svm import SVC
from sklearn.preprocessing import LabelEncoder
import pickle, numpy as np
embedder = FaceNet()
def get_embedding(face_img):
face_img = face_img.astype('float32')
mean, std = face_img.mean(), face_img.std()
face_img = (face_img - mean) / std # whitening
samples = np.expand_dims(face_img, axis=0)
return embedder.embeddings(samples)[0] # 128-d vector
# Training the SVM classifier
X_embeds = [get_embedding(f) for f in face_images]
le = LabelEncoder()
y_encoded = le.fit_transform(labels)
clf = SVC(kernel='linear', probability=True, C=1.0)
clf.fit(X_embeds, y_encoded)
with open('face_classifier.pkl','wb') as f:
pickle.dump((clf, le), f)Anti-Spoofing with LBP
Local Binary Patterns (LBP) capture micro-texture differences between real skin and printed/screen surfaces. Real faces have irregular, 3D texture; photos are flat and uniform. LBP encodes each pixel relative to its 8 neighbours into a binary histogram.
from skimage.feature import local_binary_pattern
from sklearn.svm import SVC
import numpy as np, cv2
def extract_lbp_features(face_gray, P=8, R=1):
lbp = local_binary_pattern(face_gray, P, R, method='uniform')
n_bins = P + 2
hist, _ = np.histogram(lbp.ravel(), bins=n_bins,
range=(0, n_bins), density=True)
return hist # 10-dimensional feature vector
# Collect real & spoof samples, train SVM
X_lbp = [extract_lbp_features(cv2.cvtColor(f, cv2.COLOR_RGB2GRAY))
for f in all_face_samples]
y_liveness = [1]*n_real + [0]*n_spoof # 1=real, 0=spoof
spoof_clf = SVC(kernel='rbf', probability=True, C=10, gamma='scale')
spoof_clf.fit(X_lbp, y_liveness)Results & Evaluation
Tested on 50 registered identities with 200 real and 200 spoof samples (printed photos, phone screens, and laptop screens):
Key finding: LBP outperformed deep liveness detection models (MobileNet-based) in speed at only marginal accuracy cost — critical for real-time gate deployment on CPU-only hardware.
Tech Stack
- Face Detection: MTCNN
- Face Embedding: FaceNet (keras-facenet)
- Identity Classifier: SVM (scikit-learn)
- Liveness Detection: LBP + SVM
- Camera Interface: OpenCV
- Database: SQLite with pandas export
- Dashboard: Streamlit (attendance reports, real-time feed)