Comfy-Org/ComfyUI · error · ValueError
Procrustes denominator collapsed (degenerate source).
Error message
Procrustes denominator collapsed (degenerate source).
What it means
Raised by the weighted Procrustes solver in the MediaPipe face-geometry port when the denominator used to compute the optimal scale is effectively zero. The denominator is the weighted sum of squared distances of the source points from their weighted centroid, so it collapses only when all landmark points are identical (or all weights are zero). This mirrors geometry_pipeline.cc::EstimateScale behavior where a degenerate canonical point set cannot define a rigid transform.
Source
Thrown at comfy_extras/mediapipe/face_geometry.py:30
`target ≈ M @ homogeneous(source)` in the weighted LS sense. fp64 for
SVD stability. Port of procrustes_solver.cc."""
sqrt_w = np.sqrt(weights.astype(np.float64))
w_total = float((sqrt_w ** 2).sum())
ws = src.astype(np.float64) * sqrt_w
wt = tgt.astype(np.float64) * sqrt_w
c_w = (ws @ sqrt_w) / w_total
centered = ws - np.outer(c_w, sqrt_w)
U, _S, Vt = np.linalg.svd(wt @ centered.T, full_matrices=True)
# Disallow reflection: flip the least-significant axis when det(U)·det(V)<0.
post, pre = U.copy(), Vt.T.copy()
if np.linalg.det(post) * np.linalg.det(pre) < 0:
post[:, 2] *= -1.0
R = post @ pre.T
denom = float((centered * ws).sum())
if denom < 1e-12:
raise ValueError("Procrustes denominator collapsed (degenerate source).")
scale = float((R @ centered * wt).sum()) / denom
translation = ((wt - scale * (R @ ws)) @ sqrt_w) / w_total
M = np.eye(4, dtype=np.float64)
M[:3, :3] = scale * R
M[:3, 3] = translation
return M
def _estimate_scale(canonical: np.ndarray, runtime: np.ndarray, weights: np.ndarray) -> float:
"""scale = ‖first column of M[:3]‖ per geometry_pipeline.cc::EstimateScale."""
return float(np.linalg.norm(_solve_weighted_orthogonal_problem(canonical, runtime, weights)[:3, 0]))
def solve_facial_transformation_matrix(
landmarks_normalized: np.ndarray,
canonical_vertices: np.ndarray,
procrustes_indices: np.ndarray,View on GitHub (pinned to 1c6d8d45b3)
Solutions
- Inspect the landmark tensor fed into the solver; if all points are identical, the face detector produced no real landmarks — feed frames where a face is actually detected
- If weights are user-supplied, verify they are positive and normalized instead of all zeros
- Add an upstream validity check (e.g. landmark variance > epsilon) before calling the Procrustes solve, and skip/interpolate the frame instead of crashing
- If a constant template is intentional (initialization), perturb it or bypass the solver for that frame
Example fix
// before
M = _solve_weighted_orthogonal_problem(canonical, runtime, weights)
// after
if np.linalg.norm(canonical - canonical.mean(axis=0)) < 1e-9:
raise SkipFrame("degenerate landmarks")
M = _solve_weighted_orthogonal_problem(canonical, runtime, weights) Defensive patterns
Strategy: validation
Validate before calling
import numpy as np
def landmarks_solvable(pts: np.ndarray, weights: np.ndarray, eps: float = 1e-9) -> bool:
if pts.ndim != 2 or pts.shape[1] != 3 or pts.shape[0] < 3:
return False
centered = pts - pts.mean(axis=0)
if np.linalg.norm(centered) < eps:
return False # all points identical -> denominator collapses
if weights.sum() < eps or (weights < 0).any():
return False
return True Try / catch
try:
M = _solve_weighted_orthogonal_problem(src, dst, w)
except ValueError as e:
if 'denominator collapsed' in str(e):
use_previous_frame_transform(M_prev) # or skip frame
else:
raise Prevention
- Validate landmark spread (variance) per frame before the geometry solve
- Skip or interpolate frames where the face detector reports low confidence
- Keep weights strictly positive and normalized
When it happens
Trigger: Calling _estimate_scale/_solve_weighted_procrustes with a canonical landmark array where every 3D point is the same coordinate, or with an all-zero weight vector (w_total ~ 0 makes centered ~ 0). Happens when a face-landmark model outputs constant/garbage landmarks for a frame with no detectable face.
Common situations: Running the face geometry pipeline on blank frames, fully occluded faces, or a mis-loaded landmark model that emits a constant template. Also when upstream code passes an uninitialized (zeros) landmark tensor or zero weights into the solver.
AI-assisted analysis of Comfy-Org/ComfyUI@1c6d8d45b3 (2026-08-14).
Data as JSON: /api/errors/5cdd97597e27aca1.
Report an issue: GitHub.