How to Automatically Add Face Masks to Images with Python and Face Recognition

This article demonstrates how to use the Python face_recognition library and Pillow to detect facial landmarks, generate realistic mask overlays, and produce both masked and binary mask images from the open‑source FaceMask_CelebA dataset, complete with full source code.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
How to Automatically Add Face Masks to Images with Python and Face Recognition

Effect Display

Dataset Display

Dataset source: the open‑source FaceMask_CelebA dataset.

GitHub address: https://github.com/sevenHsu/FaceMask_CelebA.git

Sample face images:

Mask sample images:

Code for Adding Face Masks to Photos

The face_recognition library (which wraps dlib) is required for facial landmark detection.

The library abstracts the C++ graphics library dlib into a simple Python API, greatly simplifying face‑recognition development.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author  : 2014Vee
import os
import numpy as np
from PIL import Image, ImageFile

__version__ = '0.3.0'

IMAGE_DIR = os.path.dirname('E:/play/FaceMask_CelebA-master/facemask_image/')
WHITE_IMAGE_PATH = os.path.join(IMAGE_DIR, 'front_14.png')
BLUE_IMAGE_PATH = os.path.join(IMAGE_DIR, 'front_14.png')
SAVE_PATH = os.path.dirname('E:/play/FaceMask_CelebA-master/save/synthesis/')
SAVE_PATH2 = os.path.dirname('E:/play/FaceMask_CelebA-master/save/masks/')

class FaceMasker:
    KEY_FACIAL_FEATURES = ('nose_bridge', 'chin')

    def __init__(self, face_path, mask_path, white_mask_path, save_path, save_path2, model='hog'):
        self.face_path = face_path
        self.mask_path = mask_path
        self.save_path = save_path
        self.save_path2 = save_path2
        self.white_mask_path = white_mask_path
        self.model = model
        self._face_img: ImageFile = None
        self._black_face_img = None
        self._mask_img: ImageFile = None
        self._white_mask_img = None

    def mask(self):
        import face_recognition
        face_image_np = face_recognition.load_image_file(self.face_path)
        face_locations = face_recognition.face_locations(face_image_np, model=self.model)
        face_landmarks = face_recognition.face_landmarks(face_image_np, face_locations)
        self._face_img = Image.fromarray(face_image_np)
        self._mask_img = Image.open(self.mask_path)
        self._white_mask_img = Image.open(self.white_mask_path)
        self._black_face_img = Image.new('RGB', self._face_img.size, 0)
        found_face = False
        for face_landmark in face_landmarks:
            skip = False
            for facial_feature in self.KEY_FACIAL_FEATURES:
                if facial_feature not in face_landmark:
                    skip = True
                    break
            if skip:
                continue
            found_face = True
            self._mask_face(face_landmark)
        if found_face:
            self._save()
        else:
            print('Found no face.')

    def _mask_face(self, face_landmark: dict):
        nose_bridge = face_landmark['nose_bridge']
        nose_point = nose_bridge[len(nose_bridge) * 1 // 4]
        nose_v = np.array(nose_point)
        chin = face_landmark['chin']
        chin_len = len(chin)
        chin_bottom_point = chin[chin_len // 2]
        chin_bottom_v = np.array(chin_bottom_point)
        chin_left_point = chin[chin_len // 8]
        chin_right_point = chin[chin_len * 7 // 8]
        # split mask and resize
        width = self._mask_img.width
        height = self._mask_img.height
        width_ratio = 1.2
        new_height = int(np.linalg.norm(nose_v - chin_bottom_v))
        # left
        mask_left_img = self._mask_img.crop((0, 0, width // 2, height))
        mask_left_width = self.get_distance_from_point_to_line(chin_left_point, nose_point, chin_bottom_point)
        mask_left_width = int(mask_left_width * width_ratio)
        mask_left_img = mask_left_img.resize((mask_left_width, new_height))
        # right
        mask_right_img = self._mask_img.crop((width // 2, 0, width, height))
        mask_right_width = self.get_distance_from_point_to_line(chin_right_point, nose_point, chin_bottom_point)
        mask_right_width = int(mask_right_width * width_ratio)
        mask_right_img = mask_right_img.resize((mask_right_width, new_height))
        # merge mask
        size = (mask_left_img.width + mask_right_img.width, new_height)
        mask_img = Image.new('RGBA', size)
        mask_img.paste(mask_left_img, (0, 0), mask_left_img)
        mask_img.paste(mask_right_img, (mask_left_img.width, 0), mask_right_img)
        # rotate mask
        angle = np.arctan2(chin_bottom_point[1] - nose_point[1], chin_bottom_point[0] - nose_point[0])
        rotated_mask_img = mask_img.rotate(angle, expand=True)
        # calculate mask location
        center_x = (nose_point[0] + chin_bottom_point[0]) // 2
        center_y = (nose_point[1] + chin_bottom_point[1]) // 2
        offset = mask_img.width // 2 - mask_left_img.width
        radian = angle * np.pi / 180
        box_x = center_x + int(offset * np.cos(radian)) - rotated_mask_img.width // 2
        box_y = center_y + int(offset * np.sin(radian)) - rotated_mask_img.height // 2
        # add mask
        self._face_img.paste(mask_img, (box_x, box_y), mask_img)
        # repeat for white mask (binary)
        width = self._white_mask_img.width
        height = self._white_mask_img.height
        new_height = int(np.linalg.norm(nose_v - chin_bottom_v))
        mask_left_img = self._white_mask_img.crop((0, 0, width // 2, height))
        mask_left_width = self.get_distance_from_point_to_line(chin_left_point, nose_point, chin_bottom_point)
        mask_left_width = int(mask_left_width * width_ratio)
        mask_left_img = mask_left_img.resize((mask_left_width, new_height))
        mask_right_img = self._white_mask_img.crop((width // 2, 0, width, height))
        mask_right_width = self.get_distance_from_point_to_line(chin_right_point, nose_point, chin_bottom_point)
        mask_right_width = int(mask_right_width * width_ratio)
        mask_right_img = mask_right_img.resize((mask_right_width, new_height))
        size = (mask_left_img.width + mask_right_img.width, new_height)
        mask_img = Image.new('RGBA', size)
        mask_img.paste(mask_left_img, (0, 0), mask_left_img)
        mask_img.paste(mask_right_img, (mask_left_img.width, 0), mask_right_img)
        angle = np.arctan2(chin_bottom_point[1] - nose_point[1], chin_bottom_point[0] - nose_point[0])
        rotated_mask_img = mask_img.rotate(angle, expand=True)
        center_x = (nose_point[0] + chin_bottom_point[0]) // 2
        center_y = (nose_point[1] + chin_bottom_point[1]) // 2
        offset = mask_img.width // 2 - mask_left_img.width
        radian = angle * np.pi / 180
        box_x = center_x + int(offset * np.cos(radian)) - rotated_mask_img.width // 2
        box_y = center_y + int(offset * np.sin(radian)) - rotated_mask_img.height // 2
        self._black_face_img.paste(mask_img, (box_x, box_y), mask_img)

    def _save(self):
        path_splits = os.path.splitext(self.face_path)
        new_face_path = self.save_path + '/' + os.path.basename(self.face_path) + '-with-mask' + path_splits[1]
        new_face_path2 = self.save_path2 + '/' + os.path.basename(self.face_path) + '-binary' + path_splits[1]
        self._face_img.save(new_face_path)
        self._black_face_img.save(new_face_path2)

    @staticmethod
    def get_distance_from_point_to_line(point, line_point1, line_point2):
        distance = abs((line_point2[1] - line_point1[1]) * point[0] +
                       (line_point1[0] - line_point2[0]) * point[1] +
                       (line_point2[0] - line_point1[0]) * line_point1[1] +
                       (line_point1[1] - line_point2[1]) * line_point1[0]) / \
                   np.sqrt((line_point2[1] - line_point1[1]) ** 2 + (line_point1[0] - line_point2[0]) ** 2)
        return int(distance)

# Example usage (commented out)
# FaceMasker("/home/aistudio/data/人脸.png", WHITE_IMAGE_PATH, True, 'hog').mask()

from pathlib import Path

images = Path("E:/play/FaceMask_CelebA-master/bbox_align_celeba").glob("*")
cnt = 0
for image in images:
    if cnt < 1:
        cnt += 1
        continue
    FaceMasker(image, BLUE_IMAGE_PATH, WHITE_IMAGE_PATH, SAVE_PATH, SAVE_PATH2, 'hog').mask()
    cnt += 1
    print(f"Processing image {cnt}, remaining {99 - cnt}")

Mask Generation Code

This part binarizes the mask samples because downstream models require binary masks.

import os
from PIL import Image

# Source directory
MyPath = 'E:/play/FaceMask_CelebA-master/save/masks/'
# Output directory
OutPath = 'E:/play/FaceMask_CelebA-master/save/Binarization/'

def processImage(filesoure, destsoure, name, imgtype):
    """Convert an image to a binary mask.
    filesoure: directory of source images
    destsoure: directory for output images
    name: filename
    imgtype: file extension (bmp or png)
    """
    imgtype = 'bmp' if imgtype == '.bmp' else 'png'
    im = Image.open(filesoure + name)
    img = im.convert("RGBA")
    pixdata = img.load()
    # Threshold on R channel
    for y in range(img.size[1]):
        for x in range(img.size[0]):
            if pixdata[x, y][0] < 90:
                pixdata[x, y] = (0, 0, 0, 255)
    # Threshold on G channel
    for y in range(img.size[1]):
        for x in range(img.size[0]):
            if pixdata[x, y][1] < 136:
                pixdata[x, y] = (0, 0, 0, 255)
    # Threshold on B channel
    for y in range(img.size[1]):
        for x in range(img.size[0]):
            if pixdata[x, y][2] > 0:
                pixdata[x, y] = (255, 255, 255, 255)
    img.save(destsoure + name, imgtype)

def run():
    os.chdir(MyPath)
    for i in os.listdir(os.getcwd()):
        postfix = os.path.splitext(i)[1]
        name = os.path.splitext(i)[0]
        name2 = name.split('.')
        if name2[1] == 'jpg-binary' or name2[1] == 'png-binary':
            processImage(MyPath, OutPath, i, postfix)

if __name__ == '__main__':
    run()
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Computer VisionImage Processingface recognitionmask generation
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.