Low-light denoising + 4x super-resolution
Trained for Kaggle competition dlp-26t2-nppe3. Public leaderboard 39.85 dB PSNR.
The task is posed as super-resolution but is mostly denoising. Bicubic upsampling of the noisy input scores 33.36 dB; bicubic upsampling of a clean low-resolution image scores 41.19 dB. About 7.8 of the 8 dB available is therefore denoising, so all 24 residual blocks run at low resolution and the upsampling is a thin pixel-shuffle branch on top of a bicubic base.
The grader scores PIL.Image.convert('L') only. Because the noise is independent across
channels (measured cross-channel correlation +0.001), that projection removes about 55% of
its variance for free, so the network predicts luma directly with a single output channel
and writes mode-L PNGs.
The degradation was identified exactly as LR = box_downsample(HR, 4) - 0.70 + n, with n
white, per-channel independent, sigma 10.32/255, excess kurtosis +0.42. Real noise fields
are therefore lifted off one training image and added to another, giving a fresh noise
realisation every step instead of one fixed realisation per image.
Usage
import torch
from huggingface_hub import hf_hub_download
from train_ddp import DenoiseSR # train_ddp.py is in this repo
ck = torch.load(hf_hub_download("ChemYukti/denoise-sr4x-dlp26t2nppe3", "model.pt"), map_location="cpu")
model = DenoiseSR(ck["nf"], ck["nb"])
model.load_state_dict(ck["sd"]); model.eval()
Input is float RGB in [0,1], shape (B,3,H,W). Returns (hr_luma, denoised_lr_luma); the
first is the prediction, at 4x the input size. Averaging the 8 rotation/flip variants at
inference is worth about +0.03 dB.
Training
160 channels, 24 residual blocks with channel attention on every 4th, 11.73 M parameters. Charbonnier for the first 60% then MSE, AdamW, cosine to 1e-6, EMA 0.999, fp16 with channels_last, 2x Tesla T4 via DistributedDataParallel, 80,000 iterations at effective batch 32.
Results
| PSNR | |
|---|---|
| bicubic from noisy LR | 33.36 dB |
| bicubic from clean LR (denoising-only ceiling) | 41.19 dB |
| this model, 40 held-out images | 39.00 dB |
| public leaderboard | 39.85 dB |
Held-out means 40 validation images excluded from training entirely. Scores over all 267 validation images come out about 0.5 dB higher and are not comparable, since 227 of them were in the training set.
The 39.92 dB leaderboard entry came from a sibling run of this same pipeline (128 channels, 32 blocks, 126,779 iterations). Its checkpoint was lost to a failed Kaggle commit, so the weights published here are the 160-channel, 24-block variant, 0.08 dB behind on held-out data.