Skip to main content
Xenors Xenors
Home › Data Science › Project: Real-Time Face Mask Detection with OpenCV and PyTorch

Module 13: Advanced Real-World Projects & CapstoneBy Ashok Kumar Yadav4 min read

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.

Webcam frame with face boxes labelled mask and no mask by a PyTorch classifier
Quick answer: Detect faces with OpenCV, crop each face, classify it with a MobileNetV2 fine-tuned on a mask dataset in PyTorch, then draw a green or red box with the label on every webcam 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.

Data pipeline and MobileNetV2 with a new head
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.

Training loop
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.

Webcam loop with face detection and classification
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

Practice example
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

Practice example
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.

Written by Ashok Kumar Yadav
Ashok Kumar Yadav writes Xenors tutorials on applied AI, programming and data workflows. Every lesson is built around code you can run and check yourself.
GitHub · LinkedIn

Continue exploring