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 keysmodel,ema,optimizer,scaler,cfg,step,bestconfig.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
- -