Skip to content

Parsing

Face parsing segments faces into semantic components or face regions.


Available Models

Model Backbone Size Output
BiSeNet ResNet18 ResNet18 51 MB 19 classes
BiSeNet ResNet34 ResNet34 89 MB 19 classes
XSeg - 67 MB Mask

Basic Usage

import cv2
from uniface.parsing import BiSeNet
from uniface.visualization import vis_parsing_maps

# Initialize parser
parser = BiSeNet()

# Load face image (cropped)
face_image = cv2.imread("face.jpg")

# Parse face
mask = parser.parse(face_image)
print(f"Mask shape: {mask.shape}")  # (H, W)

# Visualize
face_rgb = cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB)
vis_result = vis_parsing_maps(face_rgb, mask, save_image=False)

# Save result
vis_bgr = cv2.cvtColor(vis_result, cv2.COLOR_RGB2BGR)
cv2.imwrite("parsed.jpg", vis_bgr)

19 Facial Component Classes

ID Class ID Class
0 Background 10 Ear Ring
1 Skin 11 Nose
2 Left Eyebrow 12 Mouth
3 Right Eyebrow 13 Upper Lip
4 Left Eye 14 Lower Lip
5 Right Eye 15 Neck
6 Eye Glasses 16 Neck Lace
7 Left Ear 17 Cloth
8 Right Ear 18 Hair
9 Hat

Model Variants

from uniface.parsing import BiSeNet
from uniface.constants import ParsingWeights

# Default (ResNet18)
parser = BiSeNet()

# Higher accuracy (ResNet34)
parser = BiSeNet(model_name=ParsingWeights.RESNET34)
Variant Params Size
RESNET18 13.3M 51 MB
RESNET34 24.1M 89 MB

Full Pipeline

With Face Detection

import cv2
from uniface import RetinaFace
from uniface.parsing import BiSeNet
from uniface.visualization import vis_parsing_maps

detector = RetinaFace()
parser = BiSeNet()

image = cv2.imread("photo.jpg")
faces = detector.detect(image)

for i, face in enumerate(faces):
    # Crop face
    x1, y1, x2, y2 = map(int, face.bbox)
    face_crop = image[y1:y2, x1:x2]

    # Parse
    mask = parser.parse(face_crop)

    # Visualize
    face_rgb = cv2.cvtColor(face_crop, cv2.COLOR_BGR2RGB)
    vis_result = vis_parsing_maps(face_rgb, mask, save_image=False)

    # Save
    vis_bgr = cv2.cvtColor(vis_result, cv2.COLOR_RGB2BGR)
    cv2.imwrite(f"face_{i}_parsed.jpg", vis_bgr)

Extract Specific Components

Get Single Component Mask

import numpy as np

# Parse face
mask = parser.parse(face_image)

# Extract specific component
SKIN = 1
HAIR = 18
LEFT_EYE = 4
RIGHT_EYE = 5

# Binary mask for skin
skin_mask = (mask == SKIN).astype(np.uint8) * 255

# Binary mask for hair
hair_mask = (mask == HAIR).astype(np.uint8) * 255

# Binary mask for eyes
eyes_mask = ((mask == LEFT_EYE) | (mask == RIGHT_EYE)).astype(np.uint8) * 255

Count Pixels per Component

import numpy as np

mask = parser.parse(face_image)

component_names = {
    0: 'Background', 1: 'Skin', 2: 'L-Eyebrow', 3: 'R-Eyebrow',
    4: 'L-Eye', 5: 'R-Eye', 6: 'Glasses', 7: 'L-Ear', 8: 'R-Ear',
    9: 'Hat', 10: 'Earring', 11: 'Nose', 12: 'Mouth',
    13: 'U-Lip', 14: 'L-Lip', 15: 'Neck', 16: 'Necklace',
    17: 'Cloth', 18: 'Hair'
}

for class_id in np.unique(mask):
    pixel_count = np.sum(mask == class_id)
    name = component_names.get(class_id, f'Class {class_id}')
    print(f"{name}: {pixel_count} pixels")

Applications

Face Makeup

Apply virtual makeup using component masks:

import cv2
import numpy as np

def apply_lip_color(image, mask, color=(180, 50, 50)):
    """Apply lip color using parsing mask."""
    result = image.copy()

    # Get lip mask (upper + lower lip)
    lip_mask = ((mask == 13) | (mask == 14)).astype(np.uint8)

    # Create color overlay
    overlay = np.zeros_like(image)
    overlay[:] = color

    # Blend with original
    lip_region = cv2.bitwise_and(overlay, overlay, mask=lip_mask)
    non_lip = cv2.bitwise_and(result, result, mask=1 - lip_mask)

    # Combine with alpha blending
    alpha = 0.4
    result = cv2.addWeighted(result, 1 - alpha * lip_mask[:,:,np.newaxis] / 255,
                             lip_region, alpha, 0)

    return result.astype(np.uint8)

Background Replacement

def replace_background(image, mask, background):
    """Replace background using parsing mask."""
    # Create foreground mask (everything except background)
    foreground_mask = (mask != 0).astype(np.uint8)

    # Resize background to match image
    background = cv2.resize(background, (image.shape[1], image.shape[0]))

    # Combine
    result = image.copy()
    result[foreground_mask == 0] = background[foreground_mask == 0]

    return result

Hair Segmentation

def get_hair_mask(mask):
    """Extract clean hair mask."""
    hair_mask = (mask == 18).astype(np.uint8) * 255

    # Clean up with morphological operations
    kernel = np.ones((5, 5), np.uint8)
    hair_mask = cv2.morphologyEx(hair_mask, cv2.MORPH_CLOSE, kernel)
    hair_mask = cv2.morphologyEx(hair_mask, cv2.MORPH_OPEN, kernel)

    return hair_mask

Visualization Options

from uniface.visualization import vis_parsing_maps

# Default visualization
vis_result = vis_parsing_maps(face_rgb, mask)

# With different parameters
vis_result = vis_parsing_maps(
    face_rgb,
    mask,
    save_image=False,  # Don't save to file
)

XSeg

XSeg outputs a mask for face regions. Unlike BiSeNet which works on bbox crops, XSeg requires 5-point landmarks for face alignment.

Basic Usage

import cv2
from uniface import RetinaFace
from uniface.parsing import XSeg

detector = RetinaFace()
parser = XSeg()

image = cv2.imread("photo.jpg")
faces = detector.detect(image)

for face in faces:
    if face.landmarks is not None:
        mask = parser.parse(image, face.landmarks)
        print(f"Mask shape: {mask.shape}")  # (H, W), values in [0, 1]

Parameters

from uniface.parsing import XSeg

# Default settings
parser = XSeg()

# Custom settings
parser = XSeg(
    align_size=256,   # Face alignment size
    blur_sigma=5,     # Gaussian blur for smoothing (0 = raw)
)
Parameter Default Description
align_size 256 Face alignment output size
blur_sigma 0 Mask smoothing (0 = no blur)

Methods

# Full pipeline: align -> segment -> warp back to original space
mask = parser.parse(image, landmarks)

# For pre-aligned face crops
mask = parser.parse_aligned(face_crop)

# Get mask + crop + inverse matrix for custom warping
mask, face_crop, inverse_matrix = parser.parse_with_inverse(image, landmarks)

BiSeNet vs XSeg

Feature BiSeNet XSeg
Output 19 class labels Mask [0, 1]
Input Bbox crop Requires landmarks
Use case Facial components Face region extraction

Factory Function

from uniface import create_face_parser
from uniface.constants import ParsingWeights, XSegWeights

# BiSeNet (default)
parser = create_face_parser()

# XSeg
parser = create_face_parser(XSegWeights.DEFAULT)

Next Steps