Face-mask classifier: InceptionV3 transfer learning (for use after MTCNN)

A small Keras image classifier that looks at one cropped face and labels it with_mask, without_mask or mask_weared_incorrect. It is the second stage of a two-stage pipeline: MTCNN finds the faces, each face is cropped, and this model classifies the crop. It is a teaching and demonstration model, not a production system.

Model details

Architecture InceptionV3 with ImageNet weights, all layers frozen and cut at layer mixed7, then Flatten, Dense 1024, Dropout 0.3, Dense 3 (softmax)
Parameters 21,562,275 in total, 12,587,011 trainable (the new head only)
Input one face crop, RGB, resized to 100 x 100 pixels, pixel values scaled to 0-1 (divide by 255)
Output softmax over 3 classes: 0 without_mask, 1 with_mask, 2 mask_weared_incorrect
Training Adam, categorical cross-entropy, 20 epochs, batch size 32, seed 42, CPU only (about 5 minutes)
Software Python 3.11.9, TensorFlow 2.21.0, Keras 3.15.1

Files

File What it is
inceptionV3-model.keras The final epoch-20 model, full Keras model. This is the model the results below describe, and the one the demo uses.
best-model.weights.h5 Weights of the best-validation checkpoint (epoch 10), weights only: build the same architecture first, then load_weights. Secondary result in the run log; not used by the demo.
run_metrics.json Seed, split sizes, per-class precision/recall/F1, confusion matrices and training history of the run.

Training and evaluation data

Kaggle Face Mask Detection by andrewmvd, CC0 1.0 (the images and annotations come from the Make ML Mask Dataset): 853 photos, 4,072 annotated faces. Classes: with_mask 3,232 (79.4%), without_mask 717 (17.6%), mask_weared_incorrect 123 (3.0%).

The split is made by source photo (seed 42, so no photo and none of its byte-identical duplicates appears in two splits): train 3,299 face crops from 700 photos, validation 366 from 70, test 407 from 78.

The dataset shows real people's faces. CC0 describes the dataset's copyright status; it does not say whether the people shown consented to being in it.

Results

Metric Value Status How measured
Test accuracy 0.9631 (392 of 407 face crops) Verified (re-run 2026-10-13, photo-grouped split) model.evaluate on the 78 held-out photos, final epoch-20 model, seed 42
Majority-class baseline 0.7936 Verified (re-run 2026-10-13, photo-grouped split) always predicting with_mask on the same test set
Class Precision Recall F1 Test crops
without_mask 0.956 0.903 0.929 72
with_mask 0.967 0.991 0.979 323
mask_weared_incorrect 0.875 0.583 0.700 12
macro average 0.933 0.826 0.869 407

This is one run, on one split, with no confidence interval. An older notebook output from February 2022 reported 94.85% on a different, unseeded random split by face; the two numbers are different measurements on different test sets and are not directly comparable. Details: run log.

Limitations

  • The rarest class is unreliable. mask_weared_incorrect has only 12 test crops; one crop moves its recall by about 8 points, and its F1 is 0.70 (7 of 12 found).
  • Small, single-source data. 853 photos from one public dataset. It has not been tested on other cameras, lighting, face angles, skin tones or mask styles, and it will make mistakes on photos unlike the training data.
  • The detector misses faces. The model only sees what MTCNN passes to it. In one local test photo MTCNN found 5 of the 9 faces, and a missed face gets no label at all.
  • Overfitting. Validation loss rises from 0.28 at epoch 1 to 0.80 at epoch 20; the headline model is the last epoch, as in the original notebook.
  • Not a measure of compliance. A "mask" label says a mask-like object is on the crop, not that it is worn correctly or is effective.

Intended use

Teaching, coursework and demonstrations of a two-stage detect-then-classify vision pipeline, and as a baseline to compare against.

Out of scope: safety-critical decisions, health or compliance enforcement, access control, surveillance, or any decision about a person. Do not use it to identify or track individuals.

How to use

import cv2, numpy as np
from huggingface_hub import hf_hub_download
from keras.models import load_model
from mtcnn import MTCNN

path = hf_hub_download("JaneRoseFerdinant/face-mask-inceptionv3-mtcnn", "inceptionV3-model.keras")
model = load_model(path, compile=False)
labels = {0: "without_mask", 1: "with_mask", 2: "mask_weared_incorrect"}

rgb = cv2.cvtColor(cv2.imread("photo.jpg"), cv2.COLOR_BGR2RGB)
for face in MTCNN().detect_faces(rgb):
    x, y, w, h = face["box"]
    x, y = max(0, x), max(0, y)
    crop = cv2.resize(rgb[y:y + h, x:x + w], (100, 100)).astype("float32") / 255.0
    probs = model.predict(crop[None], verbose=0)[0]
    print(face["box"], labels[int(probs.argmax())], round(float(probs.max()), 3))

Use the pinned versions from the repository (requirements.txt, Python 3.11, TensorFlow 2.21, Keras 3.15.1, mtcnn 1.0.0) for results that match the run log.

License

The original work in this repository (the model weights and the card) is (c) Jane Rose Ferdinant, licensed under CC BY-NC-ND 4.0. Third-party components keep their own licenses: the training data is CC0, the InceptionV3 architecture and ImageNet starting weights come from Keras, and mtcnn is MIT. See THIRD_PARTY_NOTICES.md in the GitHub repository.

Citation and credits

  • Dataset: Kaggle "Face Mask Detection" by andrewmvd (CC0), from the Make ML Mask Dataset.
  • MTCNN: Zhang, K., Zhang, Z., Li, Z. and Qiao, Y., "Joint Face Detection and Alignment Using Multitask Cascaded Convolutional Networks", IEEE Signal Processing Letters, 2016.
  • InceptionV3: Szegedy, C. et al., "Rethinking the Inception Architecture for Computer Vision", CVPR 2016.

Author

Jane Rose Ferdinant ยท Portfolio ยท LinkedIn

Downloads last month
28
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support