Image degradation pipeline — synthesise training data for restoration models

The Real-ESRGAN / GFPGAN "high-order degradation model": take a pristine image and synthesise the damage real-world low-quality photos carry, so a restoration network can learn to invert it. One order = anisotropic Gaussian blur → down/up resample (resolution loss) → sensor noise (gaussian / uniform / Poisson shot noise via the native addNoise op). SECOND_ORDER runs the chain twice — real-world damage is compounded, which is Real-ESRGAN's central trick. Outputs a clean | degraded side-by-side. The same pipeline ships as a batch CLI (samples/binaries/degrade.js → standalone `degrade` executable) for turning a folder of clean images into clean/degraded training pairs.

INPUT
INPUT — Image degradation pipeline — synthesise training data for restoration models
Clean | degraded pair
Clean | degraded pair — Image degradation pipeline — synthesise training data for restoration models
JavaScript
// Real-ESRGAN / GFPGAN-style image degradation pipeline — generate
// training data for restoration models. Outputs a clean | degraded
// side-by-side so the effect of each parameter is visible live.
// demo_degrade.js
//!INPUT: INPUT
//!OUTPUT: OUTPUT
//!PARAM: BLUR:number=1.4,min=0,max=8,step=0.1
//!PARAM: ANISO:number=0.7,min=0,max=4,step=0.1
//!PARAM: RESAMPLE:number=2.5,min=1,max=8,step=0.5
//!PARAM: NOISE:enum(gaussian|uniform|poisson)=poisson
//!PARAM: NOISE_SIGMA:number=0.06,min=0,max=0.3,step=0.005
//!PARAM: NOISE_COLOR:boolean=true
//!PARAM: SECOND_ORDER:boolean=true
//!SAMPLE light(BLUR=0.6, ANISO=0.2, RESAMPLE=1.5, NOISE=gaussian, NOISE_SIGMA=0.02, SECOND_ORDER=false)
//!SAMPLE heavy(BLUR=2.4, ANISO=1.2, RESAMPLE=4, NOISE=poisson, NOISE_SIGMA=0.12, NOISE_COLOR=true, SECOND_ORDER=true)
//!SAMPLE webcam(BLUR=1.0, ANISO=0.3, RESAMPLE=2, NOISE=poisson, NOISE_SIGMA=0.07, NOISE_COLOR=true, SECOND_ORDER=false)

// Restoration networks (Real-ESRGAN, GFPGAN, CodeFormer) are trained
// on synthetic pairs: take a pristine image, *degrade* it, and let the
// network learn to invert the damage. The quality of that synthetic
// degradation is what makes or breaks the model.
//
// This is the "high-order degradation model" — one order is:
//
//   1. blur     — anisotropic Gaussian (BLUR ± ANISO per axis)
//   2. resample — downscale by RESAMPLE then back up (resolution loss)
//   3. noise    — native addNoise: gaussian / uniform / poisson
//                 (Poisson = signal-dependent shot noise)
//
// SECOND_ORDER runs the chain twice — real-world damage is compounded,
// not single-stage, and that is Real-ESRGAN's central trick.
//
// The same pipeline ships as a batch CLI — `samples/binaries/degrade.js`
// compiles to a standalone `degrade` executable for turning a folder
// of clean images into clean/degraded training pairs.

const orig = Engine.loadImage(INPUT);
const W = orig.width, H = orig.height;

// The image we actually degrade — keep `orig` pristine for the
// side-by-side reference panel.
const dmg = orig.clone();

function degradeOnce() {
    if (BLUR > 0 || ANISO > 0) {
        dmg.anisotropicBlur(BLUR + ANISO, Math.max(0, BLUR - ANISO));
    }
    if (RESAMPLE > 1) {
        const dw = Math.max(1, Math.round(W / RESAMPLE));
        const dh = Math.max(1, Math.round(H / RESAMPLE));
        dmg.resize(dw, dh).resize(W, H);
    }
    if (NOISE_SIGMA > 0) {
        dmg.addNoise({ type: NOISE, sigma: NOISE_SIGMA, color: NOISE_COLOR });
    }
}

degradeOnce();
if (SECOND_ORDER) degradeOnce();

// Compose clean | degraded side-by-side.
const sheet = Engine.createImage(W * 2, H);
sheet.blendAt(orig, px(0, 0), 1.0);
sheet.blendAt(dmg,  px(W, 0), 1.0);
const label = new Pixel(1, 1, 1, 0.8);
sheet.drawText("CLEAN", 16, 32, { font: "JetBrains Mono", size: 22, color: label });
sheet.drawText("DEGRADED", W + 16, 32, { font: "JetBrains Mono", size: 22, color: label });
sheet.save(OUTPUT);

sheet.free();
dmg.free();
orig.free();

`degradation: blur ${BLUR}±${ANISO}, resample ${RESAMPLE}×, ${NOISE} σ=${NOISE_SIGMA}${SECOND_ORDER ? ", 2nd-order" : ""}`;

// © 2026 Michael Lechner · mlc OpticScript · https://mlcgo.eu · Elastic License 2.0