Image restoration
Image restoration takes a degraded image and returns a clean one. LibreYOLO exposes it as the restore task, which covers denoising, deblurring and super-resolution behind a single output contract: one RGB image in, one RGB image out.
Definition
The restore task maps one image to another image. Denoising, deblurring and
super-resolution are all the same task here, because they share one contract:
the model consumes an RGB image and returns an RGB image, and the degradation it
was trained to undo is a property of the checkpoint rather than of the API.
A prediction fills result.restored, a RestoredImage payload holding an
(H, W, 3) uint8 RGB array. .array returns it as NumPy and .save(path)
writes it to disk. result.restore_scale records the upscale factor the
output canvas carries, which is 1 for a checkpoint that preserves resolution.
result.boxes stays empty, so conf, iou and max_det are accepted for
signature parity but have no effect, and save=True writes the restored image
directly rather than an annotated photo.
Models
Three families serve restore, split by the degradation they undo.
NAFNet is the denoiser, and the only restore family LibreYOLO can train. Its architecture replaces the nonlinear activations of a UNet block with elementwise multiplication, and the published checkpoint is trained on SIDD real-image noise. Output stays at the input resolution.
Real-ESRGAN is the practical upscaler: three checkpoints trained against synthetic degradations rather than only bicubic downscaling, at 4x, 2x, and a smaller, faster 4x generator built for lower latency.
SwinIR upscales 4x with a Swin Transformer backbone, in three sizes covering the official lightweight generator and two real-world generators.
Predict
Weights download from Hugging Face on first use and are cached locally.
from libreyolo import LibreYOLO, SAMPLE_IMAGE # The compact 4x generator; tile bounds peak memory on a large source.model = LibreYOLO("LibreRealESRGANx4t-restore.pt")result = model(SAMPLE_IMAGE, tile=512, tile_pad=10) result.restored.save("upscaled.png")print(result.restored.array.shape) # 4x the input in each axisfrom libreyolo import LibreYOLO, SAMPLE_IMAGE # Trained on SIDD real-image noise; output stays at the input size.model = LibreYOLO("LibreNAFNetl-restore-sidd.pt")result = model(SAMPLE_IMAGE) result.restored.save("denoised.png")print(result.restore_scale) # 1: no upscale for this checkpointRestoration runs at the source image's own resolution rather than a fixed
network canvas, padding only to the network's downsample factor, so both time
and memory scale with the pixel count of your input. tile splits the forward
pass into overlapping tiles and blends the seams back together, and tile_pad
is the halo added around each tile before it is cropped back out; both are
Python keyword arguments. See prediction for sources, streaming
and result handling.
Dataset format
Restoration pairs each degraded input image with a clean target image of exactly the same resolution, matched by filename stem.
dataset/
data.yaml
inputs/
train/photo.jpg
val/photo.jpg
targets/
train/photo.jpg
val/photo.jpgpath: dataset
train: inputs/train
val: inputs/val
input_dir: inputs
target_dir: targets
degradation: denoise
dataset: MyDataset
nc: 1
names: {0: image}nc and names are schema placeholders; a restore model returns
Results.restored, not detections. degradation and dataset are optional
provenance labels. target_stem_suffix covers datasets that name the clean
image differently from its degraded pair. Validation keeps native resolution and
pads only enough to stack a batch, so the metrics are computed on the original
canvas. See dataset formats for the full
contract.
Train
NAFNet is the only restore family with a training implementation.
Real-ESRGAN.train() and SwinIR.train() both raise NotImplementedError:
those checkpoints come from GAN training over synthetic degradation pipelines,
and the paired restore trainer would run without reproducing that recipe.
from libreyolo import LibreYOLO model = LibreYOLO("LibreNAFNetl-restore-sidd.pt")model.train(data="my-dataset.yaml", epochs=100, imgsz=256, batch=16, lr0=1e-3)from libreyolo import LibreYOLO model = LibreYOLO("LibreNAFNetl-restore-sidd.pt") # degradation and dataset are written into the saved checkpoint for# provenance; they take no part in training.model.train( data="my-dataset.yaml", epochs=100, degradation="denoise", dataset="MyDataset",)The trainer takes coupled crops of the input and target pair, so both sides stay aligned. See training for datasets, multi-GPU and loggers, and the NAFNet page for this family's defaults and the inference-time pooling it detaches while training.
Validate
val() compares the restored output against the clean target, in RGB, on the
original canvas, with no border crop and no resizing.
from libreyolo import LibreYOLO model = LibreYOLO("LibreNAFNetl-restore-sidd.pt") # val() returns a plain dict, not an object.metrics = model.val(data="my-dataset.yaml") print(metrics["metrics/PSNR"]) # fitnessprint(metrics["metrics/SSIM"])metrics/PSNR is the peak signal-to-noise ratio in decibels, and it is also
fitness, the number best-checkpoint selection reads. metrics/SSIM is
structural similarity in [0, 1], computed with an 11x11 Gaussian window at
sigma 1.5 and averaged over the three color channels. Higher is better for both.
Export
An exported restore model loads back through LibreYOLO() on its file suffix,
so a .onnx or .engine file behaves like a checkpoint and returns the same
Results, with restored carrying the output image.
from libreyolo import LibreYOLO model = LibreYOLO("LibreNAFNetl-restore-sidd.pt") # imgsz is fixed into the graph, so pass the size your deployment# actually feeds the model.model.export(format="onnx", imgsz=256)from libreyolo import LibreYOLO, SAMPLE_IMAGE # The factory routes on the file suffix, so an exported artifact loads# like any checkpoint and returns the same Results object.model = LibreYOLO("LibreNAFNetl-restore-sidd.onnx")result = model(SAMPLE_IMAGE) result.restored.save("denoised.png")Restore export fixes the spatial resolution into the graph, so pass the imgsz
your deployment will actually feed the model. For NAFNet that size must divide
by the network's downsample factor, and only the batch dimension stays dynamic
under dynamic=True. For Real-ESRGAN and SwinIR, leaving imgsz out falls back
to a small internal patch size rather than your working resolution. Per-format
coverage is on each model page and in the
full export matrix. Export
lists the arguments every format accepts.