Nodes/ComfyUI-JH-PixelPro/Geometry: Auto Align
ComfyUI Node

Geometry: Auto Align

Line Two Photos Up Without Nudging Anything by Hand

By jetthuangai·Created 6 months ago·Updated a day ago· 6
Geometry: Auto Align
  • image
  • reference
  • aligned
  • valid_mask
  • matrix_json
  • inliers
◄methodsift►
◄modelhomography►
◄max_side1024►
◄ransac_threshold3.0►
◄min_inliers12►

Every image comparison breaks the same way: the two shots are two pixels apart, and your diff, your color match, your blend all read that miss as a real difference. Geometry: Auto Align (JHPixelProAutoAlign) fixes the boring half of that problem automatically. Feed it a photo and a reference, it finds the same features in both, works out the transform, and warps one onto the other. No markers, no eyeballed offsets.

This is the setup step, not the result. It's what you run before the interesting deterministic work in the post-processing layer - color matching, layer blends, before/after diffs. Statistics-based color transfer (the Reinhard-style mean/std thing that Color Match Image and the PixelPro Color Matcher both do) is only as good as the region it samples; if half your pixels are sky in one frame and wall in the other, it quietly measures the wrong thing. Align first and it measures the right thing.

How it works

Both images get turned into greyscale copies, shrunk so their long side is at most max_side. With method = sift you get classic SIFT keypoints with mutual nearest-neighbour matching - no download, runs anywhere. With method = loftr you get Kornia's LoFTR, a learned dense matcher that survives lighting changes and repeated texture where SIFT gives up; it pulls a ~45 MB checkpoint into the torch hub cache the first time you touch it.

Either way the matches then go through RANSAC (ransac_threshold, in pixels at matching size), which fits the model on random subsets of matches and keeps the fit the most matches agree with. Surviving inliers get a least-squares refit, the matrix is scaled back up to full resolution, and the warp is a bilinear warp_perspective. Matching happens small, the warp happens full-size - that's why max_side doesn't cost you sharpness.

What you actually set

image is the picture to move; reference is the target frame, and the output takes its size. Batch of 1, or matching the image batch.

model is the choice worth thinking about, and the tooltip is honest about it. homography handles perspective change - a crop and an original, a slight tilt. affine (rotate/scale/shear/shift only) is the steadier of the two when the scene is near-flat or you only have a handful of matches, because it has fewer degrees of freedom to fit noise with.

min_inliers (default 12) is your safety valve. Below it, the node does not error - it logs a warning, resizes the image to the reference size, and hands you an all-white valid_mask. That's a trap if you only look at the picture.

The outputs: aligned is the warped image, valid_mask is 1 wherever the warp has real pixels (the borders after a rotation are black - use this to restrict your stats or crop), matrix_json is a JSON list of 3×3 matrices in full-resolution pixels, and inliers is your quality signal.

One catch about inliers

inliers reports the first item of the batch only. For a batch, read matrix_json instead: a matrix that's pure scale means that frame hit the fallback. Looks fine, isn't aligned.

Install

ComfyUI Manager: search ComfyUI-JH-PixelPro, install, restart. Manually:

cd ComfyUI/custom_nodes
git clone https://github.com/jetthuangai/ComfyUI-JH-PixelPro.git
cd ComfyUI-JH-PixelPro
pip install -r requirements.txt   # use the Python that runs ComfyUI

Restart, then find it at Add Node → ComfyUI-JH-PixelPro → geometry. The pack pulls kornia>=0.7, mediapipe>=0.10.33, opencv-python-headless>=4.8 and scipy>=1.10; PyTorch comes from ComfyUI itself. onnxruntime is optional and only matters for the depth nodes - this one doesn't need it.

Where people get burned

  • Black borders after warping. Expected. Either crop with valid_mask or feed the mask into whatever comes next.
  • loftr on a first offline run. The 45 MB checkpoint downloads on first use; on a machine with no outbound access that's a failure, not a slow start. Keep sift if you're air-gapped.
  • Two shots of totally different scenes. It won't refuse; it'll find a handful of accidental matches if it can. Check inliers against your min_inliers before trusting the output.
  • Big lighting shift, soft focus, wallpaper-like repeated texture. SIFT starves here - this is exactly the case loftr exists for.

One aside: the pack itself is Apache-2.0, actively versioned (1.10.0, 64 nodes), with bilingual docs and a pile of automated tests - and, as of writing, essentially zero chatter on Reddit. If you go looking for tutorials you'll find nothing, which is why the node's own tooltips are worth reading instead of skimming.

For a hand-held exposure bracket, this is the aligner you want before HDR Merge can't help you - that node only does translation. For everything else, it's the quiet step that makes the next three steps work.

CategoryComfyUI-JH-PixelPro/geometry

Inputs (7)

NameTypeDefaultDescription
imageIMAGEImage to warp onto the reference.
referenceIMAGETarget frame. Output has this size. Batch 1 or same as image.
methodCOMBOsiftsift: classic keypoints, no download. loftr: learned dense matcher for hard pairs; downloads ~45 MB on first use.
modelCOMBOhomographyhomography handles perspective change; affine (rotate/scale/shear/shift) is steadier for near-flat scenes or few matches.
max_sideINT1024256–2048Longest side used for matching. The warp uses full resolution.
ransac_thresholdFLOAT3.00.5–20Max error (pixels at matching size) for a match to count.
min_inliersINT124–500Below this many agreeing matches the image is just resized to the reference size (warning in the log).

Outputs (4)

NameTypeDescription
alignedIMAGEImage warped into the reference frame.
valid_maskMASK1 where the warped image has real pixels.
matrix_jsonSTRINGJSON list of 3x3 matrices, one per batch item (image to reference pixels).
inliersINTAgreeing-match count of the first batch item only.