A comprehensive 2D and 3D face analysis toolkit with state-of-the-art algorithms for face recognition, detection, and alignment.
—
Advanced 3D face modeling capabilities using morphable models for face reconstruction, pose estimation, expression analysis, and realistic 3D face rendering. Built on the Basel Face Model (BFM) and other morphable model formats.
Core 3D morphable face model for generating and manipulating 3D face geometry, texture, and expressions.
class MorphabelModel:
def __init__(self, model_path, model_type='BFM'):
"""
Initialize 3D morphable face model.
Parameters:
- model_path: str, path to morphable model file (.mat, .pkl, etc.)
- model_type: str, model type ('BFM', '3DMM', etc.)
"""
def get_shape_para(self, type='random') -> np.ndarray:
"""
Generate or retrieve shape parameters.
Parameters:
- type: str, parameter generation type ('random', 'zero', 'mean')
Returns:
np.ndarray: shape parameters vector
"""
def get_exp_para(self, type='random') -> np.ndarray:
"""
Generate or retrieve expression parameters.
Parameters:
- type: str, parameter generation type ('random', 'zero', 'mean')
Returns:
np.ndarray: expression parameters vector
"""
def generate_vertices(self, shape_para, exp_para) -> np.ndarray:
"""
Generate 3D face vertices from shape and expression parameters.
Parameters:
- shape_para: np.ndarray, shape parameters
- exp_para: np.ndarray, expression parameters
Returns:
np.ndarray: 3D vertices, shape (n_vertices, 3)
"""
def get_tex_para(self, type='random') -> np.ndarray:
"""
Generate or retrieve texture parameters.
Parameters:
- type: str, parameter generation type
Returns:
np.ndarray: texture parameters vector
"""
def generate_colors(self, tex_para) -> np.ndarray:
"""
Generate face colors/textures from texture parameters.
Parameters:
- tex_para: np.ndarray, texture parameters
Returns:
np.ndarray: vertex colors, shape (n_vertices, 3)
"""
def rotate(self, vertices, angles) -> np.ndarray:
"""
Rotate 3D face vertices.
Parameters:
- vertices: np.ndarray, 3D vertices to rotate
- angles: np.ndarray, rotation angles [pitch, yaw, roll] in radians
Returns:
np.ndarray: rotated vertices
"""
def transform(self, vertices, s, angles, t3d) -> np.ndarray:
"""
Apply full 3D transformation to vertices.
Parameters:
- vertices: np.ndarray, input vertices
- s: float, scaling factor
- angles: np.ndarray, rotation angles [pitch, yaw, roll]
- t3d: np.ndarray, 3D translation vector
Returns:
np.ndarray: transformed vertices
"""
def transform_3ddfa(self, vertices, s, angles, t3d) -> np.ndarray:
"""
Apply 3DDFA-style transformation to vertices.
Parameters: same as transform()
Returns:
np.ndarray: transformed vertices using 3DDFA convention
"""
def fit(self, x, X_ind, max_iter=4, isShow=False) -> Tuple[np.ndarray, ...]:
"""
Fit morphable model to 2D/3D landmarks.
Parameters:
- x: np.ndarray, target landmark coordinates
- X_ind: np.ndarray, indices of model vertices corresponding to landmarks
- max_iter: int, maximum fitting iterations
- isShow: bool, show fitting progress visualization
Returns:
tuple: (fitted_vertices, shape_params, exp_params, pose_params, ...)
"""Key attributes of the morphable model providing model metadata and mesh information.
# Model dimensions and parameters
nver: float # Number of vertices in the model
ntri: float # Number of triangles in the mesh
n_shape_para: int # Number of shape parameters (typically 199)
n_exp_para: int # Number of expression parameters (typically 29)
n_tex_para: int # Number of texture parameters (typically 199)
# Mesh topology
kpt_ind: np.ndarray # Indices of keypoint vertices
triangles: np.ndarray # Triangle mesh connectivity
full_triangles: np.ndarray # Complete triangle mesh for renderingComprehensive 3D mesh processing capabilities for visualization and manipulation.
# Mesh I/O operations
def read_obj(filename) -> Tuple[np.ndarray, np.ndarray]: ...
def write_obj(filename, vertices, triangles): ...
# Mesh visualization
def render_mesh(vertices, triangles, colors=None) -> np.ndarray: ...
def plot_mesh(vertices, triangles): ...
# 3D transformations
def apply_transform(vertices, transform_matrix) -> np.ndarray: ...
def compute_normal(vertices, triangles) -> np.ndarray: ...
# Lighting calculations
def phong_shading(vertices, normals, light_pos, light_color) -> np.ndarray: ...
def lambert_shading(vertices, normals, light_dir) -> np.ndarray: ...from insightface.thirdparty.face3d.morphable_model import MorphabelModel
import numpy as np
# Load morphable model (requires BFM model file)
model_path = 'path/to/BFM.mat' # Basel Face Model file
bfm = MorphabelModel(model_path, model_type='BFM')
print(f"Model info:")
print(f" Vertices: {int(bfm.nver)}")
print(f" Triangles: {int(bfm.ntri)}")
print(f" Shape parameters: {bfm.n_shape_para}")
print(f" Expression parameters: {bfm.n_exp_para}")
# Generate random face
shape_params = bfm.get_shape_para('random')
exp_params = bfm.get_exp_para('zero') # Neutral expression
# Generate 3D face vertices
vertices = bfm.generate_vertices(shape_params, exp_params)
print(f"Generated vertices shape: {vertices.shape}")
# Generate face colors/texture
tex_params = bfm.get_tex_para('random')
colors = bfm.generate_colors(tex_params)
print(f"Generated colors shape: {colors.shape}")import numpy as np
# Start with a neutral face
shape_params = bfm.get_shape_para('mean') # Average face shape
exp_params = bfm.get_exp_para('zero') # Neutral expression
vertices = bfm.generate_vertices(shape_params, exp_params)
# Apply different poses
poses = [
{'name': 'frontal', 'angles': [0, 0, 0]},
{'name': 'left_profile', 'angles': [0, np.pi/3, 0]},
{'name': 'right_profile', 'angles': [0, -np.pi/3, 0]},
{'name': 'looking_up', 'angles': [np.pi/6, 0, 0]},
{'name': 'looking_down', 'angles': [-np.pi/6, 0, 0]}
]
posed_faces = {}
for pose in poses:
# Apply rotation
rotated = bfm.rotate(vertices, pose['angles'])
# Apply full transformation with scaling and translation
s = 1.0 # No scaling
t3d = np.array([0, 0, 0]) # No translation
transformed = bfm.transform(rotated, s, pose['angles'], t3d)
posed_faces[pose['name']] = transformed
print(f"Generated {len(posed_faces)} different poses")# Fit model to detected facial landmarks
from insightface.app import FaceAnalysis
import cv2
# Setup face analysis for landmark detection
app = FaceAnalysis()
app.prepare(ctx_id=0)
# Load image and detect landmarks
img = cv2.imread('face_image.jpg')
faces = app.get(img)
if faces:
face = faces[0]
# Use 3D landmarks if available
if hasattr(face, 'landmark_3d_68') and face.landmark_3d_68 is not None:
target_landmarks = face.landmark_3d_68
# Define corresponding model vertex indices
# (This would typically be predefined based on the model)
landmark_indices = np.array([
# Indices of BFM vertices that correspond to facial landmarks
# These need to be established based on the specific model
8163, 8174, 8213, 8194, 8227, # Chin area
2435, 2452, 2463, 2441, 2456, # Left eyebrow
# ... more landmark correspondences
])
# Fit model to landmarks
fitted_results = bfm.fit(
x=target_landmarks,
X_ind=landmark_indices,
max_iter=10,
isShow=False
)
fitted_vertices, fitted_shape, fitted_exp, fitted_pose = fitted_results[:4]
print(f"Fitting completed:")
print(f" Shape parameters: {fitted_shape.shape}")
print(f" Expression parameters: {fitted_exp.shape}")
print(f" Pose parameters: {fitted_pose.shape}")# Create faces with different expressions
base_shape = bfm.get_shape_para('mean')
# Define expression variations
expressions = {
'neutral': bfm.get_exp_para('zero'),
'smile': bfm.get_exp_para('random') * 0.5, # Scaled random expression
'surprise': np.zeros(bfm.n_exp_para),
'frown': np.zeros(bfm.n_exp_para)
}
# Manually adjust specific expression parameters if known
# (These indices depend on the specific morphable model)
expressions['smile'][12] = 2.0 # Smile-related parameter
expressions['surprise'][5] = 1.5 # Eyebrow raise
expressions['frown'][15] = -1.0 # Mouth corner down
expression_faces = {}
for exp_name, exp_params in expressions.items():
vertices = bfm.generate_vertices(base_shape, exp_params)
expression_faces[exp_name] = vertices
print(f"Generated {len(expression_faces)} expression variations")from insightface.thirdparty.face3d.mesh import render, light
def render_3d_face(vertices, triangles, colors, pose_angles, image_size=(256, 256)):
"""Complete 3D face rendering pipeline."""
# Apply pose transformation
transformed_vertices = bfm.rotate(vertices, pose_angles)
# Set up lighting
light_positions = np.array([
[0, 0, 1], # Front light
[-1, 1, 1], # Left-top light
[1, 1, 1] # Right-top light
])
light_intensities = np.array([0.6, 0.3, 0.3])
# Compute normals for lighting
from insightface.thirdparty.face3d.mesh.transform import compute_normal
normals = compute_normal(transformed_vertices, triangles)
# Apply lighting
lit_colors = colors.copy()
for light_pos, intensity in zip(light_positions, light_intensities):
lighting = light.lambert_shading(normals, light_pos) * intensity
lit_colors = lit_colors * lighting[:, np.newaxis]
# Render to image
rendered_image = render.render_mesh(
transformed_vertices,
triangles,
lit_colors,
image_size
)
return rendered_image
# Render faces with different poses and lighting
shape_params = bfm.get_shape_para('mean')
exp_params = bfm.get_exp_para('zero')
tex_params = bfm.get_tex_para('mean')
vertices = bfm.generate_vertices(shape_params, exp_params)
colors = bfm.generate_colors(tex_params)
# Render with different poses
for angle in [0, np.pi/6, np.pi/3]:
pose = [0, angle, 0] # Yaw rotation
rendered = render_3d_face(vertices, bfm.triangles, colors, pose)
# Save rendered image
cv2.imwrite(f'rendered_face_yaw_{int(np.degrees(angle))}.jpg', rendered)# Create morphing between two faces
def morph_faces(shape1, exp1, shape2, exp2, n_steps=10):
"""Create smooth morphing between two face configurations."""
morphed_faces = []
for i in range(n_steps):
alpha = i / (n_steps - 1) # Interpolation weight
# Linear interpolation of parameters
morphed_shape = (1 - alpha) * shape1 + alpha * shape2
morphed_exp = (1 - alpha) * exp1 + alpha * exp2
# Generate intermediate face
vertices = bfm.generate_vertices(morphed_shape, morphed_exp)
morphed_faces.append(vertices)
return morphed_faces
# Create two different faces
face1_shape = bfm.get_shape_para('random')
face1_exp = bfm.get_exp_para('zero')
face2_shape = bfm.get_shape_para('random')
face2_exp = bfm.get_exp_para('random')
# Generate morphing sequence
morph_sequence = morph_faces(face1_shape, face1_exp, face2_shape, face2_exp)
print(f"Generated morphing sequence with {len(morph_sequence)} frames")
# Save morphing frames
for i, vertices in enumerate(morph_sequence):
# Render each frame (using rendering function from above)
colors = bfm.generate_colors(bfm.get_tex_para('mean'))
rendered = render_3d_face(vertices, bfm.triangles, colors, [0, 0, 0])
cv2.imwrite(f'morph_frame_{i:03d}.jpg', rendered)def analyze_morphable_model(bfm):
"""Analyze morphable model properties and statistics."""
print("=== Morphable Model Analysis ===")
print(f"Model Dimensions:")
print(f" Vertices: {int(bfm.nver)}")
print(f" Triangles: {int(bfm.ntri)}")
print(f" Keypoints: {len(bfm.kpt_ind)}")
print(f"\nParameter Dimensions:")
print(f" Shape: {bfm.n_shape_para}")
print(f" Expression: {bfm.n_exp_para}")
print(f" Texture: {bfm.n_tex_para}")
# Analyze parameter ranges
test_shapes = [bfm.get_shape_para('random') for _ in range(100)]
shape_stats = np.array(test_shapes)
print(f"\nShape Parameter Statistics:")
print(f" Mean range: [{shape_stats.mean(axis=0).min():.3f}, {shape_stats.mean(axis=0).max():.3f}]")
print(f" Std range: [{shape_stats.std(axis=0).min():.3f}, {shape_stats.std(axis=0).max():.3f}]")
# Analyze mesh properties
mean_shape = bfm.get_shape_para('mean')
mean_exp = bfm.get_exp_para('zero')
mean_vertices = bfm.generate_vertices(mean_shape, mean_exp)
print(f"\nMesh Properties:")
print(f" Vertex bounds: X[{mean_vertices[:, 0].min():.2f}, {mean_vertices[:, 0].max():.2f}]")
print(f" Y[{mean_vertices[:, 1].min():.2f}, {mean_vertices[:, 1].max():.2f}]")
print(f" Z[{mean_vertices[:, 2].min():.2f}, {mean_vertices[:, 2].max():.2f}]")
# Run analysis
analyze_morphable_model(bfm)Install with Tessl CLI
npx tessl i tessl/pypi-insightface