Choosing Blending Modes for Orthomosaic Quality

Every mosaicking tool offers a blending choice, and the default is usually feathering because it makes the fewest people complain. For a survey deliverable that is not the same thing as being correct: blending trades measurable sharpness for a less visible seam, and on a mosaic that will be digitised against, that trade can be the wrong way round.

This page sets out what each mode does to the pixels, what it costs, and which deliverable each suits. It belongs to orthomosaic radiometry and seamline control.

The three modes

Hard cut. Every output pixel comes from exactly one source image. Nothing is averaged, so nothing is blurred. Every residual difference appears as a sharp step along the seamline.

Feathering. Within a transition zone either side of the seamline, output pixels are a weighted average of the two sources, with the weight running smoothly from one to the other. The step becomes a gradient. Any misregistration between the two images becomes a double image across that zone.

Multi-band blending. The images are decomposed into frequency bands. Low frequencies — the broad brightness differences — are blended over a wide zone; high frequencies — the detail — are blended over a very narrow one, or taken from a single source. The brightness step disappears without the detail smearing.

What each blending mode does to a brightness step and to image detail Three columns compare the modes on two rows. On the brightness row, hard cut leaves a sharp step, feathering converts it to a smooth ramp across the transition zone, and multi-band blending removes it over a wide zone. On the detail row, hard cut preserves detail fully with a visible discontinuity, feathering smears detail across the whole transition zone producing a double image where registration is imperfect, and multi-band blending preserves detail because high frequencies are blended over a very narrow zone or taken from one source only. hard cut feathering multi-band brightness across the seam sharp step ramp across the zone removed over a wide zone detail across the seam fully preserved, discontinuity visible smeared across the zone, doubling if misregistered preserved: high frequencies blended narrowly or not at all Multi-band separates the two problems and solves each at its own scale.

Figure 1 — The same seam, three treatments.

Minimal reproducible solution

Feathering is a weight computed from distance to the seam, and the distance transform does all the work.

import numpy as np
from scipy import ndimage


def feather_weights(mask_a: np.ndarray, mask_b: np.ndarray,
                    *, width: int = 48) -> np.ndarray:
    """Blend weight for image A, running 1 → 0 across the transition zone.

    Distance to the other image's exclusive area is a better driver than
    distance to the seamline itself: it degrades gracefully where the overlap
    is narrower than the requested width, rather than producing a truncated
    ramp with a residual step at its end.
    """
    only_b = mask_b & ~mask_a
    distance = ndimage.distance_transform_edt(~only_b).astype("float32")
    weight = np.clip(distance / float(width), 0.0, 1.0)
    weight[~mask_a] = 0.0
    return weight


def feather_blend(img_a: np.ndarray, img_b: np.ndarray,
                  mask_a: np.ndarray, mask_b: np.ndarray,
                  *, width: int = 48) -> np.ndarray:
    """Weighted average of two co-registered images across their overlap."""
    wa = feather_weights(mask_a, mask_b, width=width)
    wb = feather_weights(mask_b, mask_a, width=width)
    total = wa + wb
    total[total == 0] = 1.0
    return (img_a.astype("float32") * wa + img_b.astype("float32") * wb) / total

The width parameter is the whole decision. It should be wide enough that the brightness step is spread below visibility and narrow enough that misregistration does not become a visible double image — which is a real tension, and multi-band blending exists precisely because the two requirements pull in opposite directions.

Choosing by deliverable

The right mode follows from what the mosaic is for.

Digitising and measurement. Hard cut, with well-routed seamlines. Any averaging displaces edges slightly, and an operator digitising a kerb line against a feathered mosaic is tracing a compromise between two views of it. Sharpness is the product; a visible seam along a hedge is an acceptable price.

Visual presentation and web maps. Multi-band, or feathering if multi-band is unavailable. The mosaic will be viewed at reduced scale by people who will judge it on appearance, and the sub-pixel displacement is irrelevant to that use.

Change detection between epochs. Hard cut, and the same seamlines each time if at all possible. Blending introduces an epoch-specific smoothing that a difference operation will read as change, which is the most insidious error on this page because it looks like a result.

Input to automated analysis. Depends on the analysis. Anything that thresholds or classifies on texture is disturbed by feathering; anything that works on broad radiometry is disturbed by hard cuts.

Blending mode selected by what the mosaic is for Four deliverable types each map to a recommended mode. Digitising and measurement maps to hard cut with well-routed seamlines, because averaging displaces edges. Visual presentation and web maps map to multi-band blending, or feathering where multi-band is unavailable. Change detection between epochs maps to hard cut with identical seamlines across epochs, because blending introduces an epoch-specific smoothing that differencing reads as change. Automated analysis maps to a conditional answer depending on whether the analysis works on texture or on broad radiometry. digitising & measurement → hard cut averaging displaces edges; an operator should trace one view, not a compromise presentation & web maps → multi-band judged on appearance at reduced scale; sub-pixel displacement is irrelevant change detection → hard cut, identical seamlines each epoch blending adds epoch-specific smoothing that differencing reads as change automated analysis → depends on the analysis texture methods dislike feathering; broad-radiometry methods dislike hard cuts

Figure 2 — The deliverable decides, not the appearance at full extent.

Implementing multi-band blending

The idea behind multi-band blending is that the two requirements pulling against each other — a wide zone for brightness, a narrow one for detail — apply to different parts of the signal, so they can be satisfied separately and recombined.

Decompose each image into a small pyramid of frequency bands. The coarsest band holds the broad brightness differences; the finest holds edges and texture. Blend each band with a zone width proportional to that band’s scale, then sum the results. The brightness difference is spread over hundreds of pixels while edges are handed over across two or three.

def gaussian_pyramid(img: np.ndarray, levels: int = 5) -> list[np.ndarray]:
    """Successively smoothed copies, each at half the previous detail scale."""
    out = [img.astype("float32")]
    for _ in range(levels - 1):
        out.append(ndimage.gaussian_filter(out[-1], sigma=2.0))
    return out


def laplacian_pyramid(img: np.ndarray, levels: int = 5) -> list[np.ndarray]:
    """Difference of successive smoothings, plus the smoothest residual.

    Each level holds the detail present at one scale and absent at the next,
    which is exactly the decomposition the blend needs: the finest level is
    edges, the coarsest residual is broad brightness.
    """
    g = gaussian_pyramid(img, levels)
    return [g[i] - g[i + 1] for i in range(levels - 1)] + [g[-1]]


def multiband_blend(img_a: np.ndarray, img_b: np.ndarray,
                    mask_a: np.ndarray, mask_b: np.ndarray,
                    *, levels: int = 5, base_width: int = 6) -> np.ndarray:
    """Blend each frequency band with a zone sized to that band's scale.

    Width doubles per level, so the finest band hands over across a handful of
    pixels and the coarsest across a few hundred — which is the entire trick.
    """
    la = laplacian_pyramid(img_a, levels)
    lb = laplacian_pyramid(img_b, levels)

    out = np.zeros_like(la[0])
    for level in range(levels):
        width = base_width * (2 ** level)
        wa = feather_weights(mask_a, mask_b, width=width)
        wb = feather_weights(mask_b, mask_a, width=width)
        total = wa + wb
        total[total == 0] = 1.0
        out += (la[level] * wa + lb[level] * wb) / total
    return out

Five levels with a base width of six pixels gives a finest-band handover of six pixels and a coarsest of ninety-six, which suits typical survey ground sample distances of two to five centimetres. On a coarser mosaic — say, a 15 cm product — halve the level count, because the fine bands hold little beyond noise at that resolution and blending them wastes memory for no visible gain.

The memory cost is real: five levels of float32 for two images is ten times the working set of a hard cut. On large mosaics this is the reason to process by tile, with a halo at least as wide as the coarsest band’s blend zone so that no tile boundary falls inside an active transition.

The cost each blending mode imposes, stated as a measurement Three rows. A hard cut costs nothing in sharpness and costs a step of whatever the residual radiometric difference is, measured in digital numbers across the seam. Feathering costs sharpness across the transition zone, measurable as a reduction in gradient energy inside the zone, and reduces the step to that difference divided by the zone width. Multi-band blending costs memory and processing time roughly proportional to the level count, and costs sharpness only within the finest band's narrow zone, which is a handful of pixels. hard cut no sharpness cost; a step of the full residual difference feathering gradient energy lost across the zone; the step spread over its width multi-band memory and time by level count; sharpness lost over a handful of pixels Each cost is measurable, which means the choice can be argued from numbers rather than taste.

Figure 3 — Three modes, three measurable costs.

Edge-case matrix

Situation Effect Handling
Feathering a measurement mosaic Edges displaced Hard cut instead
Wide feather, poor registration Visible doubling Narrow the zone or fix alignment
Narrow feather, big step Step merely blurred Fix the balance, not the blend
Feather wider than the overlap Truncated ramp, residual step Drive weights from distance, not seam
Blending across epochs Smoothing read as change Hard cut, consistent seamlines
Multi-band on a huge mosaic Memory pressure Process by tile with a halo
Blending over a building Double roof Exclude tall areas from the zone
Nodata inside the zone Weights collapse Guard the zero-weight case

Verification snippet

def sharpness_cost(hard_cut: np.ndarray, blended: np.ndarray,
                   seam_zone: np.ndarray) -> dict:
    """How much detail did blending cost, measured inside the zone only?

    Comparing over the whole mosaic dilutes the effect into invisibility: the
    zone is a small fraction of the area, so a severe local smear barely moves
    a global statistic.
    """
    def energy(a):
        gy, gx = np.gradient(a.astype("float32"))
        return float(np.hypot(gx, gy)[seam_zone].mean())

    before, after = energy(hard_cut), energy(blended)
    return {
        "gradient_before": before,
        "gradient_after": after,
        "detail_retained": after / before if before else 1.0,
        # Below about 0.85, the blend is costing real sharpness where an
        # operator will be digitising.
        "acceptable_for_measurement": (after / before if before else 1.0) > 0.85,
    }

When to escalate

  • No blend width works. The images are misaligned. Fix the reconstruction; blending cannot hide a registration error, only spread it.
  • Multi-band exhausts memory. Tile the mosaic with a halo at least as wide as the widest blend zone, and blend each tile independently.
  • Two deliverables want different modes. Produce both. Mosaicking is cheap next to reconstruction, and a measurement mosaic and a presentation mosaic from the same project is a routine deliverable pair.

Orthomosaic Radiometry and Seamline Control