Nodes/ComfyUI CV/cv2.estimateAffinePartial2D
ComfyUI Node

cv2.estimateAffinePartial2D

The 4-DOF fit that doesn't invent shear

By bmad4ever·Created 4 months ago·Updated 14 days ago· 1
cv2.estimateAffinePartial2D
  • from_
  • to
  • out
  • inliers
◄methodRANSAC►
◄ransacReprojThreshold3.0000►
◄maxIters2000►
◄confidence0.9900►
◄refineIters10►

There are three "fit a transform to 2D correspondences" nodes in this pack and they are not interchangeable. This is the middle one: a partial affine, meaning rotation, uniform scale and translation - four degrees of freedom instead of six. No shear, no independent X/Y stretch. The name is OpenCV's, and it's a poor description of what you get, which is a similarity transform: the rigid motion you'd use to align two photographs of the same flat thing taken with slightly different framing.

Why four DOF is often the right answer

A full affine (cv2.estimateAffine2D) will happily absorb noise as shear. Feed it slightly wrong correspondences from a face or a logo and you get a matrix with a small skew baked in - mathematically the best least-squares fit, visually a subtly wrong stamp. The partial version can't do that. It has no shear term to spend on errors, so the fit stays a rotation plus one scale, which is what those scenes actually are. With few points, or points you don't fully trust, that constraint is a feature.

Two point pairs are the theoretical minimum; in practice you feed it tens and let RANSAC do the work. It won't do perspective - that's CV Find Homography (RANSAC) in this pack - and if a shape genuinely stretches differently in X and Y (an anamorphic squeeze, a scan with a paper-handling stretch), you need the full affine.

The inputs

from_ and to: NPARRAY only, N x 2, equal length, paired in order (from_ keeps its underscore because from is a Python keyword; it's just a name). The usual source is this pack's curated CV Detect Features → CV Match Features chain, or CV Points for hand-typed correspondences.

Then the same RANSAC family of knobs as the full affine, with the same defaults and the same advice:

  • method - RANSAC (default) or LMEDS, the least-median variant for when more than half your matches are wrong.
  • ransacReprojThreshold - 3.0, in pixels of the destination image. This is the knob that actually decides your result.
  • maxIters (2000) and confidence (0.99) - budget and target certainty. Under ~0.9 confidence starts producing wrong transforms; over 0.99 just costs time.
  • refineIters (10) - Levenberg-Marquardt refinement of the consensus. 0 disables it, giving you exactly what RANSAC found.

Outputs are out, the 2×3 matrix on an NPARRAY socket, and inliers, the N×1 mask of points that survived. out is wire-compatible with cv2.warpAffine's M input, with the destination size coming from CV Array Size. Because warpAffine echoes the input format, an IMAGE in means an IMAGE out and the registration never forces you into raw-array land.

Where it fits

Alignment, stabilisation, and canonical-frame preparation. Straightening a photographed page or a poster; snapping a slightly rotated, slightly zoomed tile back into register before you compare or composite it; two frames of the same shot where the camera drifted. The identity corner is the same problem wearing a hat: face swap and identity tools all begin by fitting a crop to a canonical template, and a similarity transform is exactly the right model for "the same face, slightly rotated and slightly larger". That's why InsightFace owns that step - and why this pack also ships CV Affine Shape Warp, the curated fit-and-warp node to look at before building the chain by hand.

The other use is diagnostic. Run both this and cv2.estimateAffine2D on the same correspondences and compare the inliers counts: if the full affine keeps meaningfully more points, your scene genuinely has perspective or anisotropic stretch; if it keeps about the same, the extra two degrees of freedom were fitting noise, and you should ship the partial one.

Installing it

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

ComfyUI Manager: search comfyui_cv and restart. Python ≥ 3.12, recent V3-API ComfyUI. It lives under image/CV/low-level/cv2 E alongside its two siblings.

Traps

out is the matrix, not a warped image - wiring it into an IMAGE input to "see what happened" gets you nothing useful; preview it with CV Inspect CV Data. Point sets must be float and equally long, or OpenCV raises. The classic silent failure is a confident-looking matrix fit from four of three hundred matches: always look at the inlier count before you trust out. And be honest about the scene - if two points of a rectangle land within a pixel but the fourth doesn't, the difference between this and the full affine is exactly the bug you're chasing.

Categoryimage/CV/low-level/cv2 E

Inputs (7)

NameTypeDefaultDescription
from_NPARRAY - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
toNPARRAYSecond input 2D point set. A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here.
methodoptCOMBORANSACRobust method used to compute transformation. The following methods are possible: - - RANSAC-based robust method - - Least-Median robust method RANSAC is the default method.
ransacReprojThresholdoptFLOAT3.0000-1e+38–1e+38Maximum reprojection error in the RANSAC algorithm to consider a point as an inlier. Applies only to RANSAC. Preset to the OpenCV default (3.0).
maxItersoptINT2000-2147483648–2147483647The maximum number of robust method iterations. Preset to the OpenCV default (2000).
confidenceoptFLOAT0.9900-1e+38–1e+38Confidence level, between 0 and 1, for the estimated transformation. Anything between 0.95 and 0.99 is usually good enough. Values too close to 1 can slow down the estimation significantly. Values lower than 0.8-0.9 can result in an incorrectly estimated transformation. Preset to the OpenCV default (0.99).
refineItersoptINT10-2147483648–2147483647Maximum number of iterations of refining algorithm (Levenberg-Marquardt). Passing 0 will disable refining, so the output matrix will be output of robust method. Preset to the OpenCV default (10).

Outputs (2)

NameTypeDescription
outNPARRAY—
inliersNPARRAY—