Nodes/ComfyUI CV/cv2.SVBackSubst
ComfyUI Node

cv2.SVBackSubst

The solve step that turns an SVD into an answer

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.SVBackSubst
  • w
  • u
  • vt
  • rhs
  • nparray

An SVD on its own doesn't solve anything - it's a factorization. cv2.SVBackSubst is the other half: given the w, u and vt from a decomposition and a right-hand side, it produces the solution vector. Written out, it computes x = V · diag(1/w) · Uᵀ · b, which is the pseudo-inverse solve, which is least squares, which is what you almost always actually wanted.

The reason to split it into two nodes instead of calling a solve function is the gap between them. In that gap you can edit w. Zero the small singular values and the solve becomes a rank-truncated least-squares fit that ignores the directions your data barely constrains - that's the whole trick, and it's why this pair exists.

One of roughly 470 auto-generated raw cv2.* wrappers in ComfyUI CV (bmad4ever/comfyui_cv). The usual provenance note applies: LLM-generated wrappers, uncurated, verify before trusting.

Inputs and outputs

Four required inputs, all NPARRAY-only data arrays - no image sockets anywhere on this node:

  • w - the singular values, straight out of cv2.SVDecomp's w output.
  • u and vt - the other two factors from the same decomposition.
  • rhs - the right-hand side b. An N×1 (or N×k) float array; build it with Parse Matrix or any array-producing node.

The single output is nparray: the solution x. Feed it into Inspect CV Data to read it, CV Matrix Multiply to check A · x ≈ b, or anywhere else in the ndarray half of the pack.

The important constraint is that w, u, vt and rhs must be consistent: the factors have to come from the same decomposition of a matrix whose row count matches the length of rhs. Mix factors from two different matrices and you get a plausible-looking vector of nonsense, with no error to warn you.

The pipeline, spelled out

Parse Matrix  ──> cv2.SVDecomp ──> w, u, vt ─┐
   (A)                                        ├─> cv2.SVBackSubst ──> x
Parse Matrix  ────────────────────> rhs ──────┘
   (b)

That's a least-squares solve of A x = b. If A is square and well-conditioned, x is the exact solution. If it's overdetermined - more equations than unknowns, the normal case for anything fitted from measurements - x is the best fit in the least-squares sense, without you forming the normal equations and squaring the condition number.

Add one step for truncation: after the decomposition, scale or zero the small entries of w before it reaches this node. Discarding everything below, say, 1% of the largest singular value throws away the directions the data doesn't support, which is regularisation by hand and often the difference between a wildly unstable fit and a usable one.

In this pack's terms, that's the honest use of these two wrappers together: not "compute an SVD because SVDs are cool", but "fit a linear model to measurements that contain outliers or near-degenerate directions, and keep it from exploding". Camera-pose and homography work is where that matters most, alongside the pack's curated CV Find Homography (RANSAC) and CV Solve PnP (Pose) nodes, which do the robust part for you.

Installing the pack

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
cd comfyui_cv && pip install "opencv-contrib-python-headless~=5.0.0.93"

Manager → search ComfyUI CV → install → restart, same result. Python ≥ 3.12 and a ComfyUI with the V3 node API are required or the nodes never appear. Keep the contrib OpenCV wheel: all four distributions share one site-packages/cv2, so installing plain opencv-python over the contrib build empties the contrib submodules, and tools/repair_opencv_contrib.py --check then --apply is the repair. Pinned to 5.0.0.93; updates aren't planned.

Where it bites

Feeding w, u, vt from a decomposition where you set SVD_NO_UV - there's no u or vt to give, because you asked cv2 not to compute them. Using a reduced (none (0)) decomposition with a rhs that has more rows than the smaller matrix dimension; set SVD_FULL_UV on the cv2.SVDecomp node instead. And a zero in w: back-substitution divides by it. That's exactly the case truncation exists to handle, so zero it out deliberately rather than letting the division happen.

Categoryimage/CV/low-level/cv2 S

Inputs (4)

NameTypeDefaultDescription
wNPARRAY - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
uNPARRAY - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
vtNPARRAY - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
rhsNPARRAY - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.

Outputs (1)

NameTypeDescription
nparrayNPARRAY—