Project: Real-Time Face Mask Detection with OpenCV and PyTorch
This project chains two models: a fast face detector finds faces, and a small fine-tuned CNN labels each crop as mask or no mask, frame by frame.
System design
Two stages keep each model simple. Stage one finds face boxes. Stage two classifies each cropped face. MobileNetV2 is light enough for CPU real-time use. Use a public dataset such as the Kaggle Face Mask Detection set, and check the licence before publishing.
Dataset and model
Arrange images in folders named with_mask and without_mask so ImageFolder can read labels.
import torch, torch.nn as nn
from torchvision import datasets, models, transforms
tf = transforms.Compose([
transforms.Resize((224, 224)),
transforms.RandomHorizontalFlip(),
transforms.ColorJitter(0.2, 0.2, 0.2),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
train_ds = datasets.ImageFolder("data/train", tf)
loader = torch.utils.data.DataLoader(train_ds, batch_size=32, shuffle=True)
print(train_ds.classes) # ['with_mask', 'without_mask']
model = models.mobilenet_v2(weights=models.MobileNet_V2_Weights.DEFAULT)
for p in model.features.parameters():
p.requires_grad = False
model.classifier[1] = nn.Linear(model.last_channel, 2)Train and save
A few epochs on the new head is enough to reach a strong baseline.
opt = torch.optim.Adam(model.classifier.parameters(), lr=1e-3)
loss_fn = nn.CrossEntropyLoss()
for epoch in range(5):
model.train(); total = correct = 0
for x, y in loader:
opt.zero_grad()
out = model(x); loss = loss_fn(out, y)
loss.backward(); opt.step()
correct += (out.argmax(1) == y).sum().item(); total += len(y)
print(f"epoch {epoch+1}: train acc {correct/total:.3f}")
torch.save({"state": model.state_dict(), "classes": train_ds.classes}, "mask.pt")Real-time inference
OpenCV's built-in Haar cascade is fast and needs no extra download, though a DNN face detector is more accurate.
import cv2
from PIL import Image
ck = torch.load("mask.pt"); model.load_state_dict(ck["state"]); model.eval()
face_net = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
cap = cv2.VideoCapture(0)
while True:
ok, frame = cap.read()
if not ok: break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
for (x, y, w, h) in face_net.detectMultiScale(gray, 1.1, 5, minSize=(60, 60)):
crop = Image.fromarray(cv2.cvtColor(frame[y:y+h, x:x+w], cv2.COLOR_BGR2RGB))
with torch.no_grad():
p = model(tf(crop).unsqueeze(0)).softmax(1)[0]
label = ck["classes"][int(p.argmax())]
color = (0, 200, 0) if label == "with_mask" else (0, 0, 255)
cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
cv2.putText(frame, f"{label} {p.max():.2f}", (x, y-8),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
cv2.imshow("Mask detector", frame)
if cv2.waitKey(1) & 0xFF == ord("q"): break
cap.release(); cv2.destroyAllWindows()Improve it responsibly
Test on faces with varied lighting, skin tones, glasses and angles, and report per-condition accuracy. Do not store or share webcam footage without consent, and be clear about privacy in your README.
Hands-on practice
Run the examples in the downloadable Jupyter notebook. The extra checks below focus on mistakes that appear in real projects, not just syntax.
Check class balance before training
from collections import Counter
labels=['mask']*80+['no_mask']*20
print(Counter(labels))Counter({'mask': 80, 'no_mask': 20})Class imbalance can make headline accuracy misleading.
Measure per-class recall
from sklearn.metrics import classification_report
y_true=[0,0,0,1,1]; y_pred=[0,0,1,1,0]
print(classification_report(y_true,y_pred,digits=2))precision recall f1-score ...
For safety-related detection, inspect the class whose misses matter most.
Self-check
- Can you explain why the first practice result is trustworthy rather than just plausible?
- What would you monitor or test before using this Mask Detection Project workflow in production?
- Which assumption in the example would break first on a larger or messier dataset?
Frequently asked questions
Why use two models?
A face detector is optimised for finding faces, and a small classifier then only has to decide mask or no mask on a tight crop.
Can I use YOLO instead?
Yes. A single YOLO model can detect faces with and without masks directly if you train it on labelled boxes.
How do I run it faster?
Lower the frame size, skip frames, export to ONNX or use a lighter detector.
Primary references
Use these official sources when exact syntax, defaults or definitions matter beyond the examples here.




















































































































































