Nodes/ComfyUI 1hewNodes/Image Align Change Mask
ComfyUI Node

Image Align Change Mask

Get a mask of only what your edit model actually changed

By 1hew·Created about a year ago·Updated 2 days ago· 33
Image Align Change Mask
  • edit_image
  • org_image
  • align_image
  • clean_mask
  • raw_mask
  • change_score
  • overlay_image
modeauto
max_offset8
max_rotation2.0
max_scale0.030
min_align_score0.80
color_compensationtrue
sensitivity8.0
min_component_area32
expand8
feather4

The problem it solves

Instruction editing ate inpainting's lunch, with one exception nobody talks about: Kontext and Qwen-Image-Edit take the whole frame into context and hand back a whole new frame. The pixels you didn't ask about come back close, not identical. The KB's inpainting doc has the quotes - "QwenEdit don't have inpaint. The details after editing are looking similar but not the same."

That's a problem the moment you need the rest of the picture to hold still: you have an edit, you have the original, and you need a mask of what moved. Painting it by hand is miserable, and diffing the two naively gives you the edit plus every bit of global color drift, resampling noise and one-pixel misregistration.

Image Align Change Mask does the two steps in order: register the edit onto the original, then decide which differences are real changes. It's the crop-and-stitch "composite this region back" move, except the region is computed for you.

How it works

Alignment first. mode picks the regressor. translation seeds with phase correlation, then refines with OpenCV's ECC (findTransformECC) under MOTION_TRANSLATION. similarity goes harder: AKAZE keypoints (SIFT/ORB fallback), a ratio-test match, RANSAC estimateAffinePartial2D, an ECC affine refine - and then it projects the affine onto the nearest pure rotation + isotropic scale, deliberately discarding shear so a shear term can't quietly explain away a real local edit. auto evaluates identity, translation and similarity, keeping the more complex candidate only if it beats the simpler residual by a margin (1% for translation, 2.5% for similarity).

No optical flow, on purpose. The source docstring spells it out: a dense warp can make a genuine generated edit disappear. Locally, "this region changed" and "this region is misregistered" look identical, and a flow field will always choose the second explanation.

Then color drift, then change. With color_compensation on, it fits a per-channel gain + bias on pixels that look stable (trimmed to the lowest-60% residual, slope clamped to 0.75–1.25, bias ±0.12), validates that on held-out blocks, and keeps it only if held-out error improves by 1%. Then each pixel is scored: Lab color distance over a 4-unit noise floor, and a robust z-score of the gradient (edge) difference, combined with max. Threshold at sensitivity, hysteresis so weak pixels survive only when connected to strong ones, drop blobs under min_component_area, dilate by expand, blur by feather.

That compensation step is why an edit that "comes back warmer" - the Flux 2 drift the KB's troubleshooting doc flags - doesn't paint a mask ring around the whole frame.

Inputs and outputs that matter

On the input side, edit_image and org_image are the only mandatory wiring, and the constraints are real: same pixel dimensions, batches equal or one side of size 1. The four settings you'll actually touch are sensitivity (default 8 - lower is more sensitive; it's a statistical cutoff, not a percentage), min_component_area (32, your speckle killer), expand (8, which grows the mask so you don't guillotine the edge of the change), and feather (4). max_offset / max_rotation / max_scale are the aperture you allow the aligner.

Five outputs, and three are genuinely useful:

  • align_image - the edit, warped onto the original's geometry. Composite from this, not from your raw edit.
  • clean_mask - the production mask. Wire it into your inpaint/SetLatentNoiseMask path.
  • change_score - a MASK, not a scalar: a normalized 0–1 map of how different each pixel is. Threshold it yourself if clean_mask is too eager.
  • overlay_image - the original with changed areas tinted red. Check this first, every time; one glance tells you whether the alignment worked or the node is flagging the whole frame.
  • raw_mask - the same mask before morphology, for diagnosing clean_mask.

Install

ComfyUI Manager → search 1hewNodes, or:

cd ComfyUI/custom_nodes
git clone https://github.com/1hew/ComfyUI-1hewNodes

Restart ComfyUI. The pack's requirements.txt is long - ultralytics, rembg, onnxruntime, av - but those serve its detection and video nodes. This one needs numpy, Pillow and opencv-python, which you almost certainly have. The pack is built on the new comfy_api.latest schema, so a very old ComfyUI install won't import it at all.

Common issues

"must have equal batch sizes, or one input must have batch size 1" / "must have the same dimensions". Load the original once and batch the edits, and resize the edit if your API returned a different resolution - this is not a resize node. Unequal batches of more than one error out rather than guessing.

The mask covers the whole frame, or a ring around everything. Two causes. Either nothing beat min_align_score and the node silently fell back to identity - soft, low-contrast photos often score under 0.8 on ECC - so genuine misregistration got counted as change; drop min_align_score toward 0.6–0.7. Or the drift exceeds max_offset / max_rotation / max_scale, in which case raise the limits - or stop feeding it a re-encoded JPEG of the original.

One silent trap in the source: with mode: auto or translation, the translation candidate is skipped entirely when max_offset is 0. Set it to 0 expecting "no translation allowed" and you also lose auto's translation path. And budget time accordingly - feature detection, an ECC optimisation up to 100 iterations, and two Lab conversions per frame, all on CPU.

Category1hewNodes/image

Inputs (12)

NameTypeDefaultDescription
edit_imageIMAGE
org_imageIMAGE
modeCOMBOautoAuto evaluates identity, translation, and constrained similarity candidates.
max_offsetINT80–128
max_rotationFLOAT2.00–30
max_scaleFLOAT0.0300–0.25
min_align_scoreFLOAT0.800–1
color_compensationBOOLEANtrue
sensitivityFLOAT8.00.5–20
min_component_areaINT320–100000
expandINT80–128
featherINT40–128

Outputs (5)

NameTypeDescription
align_imageIMAGE
clean_maskMASK
raw_maskMASK
change_scoreMASK
overlay_imageIMAGE