QAlign
The node that warps an edit model's drift back onto the original
- reference
- source
- subject_mask
- aligned_image
- overlay_preview
- difference_preview
- alignment_report
QAlign is a one-trick node: you have two pictures of the same scene, one has drifted or stretched a little, and you want the geometry of one without the pixels of the other. It generates nothing, calls no API, ships no weights. It's OpenCV image registration wearing a ComfyUI hat.
Why you'd want this
Spend any time with an instruction editor and you know the failure. The model re-emits the whole frame rather than patching it, so unedited regions come back close but not identical, and the error lands as a geometric offset that distorts proportions - the complaint behind the most-upvoted "image offset problem of Qwen-image-edit" post of 2025. Faces drift, shirts wander.
The established fixes are upstream: keep the input resolution at a sensible multiple so the model stops hallucinating the offset, or crop to a mask, edit, and stitch. QAlign is the third option, and the after-the-fact one: measure the drift, then warp it out. Same category of fix as Klein's PixelDriftFix node, different mechanism.
The author's framing is the whole design: the reference defines where things belong; the source defines how they look. Your aligned output is transformed source pixels - the reference contributes geometry and nothing else, so lighting, texture, and color stay wrong-on-purpose.
How it works
Classical computer vision, no learned model anywhere. The pair becomes 8-bit RGB, and a blurred grayscale copy gets CLAHE'd so uneven lighting is less of a problem for matching.
Automatic mode runs feature registration: ORB keypoints (up to 10,000), a brute-force Hamming matcher, a Lowe-style ratio test to drop ambiguous matches, then a v0.5.0 addition that buckets the survivors into an 8×8 grid so one textured region can't dominate. RANSAC fits an affine transform, accepted only if it clears your translation/rotation/scale limits with at least eight inliers - otherwise it falls back to multi-scale ECC, and the report says which path won.
Hybrid, Affine, and Translation skip features: phase correlation supplies a translation seed, then multi-scale ECC refines it (Translation stops at the seed). Local correction then runs Farneback dense optical flow, smooths the field with a Gaussian, clips it to max_local_flow, scales it by correction_strength, and remaps. The final warp is Lanczos with reflected borders.
Inputs worth touching
Wire reference (correct geometry) and source (the appearance you're keeping) in that order; backwards makes the node faithfully move the wrong image. alignment_mode has a small trap: the README table lists Affine, but that's only the default when you call the node through the API - the UI gives you the first dropdown entry, Automatic, which is where you want to start anyway. max_global_shift is the first thing to raise if the output is massively offset. And the scale-deviation and rotation values are acceptance limits, not requests: max_scale_deviation_x = 0.08 permits 0.92–1.08, it doesn't apply an 8% correction.
For local correction, turning local_correction off first is the discipline - get the global transform right, check the overlay, then enable flow. correction_strength and max_local_flow multiply into your actual maximum displacement; flow_smoothing decides how broadly it spreads. If flow bends a face or smears an edge, those three are your dials, in that order. The optional subject_mask gates local flow to a region and keeps the globally aligned source outside it; it doesn't affect the global estimate.
Outputs
aligned_image is the one you wire onward. overlay_preview is a 50/50 blend of reference and result when diagnostic_overlay is on - this is how you actually judge alignment, on eye corners and shoe outlines. difference_preview is a normalized grayscale absolute difference; it stays bright wherever clothing or lighting changed, which is why it's a diagnostic and not a score. alignment_report is JSON: the transform, RANSAC inliers, which estimator supplied the matrix, and the flow statistics.
Install
Manager → search "Qalign", or:
cd ComfyUI/custom_nodes
git clone https://github.com/zuanfilm/Qalign
Restart and search the node menu for QAlign - no model files, no keys, Apache-2.0.
The one real dependency is OpenCV, and this pack's pyproject.toml declares no dependencies at all, so nothing installs cv2 for you - other packs usually bring it in, but a clean environment can miss it:
python -m pip install opencv-python
Where it goes wrong
Feature matching doesn't know what a subject is. A busy background, a repeated pattern, or a big outfit change can drive a transform that agrees with the wallpaper and not your character - high inlier ratio, wrong alignment - and the 8×8 balancing only partly mitigates that. Switch to Affine or Translation and compare. Also: only the first image in a batch is processed, so one pair per execution.
Two smaller traps. If dimensions differ, the source gets resized to the reference unless preserve_source_size is on, and a resize is itself a geometric operation - normalize both inputs first and let QAlign fix what's left. And a difference preview that never goes dark usually isn't an alignment failure; it's new clothes.
Inputs (19)
| Name | Type | Default | Description |
|---|---|---|---|
| reference | IMAGE | — | |
| source | IMAGE | — | |
| alignment_mode | COMBO | 5 options: Automatic, Hybrid, Translation, Affine, Local only | |
| local_correction | BOOLEAN | true | — |
| correction_strength | INT | 1000–100 | — |
| max_global_shift | INT | 2561–4096 | — |
| max_local_flow | FLOAT | 16.00–256 | — |
| flow_smoothing | INT | 813–301 | — |
| protect_background | BOOLEAN | false | — |
| preserve_source_size | BOOLEAN | false | — |
| diagnostic_overlay | BOOLEAN | true | — |
| pyramid_levels | INT | 41–6 | — |
| ecc_iterations | INT | 10010–500 | — |
| max_scale_deviation | FLOAT | 0.120.01–1 | — |
| max_rotation_degrees | FLOAT | 121–90 | — |
| scale_mode | COMBO | Anisotropic X/Y | 2 options: Full affine, Anisotropic X/Y |
| max_scale_deviation_x | FLOAT | 0.0800.005–1 | — |
| max_scale_deviation_y | FLOAT | 0.0800.005–1 | — |
| subject_maskopt | MASK | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| aligned_image | IMAGE | — |
| overlay_preview | IMAGE | — |
| difference_preview | IMAGE | — |
| alignment_report | STRING | — |