NPPE-3 β€” Low-light Denoising + 4x Super-Resolution (RRDBNet)

RRDBNet (Real-ESRGAN generator), 4x upscaling with joint denoising, trained for the DLP26T2 NPPE-3 competition. PSNR-oriented (Charbonnier pixel loss, no GAN).

  • Params: ~8.78M ( nf=64, nb=12 RRDB blocks, gc=32 )
  • Input: noisy low-light RGB, any size
  • Output: 4x, denoised RGB
  • Training: 9000 iters, Charbonnier loss, AdamW + cosine LR (2e-4 to 1e-6), EMA (decay 0.999), 64px random crops, batch 16, AMP. Global residual over a bilinear 4x upsample.

Validation (competition val split, 267 images)

metric value
RGB PSNR 39.10
Competition metric β€” grayscale, flatten()[::8] β€” PSNR 39.24
best val-subset metric during training 39.073
bicubic baseline (same grayscale metric) 33.54

Files

  • best.pt β€” dict with keys model, ema, optimizer, scaler, cfg, step, best
  • config.json β€” the training config

Usage

import torch, torch.nn as nn, torch.nn.functional as F

class ResidualDenseBlock(nn.Module):
    def __init__(self, nf=64, gc=32):
        super().__init__()
        self.conv1 = nn.Conv2d(nf, gc, 3, 1, 1)
        self.conv2 = nn.Conv2d(nf + gc, gc, 3, 1, 1)
        self.conv3 = nn.Conv2d(nf + 2*gc, gc, 3, 1, 1)
        self.conv4 = nn.Conv2d(nf + 3*gc, gc, 3, 1, 1)
        self.conv5 = nn.Conv2d(nf + 4*gc, nf, 3, 1, 1)
        self.lrelu = nn.LeakyReLU(0.2, inplace=True)
    def forward(self, x):
        x1 = self.lrelu(self.conv1(x))
        x2 = self.lrelu(self.conv2(torch.cat((x, x1), 1)))
        x3 = self.lrelu(self.conv3(torch.cat((x, x1, x2), 1)))
        x4 = self.lrelu(self.conv4(torch.cat((x, x1, x2, x3), 1)))
        x5 = self.conv5(torch.cat((x, x1, x2, x3, x4), 1))
        return x5 * 0.2 + x

class RRDB(nn.Module):
    def __init__(self, nf=64, gc=32):
        super().__init__()
        self.b1, self.b2, self.b3 = (ResidualDenseBlock(nf, gc) for _ in range(3))
    def forward(self, x):
        return self.b3(self.b2(self.b1(x))) * 0.2 + x

class RRDBNet(nn.Module):
    def __init__(self, in_ch=3, out_ch=3, nf=64, nb=12, gc=32):
        super().__init__()
        self.conv_first = nn.Conv2d(in_ch, nf, 3, 1, 1)
        self.body = nn.ModuleList([RRDB(nf, gc) for _ in range(nb)])
        self.conv_body = nn.Conv2d(nf, nf, 3, 1, 1)
        self.conv_up1 = nn.Conv2d(nf, nf, 3, 1, 1)
        self.conv_up2 = nn.Conv2d(nf, nf, 3, 1, 1)
        self.conv_hr  = nn.Conv2d(nf, nf, 3, 1, 1)
        self.conv_last = nn.Conv2d(nf, out_ch, 3, 1, 1)
        self.lrelu = nn.LeakyReLU(0.2, inplace=True)
    def forward(self, x):
        base = F.interpolate(x, scale_factor=4, mode="bilinear", align_corners=False)
        feat = self.conv_first(x); body = feat
        for blk in self.body:
            body = blk(body)
        feat = feat + self.conv_body(body)
        feat = self.lrelu(self.conv_up1(F.interpolate(feat, scale_factor=2, mode="nearest")))
        feat = self.lrelu(self.conv_up2(F.interpolate(feat, scale_factor=2, mode="nearest")))
        return self.conv_last(self.lrelu(self.conv_hr(feat))) + base

ck = torch.load("best.pt", map_location="cpu")
model = RRDBNet(nf=ck["cfg"]["nf"], nb=ck["cfg"]["nb"], gc=ck["cfg"]["gc"])
model.load_state_dict(ck["model"])          # or apply ck["ema"] for the EMA weights
model.eval()
Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support