Spaces:
Runtime error
Runtime error
Added Multiple things
Browse files- app.py +194 -0
- models/__pycache__/autoencoder.cpython-312.pyc +0 -0
- models/__pycache__/fourier_interpolation.cpython-312.pyc +0 -0
- models/__pycache__/lancros.cpython-312.pyc +0 -0
- models/__pycache__/lancros_interpolation.cpython-312.pyc +0 -0
- models/__pycache__/sr_gan.cpython-312.pyc +0 -0
- models/autoencoder.py +65 -0
- models/cnn.py +82 -0
- models/edge_directed_interpolation.py +49 -0
- models/espcn.py +41 -0
- models/fourier_interpolation.py +41 -0
- models/lancros_interpolation.py +76 -0
- models/nn_interpolation.py +40 -0
- models/random_forest_sr.py +79 -0
- models/random_forest_sr1.py +68 -0
- models/rcan.py +111 -0
- models/spline_interpolation.py +33 -0
- models/sr_gan.py +87 -0
- requirements.txt +9 -0
- sample_images/0001.png +0 -0
- sample_images/0002.png +0 -0
- sample_images/0003.png +0 -0
- sample_images/0004.png +0 -0
- sample_images/0006.png +0 -0
- sample_images/0007.png +0 -0
- sample_images/0010.png +0 -0
- sample_images/0012.png +0 -0
- sample_images/0019.png +0 -0
- sample_images/0021.png +0 -0
- sample_images/0024.png +0 -0
- sample_images/0055.png +0 -0
- sample_images/0064.png +0 -0
- sample_images/0068.png +0 -0
- sample_images/0086.png +0 -0
- sample_images/0097.png +0 -0
- sample_images/0171.png +0 -0
- sample_images/0172.png +0 -0
- weights/model_auto_2.pth +3 -0
- weights/model_cnn.pth +3 -0
- weights/model_espcn.pth +3 -0
- weights/model_srgan.pth +3 -0
- weights/rcan_epoch_20.pth +3 -0
app.py
ADDED
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from PIL import Image
|
3 |
+
import os
|
4 |
+
import numpy as np
|
5 |
+
import cv2
|
6 |
+
|
7 |
+
# Existing imports
|
8 |
+
from models.lancros_interpolation import upsample_lancros
|
9 |
+
from models.fourier_interpolation import fourier_upscale
|
10 |
+
from models.autoencoder import autoencoder_upscale
|
11 |
+
from models.espcn import espcn_upscale
|
12 |
+
from models.sr_gan import srgan_upscale
|
13 |
+
from models.cnn import srcnn_upscale
|
14 |
+
|
15 |
+
# ✅ New import for Random Forest Super Resolution
|
16 |
+
from models.random_forest_sr import random_forest_upscale
|
17 |
+
|
18 |
+
# ✅ EDI (Edge Directed Interpolation) method
|
19 |
+
from PIL import Image
|
20 |
+
import numpy as np
|
21 |
+
|
22 |
+
from PIL import Image
|
23 |
+
import numpy as np
|
24 |
+
from models.rcan import rcan_upscale
|
25 |
+
|
26 |
+
def edge_directed_interpolation(lr_img_pil, scale=2):
|
27 |
+
# Ensure input is a PIL Image
|
28 |
+
|
29 |
+
if isinstance(lr_img_pil, np.ndarray):
|
30 |
+
lr_img_pil = Image.fromarray(lr_img_pil)
|
31 |
+
|
32 |
+
# Convert to RGB
|
33 |
+
lr_img_rgb = lr_img_pil.convert("RGB")
|
34 |
+
lr_img = np.array(lr_img_rgb)
|
35 |
+
|
36 |
+
h, w, c = lr_img.shape
|
37 |
+
hr_h, hr_w = h * scale, w * scale
|
38 |
+
hr_img = np.zeros((hr_h, hr_w, c), dtype=np.uint8)
|
39 |
+
|
40 |
+
# Copy original pixels to even positions
|
41 |
+
for i in range(h):
|
42 |
+
for j in range(w):
|
43 |
+
hr_img[i * scale, j * scale] = lr_img[i, j]
|
44 |
+
|
45 |
+
# Interpolate diagonal pixels per channel
|
46 |
+
for i in range(0, hr_h, scale):
|
47 |
+
for j in range(0, hr_w, scale):
|
48 |
+
if i + scale < hr_h and j + scale < hr_w:
|
49 |
+
for ch in range(c):
|
50 |
+
p1 = hr_img[i, j, ch]
|
51 |
+
p2 = hr_img[i, j + scale, ch]
|
52 |
+
p3 = hr_img[i + scale, j, ch]
|
53 |
+
p4 = hr_img[i + scale, j + scale, ch]
|
54 |
+
|
55 |
+
d1 = abs(int(p1) - int(p4))
|
56 |
+
d2 = abs(int(p2) - int(p3))
|
57 |
+
|
58 |
+
interp = (int(p1) + int(p4)) // 2 if d1 < d2 else (int(p2) + int(p3)) // 2
|
59 |
+
hr_img[i + scale // 2, j + scale // 2, ch] = interp
|
60 |
+
|
61 |
+
# Fill remaining zero pixels per channel
|
62 |
+
for i in range(hr_h):
|
63 |
+
for j in range(hr_w):
|
64 |
+
for ch in range(c):
|
65 |
+
if hr_img[i, j, ch] == 0:
|
66 |
+
neighbors = []
|
67 |
+
if i - 1 >= 0:
|
68 |
+
neighbors.append(hr_img[i - 1, j, ch])
|
69 |
+
if i + 1 < hr_h:
|
70 |
+
neighbors.append(hr_img[i + 1, j, ch])
|
71 |
+
if j - 1 >= 0:
|
72 |
+
neighbors.append(hr_img[i, j - 1, ch])
|
73 |
+
if j + 1 < hr_w:
|
74 |
+
neighbors.append(hr_img[i, j + 1, ch])
|
75 |
+
if neighbors:
|
76 |
+
hr_img[i, j, ch] = int(np.mean(neighbors))
|
77 |
+
|
78 |
+
return Image.fromarray(hr_img)
|
79 |
+
|
80 |
+
|
81 |
+
|
82 |
+
# === Interfaces === #
|
83 |
+
lancros_page = gr.Interface(
|
84 |
+
fn=upsample_lancros,
|
85 |
+
inputs=[gr.Image(label="Low Resolution Image"),
|
86 |
+
gr.Slider(2, 6, step=1, value=2, label="Upscaling Factor"),],
|
87 |
+
outputs=gr.Image(type="pil", label="High Resolution Images"),
|
88 |
+
title="Lancros Upsampling",
|
89 |
+
examples=[
|
90 |
+
["sample_images/0001.png"],
|
91 |
+
["sample_images/0172.png"]
|
92 |
+
]
|
93 |
+
)
|
94 |
+
|
95 |
+
fourier_page = gr.Interface(
|
96 |
+
fn=fourier_upscale,
|
97 |
+
inputs=[gr.Image(label="Low Resolution Image"),
|
98 |
+
gr.Slider(2, 6, step=1, value=2, label="Upscaling Factor"),],
|
99 |
+
outputs=gr.Image(type="pil", label="High Resolution Images"),
|
100 |
+
title="Fourier Upsampling",
|
101 |
+
examples=[
|
102 |
+
["sample_images/0004.png"],
|
103 |
+
["sample_images/0012.png"]
|
104 |
+
]
|
105 |
+
)
|
106 |
+
|
107 |
+
autoencoder_page = gr.Interface(
|
108 |
+
fn=autoencoder_upscale,
|
109 |
+
inputs=[gr.Image(label="Low Resolution Image")],
|
110 |
+
outputs=gr.Image(type="pil", label="High Resolution Images"),
|
111 |
+
title="Autoencoder based Super Resolution",
|
112 |
+
examples=[
|
113 |
+
["sample_images/0019.png"],
|
114 |
+
["sample_images/0064.png"]
|
115 |
+
]
|
116 |
+
)
|
117 |
+
|
118 |
+
espcn_page = gr.Interface(
|
119 |
+
fn=espcn_upscale,
|
120 |
+
inputs=[gr.Image(label="Low Resolution Image")],
|
121 |
+
outputs=gr.Image(type="pil", label="High Resolution Images"),
|
122 |
+
title="ESPCN based Super Resolution",
|
123 |
+
examples=[
|
124 |
+
["sample_images/0024.png"],
|
125 |
+
["sample_images/0068.png"]
|
126 |
+
]
|
127 |
+
)
|
128 |
+
|
129 |
+
srgan_page = gr.Interface(
|
130 |
+
fn=srgan_upscale,
|
131 |
+
inputs=[gr.Image(label="Low Resolution Image")],
|
132 |
+
outputs=gr.Image(type="pil", label="High Resolution Images"),
|
133 |
+
title="GAN based Super Resolution",
|
134 |
+
examples=[
|
135 |
+
["sample_images/0055.png"],
|
136 |
+
["sample_images/0003.png"]
|
137 |
+
]
|
138 |
+
)
|
139 |
+
|
140 |
+
random_forest_page = gr.Interface(
|
141 |
+
fn=random_forest_upscale,
|
142 |
+
inputs=[gr.Image(label="Low Resolution Image")],
|
143 |
+
outputs=gr.Image(type="pil", label="High Resolution Images"),
|
144 |
+
title="Random Forest based Super Resolution",
|
145 |
+
examples=[
|
146 |
+
["sample_images/0097.png"],
|
147 |
+
["sample_images/0086.png"]
|
148 |
+
]
|
149 |
+
)
|
150 |
+
|
151 |
+
# ✅ EDI Page
|
152 |
+
edi_page = gr.Interface(
|
153 |
+
fn=edge_directed_interpolation,
|
154 |
+
inputs=[gr.Image(label="Low Resolution Image"), gr.Slider(2, 4, step=1, value=2, label="Upscaling Factor")],
|
155 |
+
outputs=gr.Image(type="pil", label="High Resolution Image"),
|
156 |
+
title="Edge Directed Interpolation",
|
157 |
+
examples=[
|
158 |
+
["sample_images/0002.png"],
|
159 |
+
["sample_images/0006.png"]
|
160 |
+
]
|
161 |
+
)
|
162 |
+
|
163 |
+
rcan_page = gr.Interface(
|
164 |
+
fn=rcan_upscale,
|
165 |
+
inputs=[gr.Image(label="Low Resolution Image")],
|
166 |
+
outputs=gr.Image(type="pil", label="High Resolution Image"),
|
167 |
+
title="RCAN based Super Resolution",
|
168 |
+
examples=[
|
169 |
+
["sample_images/0007.png"],
|
170 |
+
["sample_images/0010.png"]
|
171 |
+
]
|
172 |
+
)
|
173 |
+
|
174 |
+
srcnn_page = gr.Interface(
|
175 |
+
fn=srcnn_upscale,
|
176 |
+
inputs=[gr.Image(label="Low Resolution Image")],
|
177 |
+
outputs=gr.Image(type="pil", label="High Resolution Image"),
|
178 |
+
title="SRCNN based Super Resolution",
|
179 |
+
examples=[
|
180 |
+
["sample_images/0007.png"],
|
181 |
+
["sample_images/0010.png"]
|
182 |
+
]
|
183 |
+
)
|
184 |
+
|
185 |
+
# Tabs setup
|
186 |
+
demo = gr.TabbedInterface(
|
187 |
+
[srgan_page, lancros_page, fourier_page, autoencoder_page, espcn_page, random_forest_page, edi_page, rcan_page,srcnn_page],
|
188 |
+
["GAN based Super Resolution", "Lancros Interpolation", "Fourier Interpolation", "Autoencoder based Super Resolution",
|
189 |
+
"EspCN Super Resolution", "Random Forest based Super Resolution", "Edge Directed Interpolation", "RCAN Super Resolution","SRCNN Super Resolution"],
|
190 |
+
title="Image Super Resolution"
|
191 |
+
)
|
192 |
+
|
193 |
+
if __name__ == "__main__":
|
194 |
+
demo.launch(debug=True)
|
models/__pycache__/autoencoder.cpython-312.pyc
ADDED
Binary file (3.73 kB). View file
|
|
models/__pycache__/fourier_interpolation.cpython-312.pyc
ADDED
Binary file (1.66 kB). View file
|
|
models/__pycache__/lancros.cpython-312.pyc
ADDED
Binary file (3.28 kB). View file
|
|
models/__pycache__/lancros_interpolation.cpython-312.pyc
ADDED
Binary file (3.29 kB). View file
|
|
models/__pycache__/sr_gan.cpython-312.pyc
ADDED
Binary file (4.92 kB). View file
|
|
models/autoencoder.py
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import torch.nn as nn
|
3 |
+
from collections import OrderedDict
|
4 |
+
from PIL import Image,ImageOps
|
5 |
+
import numpy as np
|
6 |
+
|
7 |
+
class Autoencoder(nn.Module):
|
8 |
+
def __init__(self):
|
9 |
+
super().__init__()
|
10 |
+
|
11 |
+
# ENCODER — compress from 128 -> 64 -> 32
|
12 |
+
self.enc1 = nn.Conv2d(3, 64, 3, stride=2, padding=1) # 128 → 64
|
13 |
+
self.enc2 = nn.Conv2d(64, 128, 3, stride=2, padding=1) # 64 → 32
|
14 |
+
|
15 |
+
# DECODER — upsample from 32 → 64 → 128 → 256 → 512
|
16 |
+
self.dec1 = nn.ConvTranspose2d(128, 64, 3, stride=2, padding=1, output_padding=1) # 32 → 64
|
17 |
+
self.dec2 = nn.ConvTranspose2d(64, 32, 3, stride=2, padding=1, output_padding=1) # 64 → 128
|
18 |
+
self.dec3 = nn.ConvTranspose2d(32, 16, 3, stride=2, padding=1, output_padding=1) # 128 → 256
|
19 |
+
self.dec4 = nn.ConvTranspose2d(16, 3, 3, stride=2, padding=1, output_padding=1) # 256 → 512
|
20 |
+
|
21 |
+
# Activations
|
22 |
+
self.relu = nn.ReLU()
|
23 |
+
self.sigmoid = nn.Sigmoid()
|
24 |
+
|
25 |
+
def forward(self, x):
|
26 |
+
# Encoder
|
27 |
+
x = self.relu(self.enc1(x)) # [B, 64, 64, 64]
|
28 |
+
x = self.relu(self.enc2(x)) # [B, 128, 32, 32]
|
29 |
+
|
30 |
+
# Decoder
|
31 |
+
x = self.relu(self.dec1(x)) # [B, 64, 64, 64]
|
32 |
+
x = self.relu(self.dec2(x)) # [B, 32, 128, 128]
|
33 |
+
x = self.relu(self.dec3(x)) # [B, 16, 256, 256]
|
34 |
+
x = self.sigmoid(self.dec4(x)) # [B, 3, 512, 512]
|
35 |
+
|
36 |
+
return x
|
37 |
+
|
38 |
+
def autoencoder_upscale(image):
|
39 |
+
image = Image.fromarray(image)
|
40 |
+
target_size = (128, 128)
|
41 |
+
pad_color=(0, 0, 0)
|
42 |
+
ImageOps.pad(image, target_size, method=Image.BICUBIC, color=pad_color)
|
43 |
+
image = np.array(image)
|
44 |
+
|
45 |
+
image = image/255
|
46 |
+
image = torch.from_numpy(image).float().unsqueeze(dim=0).permute(0,3,1,2)
|
47 |
+
|
48 |
+
model = Autoencoder()
|
49 |
+
checkpoint = torch.load("weights/model_auto_2.pth", map_location=torch.device('cpu')) # or 'cuda' if using GPU
|
50 |
+
state_dict = checkpoint['state_dict']
|
51 |
+
|
52 |
+
new_state_dict = OrderedDict()
|
53 |
+
for k, v in state_dict.items():
|
54 |
+
new_key = k.replace("module.", "") # Remove "module." from each key
|
55 |
+
new_state_dict[new_key] = v
|
56 |
+
|
57 |
+
model.load_state_dict(new_state_dict)
|
58 |
+
model.eval()
|
59 |
+
|
60 |
+
with torch.no_grad():
|
61 |
+
output = model(image)
|
62 |
+
|
63 |
+
output = output.squeeze(0).permute(1, 2, 0).cpu().numpy()
|
64 |
+
output = (output * 255.0).clip(0, 255).astype("uint8")
|
65 |
+
return output
|
models/cnn.py
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import torch.nn as nn
|
3 |
+
from collections import OrderedDict
|
4 |
+
|
5 |
+
# SRCNN Model Definition
|
6 |
+
class SuperResolutionCNN(nn.Module):
|
7 |
+
def __init__(self):
|
8 |
+
super(SuperResolutionCNN, self).__init__()
|
9 |
+
|
10 |
+
# Feature extraction
|
11 |
+
self.features = nn.Sequential(
|
12 |
+
nn.Conv2d(3, 64, kernel_size=9, padding=4),
|
13 |
+
nn.ReLU(),
|
14 |
+
nn.Conv2d(64, 32, kernel_size=1, padding=0),
|
15 |
+
nn.ReLU()
|
16 |
+
)
|
17 |
+
|
18 |
+
# Upsampling blocks
|
19 |
+
self.upsample = nn.Sequential(
|
20 |
+
nn.ConvTranspose2d(32, 32, kernel_size=3, stride=2, padding=1, output_padding=1),
|
21 |
+
nn.ReLU(),
|
22 |
+
nn.ConvTranspose2d(32, 32, kernel_size=3, stride=2, padding=1, output_padding=1),
|
23 |
+
nn.ReLU()
|
24 |
+
)
|
25 |
+
|
26 |
+
# Reconstruction
|
27 |
+
self.reconstruction = nn.Conv2d(32, 3, kernel_size=5, padding=2)
|
28 |
+
|
29 |
+
def forward(self, x):
|
30 |
+
x = self.features(x)
|
31 |
+
x = self.upsample(x)
|
32 |
+
x = self.reconstruction(x)
|
33 |
+
return torch.sigmoid(x)
|
34 |
+
|
35 |
+
|
36 |
+
def srcnn_upscale(image, model_path="weights/model_cnn.pth", device='cpu'):
|
37 |
+
"""
|
38 |
+
Upscale image using SRCNN model
|
39 |
+
Input: numpy array (H,W,3) in range 0-255
|
40 |
+
Output: numpy array (H,W,3) in range 0-255
|
41 |
+
|
42 |
+
Args:
|
43 |
+
image: Input image as numpy array (H,W,3) in range 0-255
|
44 |
+
model_path: Path to the trained SRCNN model weights
|
45 |
+
device: 'cpu' or 'cuda' for GPU acceleration
|
46 |
+
"""
|
47 |
+
# Normalize and convert to tensor [1,3,H,W]
|
48 |
+
image = image / 255.0
|
49 |
+
image = torch.from_numpy(image).float().unsqueeze(0).permute(0, 3, 1, 2).to(device)
|
50 |
+
|
51 |
+
# Load model
|
52 |
+
model = SuperResolutionCNN().to(device)
|
53 |
+
|
54 |
+
# Load weights
|
55 |
+
checkpoint = torch.load(model_path, map_location=torch.device(device))
|
56 |
+
|
57 |
+
# Handle different checkpoint formats
|
58 |
+
if 'state_dict' in checkpoint:
|
59 |
+
state_dict = checkpoint['state_dict']
|
60 |
+
elif 'model_state_dict' in checkpoint:
|
61 |
+
state_dict = checkpoint['model_state_dict']
|
62 |
+
else:
|
63 |
+
state_dict = checkpoint # Assume direct state dict
|
64 |
+
|
65 |
+
# Remove "module." prefix if present (for DataParallel models)
|
66 |
+
new_state_dict = OrderedDict()
|
67 |
+
for k, v in state_dict.items():
|
68 |
+
new_key = k.replace("module.", "")
|
69 |
+
new_state_dict[new_key] = v
|
70 |
+
|
71 |
+
model.load_state_dict(new_state_dict)
|
72 |
+
model.eval()
|
73 |
+
|
74 |
+
# Process image
|
75 |
+
with torch.no_grad():
|
76 |
+
output = model(image)
|
77 |
+
|
78 |
+
# Convert back to numpy array [H,W,3] 0-255
|
79 |
+
output = output.squeeze(0).permute(1, 2, 0).cpu().numpy()
|
80 |
+
output = (output * 255.0).clip(0, 255).astype("uint8")
|
81 |
+
|
82 |
+
return output
|
models/edge_directed_interpolation.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import cv2
|
3 |
+
|
4 |
+
def edge_directed_interpolation(lr_img_pil, scale=2):
|
5 |
+
lr_img = np.array(lr_img_pil.convert("L"))
|
6 |
+
h, w = lr_img.shape
|
7 |
+
hr_h, hr_w = h * scale, w * scale
|
8 |
+
|
9 |
+
hr_img = np.zeros((hr_h, hr_w), dtype=np.uint8)
|
10 |
+
|
11 |
+
for i in range(h):
|
12 |
+
for j in range(w):
|
13 |
+
hr_img[i * scale, j * scale] = lr_img[i, j]
|
14 |
+
|
15 |
+
for i in range(0, hr_h, scale):
|
16 |
+
for j in range(0, hr_w, scale):
|
17 |
+
if i + scale < hr_h and j + scale < hr_w:
|
18 |
+
p1 = hr_img[i, j]
|
19 |
+
p2 = hr_img[i, j + scale]
|
20 |
+
p3 = hr_img[i + scale, j]
|
21 |
+
p4 = hr_img[i + scale, j + scale]
|
22 |
+
|
23 |
+
d1 = abs(int(p1) - int(p4))
|
24 |
+
d2 = abs(int(p2) - int(p3))
|
25 |
+
|
26 |
+
if d1 < d2:
|
27 |
+
interp = (int(p1) + int(p4)) // 2
|
28 |
+
else:
|
29 |
+
interp = (int(p2) + int(p3)) // 2
|
30 |
+
|
31 |
+
hr_img[i + scale // 2, j + scale // 2] = interp
|
32 |
+
|
33 |
+
for i in range(hr_h):
|
34 |
+
for j in range(hr_w):
|
35 |
+
if hr_img[i, j] == 0:
|
36 |
+
neighbors = []
|
37 |
+
if i - 1 >= 0:
|
38 |
+
neighbors.append(hr_img[i - 1, j])
|
39 |
+
if i + 1 < hr_h:
|
40 |
+
neighbors.append(hr_img[i + 1, j])
|
41 |
+
if j - 1 >= 0:
|
42 |
+
neighbors.append(hr_img[i, j - 1])
|
43 |
+
if j + 1 < hr_w:
|
44 |
+
neighbors.append(hr_img[i, j + 1])
|
45 |
+
if neighbors:
|
46 |
+
hr_img[i, j] = np.mean(neighbors).astype(np.uint8)
|
47 |
+
|
48 |
+
hr_img_pil = Image.fromarray(hr_img)
|
49 |
+
return hr_img_pil
|
models/espcn.py
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import torch.nn as nn
|
3 |
+
import torch.nn.functional as F
|
4 |
+
from collections import OrderedDict
|
5 |
+
|
6 |
+
class ESPCN(nn.Module):
|
7 |
+
def __init__(self):
|
8 |
+
super(ESPCN, self).__init__()
|
9 |
+
self.conv1 = nn.Conv2d(3, 64, kernel_size=5, padding=2)
|
10 |
+
self.conv2 = nn.Conv2d(64, 32, kernel_size=3, padding=1)
|
11 |
+
self.conv3 = nn.Conv2d(32, (4 ** 2) * 3, kernel_size=3, padding=1) # Handle 3-channel output
|
12 |
+
self.pixel_shuffle = nn.PixelShuffle(4)
|
13 |
+
|
14 |
+
def forward(self, x):
|
15 |
+
x = F.relu(self.conv1(x))
|
16 |
+
x = F.relu(self.conv2(x))
|
17 |
+
x = self.pixel_shuffle(self.conv3(x))
|
18 |
+
return x
|
19 |
+
|
20 |
+
def espcn_upscale(image):
|
21 |
+
image = image/255
|
22 |
+
image = torch.from_numpy(image).float().unsqueeze(dim=0).permute(0,3,1,2)
|
23 |
+
|
24 |
+
model = ESPCN()
|
25 |
+
checkpoint = torch.load("weights/model_espcn.pth", map_location=torch.device('cpu')) # or 'cuda' if using GPU
|
26 |
+
state_dict = checkpoint['state_dict']
|
27 |
+
|
28 |
+
new_state_dict = OrderedDict()
|
29 |
+
for k, v in state_dict.items():
|
30 |
+
new_key = k.replace("module.", "") # Remove "module." from each key
|
31 |
+
new_state_dict[new_key] = v
|
32 |
+
|
33 |
+
model.load_state_dict(new_state_dict)
|
34 |
+
model.eval()
|
35 |
+
|
36 |
+
with torch.no_grad():
|
37 |
+
output = model(image)
|
38 |
+
|
39 |
+
output = output.squeeze(0).permute(1, 2, 0).cpu().numpy()
|
40 |
+
output = (output * 255.0).clip(0, 255).astype("uint8")
|
41 |
+
return output
|
models/fourier_interpolation.py
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
|
4 |
+
from scipy.fft import fft2, ifft2, fftshift, ifftshift
|
5 |
+
from PIL import Image
|
6 |
+
|
7 |
+
def fourier_upscale(image, scale_factor=4):
|
8 |
+
upscaled_channels = []
|
9 |
+
|
10 |
+
for c in range(3): # For R, G, B channels
|
11 |
+
# Get single channel
|
12 |
+
channel = image[:, :, c]
|
13 |
+
|
14 |
+
# Forward FFT
|
15 |
+
F = fft2(channel)
|
16 |
+
F_shifted = fftshift(F)
|
17 |
+
|
18 |
+
# Zero-padding in frequency domain
|
19 |
+
h, w = channel.shape
|
20 |
+
new_h, new_w = h * scale_factor, w * scale_factor
|
21 |
+
F_padded = np.zeros((new_h, new_w), dtype=complex)
|
22 |
+
|
23 |
+
# Place the original spectrum in the center of the padded one
|
24 |
+
h_center, w_center = new_h // 2, new_w // 2
|
25 |
+
h_half, w_half = h // 2, w // 2
|
26 |
+
F_padded[h_center - h_half:h_center + h_half, w_center - w_half:w_center + w_half] = F_shifted
|
27 |
+
|
28 |
+
# Inverse FFT
|
29 |
+
F_unshifted = ifftshift(F_padded)
|
30 |
+
upscaled = np.real(ifft2(F_unshifted))
|
31 |
+
|
32 |
+
# ✅ Rescale intensities
|
33 |
+
upscaled *= scale_factor ** 2 # Rescale to preserve brightness
|
34 |
+
|
35 |
+
# Normalize to [0, 255]
|
36 |
+
upscaled = np.clip(upscaled, 0, 255)
|
37 |
+
upscaled_channels.append(upscaled.astype(np.uint8))
|
38 |
+
|
39 |
+
# Stack the 3 upscaled channels
|
40 |
+
upscaled_img = np.stack(upscaled_channels, axis=2)
|
41 |
+
return upscaled_img
|
models/lancros_interpolation.py
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
|
4 |
+
def upsample_lancros(image_x,scale=4):
|
5 |
+
h, w = image_x.shape[:2]
|
6 |
+
new_w, new_h = int(w * scale), int(h * scale)
|
7 |
+
|
8 |
+
# Upsample using Lanczos interpolation
|
9 |
+
pred = cv2.resize(image_x, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
10 |
+
return pred
|
11 |
+
|
12 |
+
# def lanczos_kernel(x, a):
|
13 |
+
# """Lanczos kernel function."""
|
14 |
+
# if x == 0:
|
15 |
+
# return 1
|
16 |
+
# elif -a < x < a:
|
17 |
+
# return a * np.sinc(x) * np.sinc(x / a)
|
18 |
+
# else:
|
19 |
+
# return 0
|
20 |
+
|
21 |
+
# def upsample_lancros_2(image, scale=4, a=3):
|
22 |
+
# """
|
23 |
+
# Upsample an image using Lanczos interpolation.
|
24 |
+
|
25 |
+
# Parameters:
|
26 |
+
# image: numpy array (grayscale or RGB)
|
27 |
+
# scale: scaling factor (default 4x)
|
28 |
+
# a: size of Lanczos window (default 3)
|
29 |
+
|
30 |
+
# Returns:
|
31 |
+
# Upsampled image as numpy array.
|
32 |
+
# """
|
33 |
+
# if image.ndim == 2: # Grayscale
|
34 |
+
# h, w = image.shape
|
35 |
+
# channels = 1
|
36 |
+
# else: # RGB
|
37 |
+
# h, w, channels = image.shape
|
38 |
+
|
39 |
+
# new_h, new_w = int(h * scale), int(w * scale)
|
40 |
+
# output = np.zeros((new_h, new_w, channels)) if channels > 1 else np.zeros((new_h, new_w))
|
41 |
+
|
42 |
+
# for y_new in range(new_h):
|
43 |
+
# for x_new in range(new_w):
|
44 |
+
# print(y_new,x_new)
|
45 |
+
# # Map new pixel to original image space
|
46 |
+
# x_orig = x_new / scale
|
47 |
+
# y_orig = y_new / scale
|
48 |
+
|
49 |
+
# x0 = int(np.floor(x_orig))
|
50 |
+
# y0 = int(np.floor(y_orig))
|
51 |
+
|
52 |
+
# # Accumulators
|
53 |
+
# pixel = np.zeros(channels) if channels > 1 else 0.0
|
54 |
+
# norm = 0.0
|
55 |
+
|
56 |
+
# for j in range(y0 - a + 1, y0 + a + 1):
|
57 |
+
# for i in range(x0 - a + 1, x0 + a + 1):
|
58 |
+
# if 0 <= i < w and 0 <= j < h:
|
59 |
+
# wx = lanczos_kernel(x_orig - i, a)
|
60 |
+
# wy = lanczos_kernel(y_orig - j, a)
|
61 |
+
# weight = wx * wy
|
62 |
+
# if channels > 1:
|
63 |
+
# pixel += image[j, i] * weight
|
64 |
+
# else:
|
65 |
+
# pixel += image[j, i] * weight
|
66 |
+
# norm += weight
|
67 |
+
|
68 |
+
# if norm > 0:
|
69 |
+
# output[y_new, x_new] = pixel / norm
|
70 |
+
|
71 |
+
# if channels == 1:
|
72 |
+
# output = output.astype(image.dtype)
|
73 |
+
# else:
|
74 |
+
# output = output.clip(0, 255).astype(image.dtype)
|
75 |
+
|
76 |
+
# return output
|
models/nn_interpolation.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
|
4 |
+
def nn_upscale(image, scale_factor=4):
|
5 |
+
"""
|
6 |
+
Upscale image using Nearest Neighbor interpolation
|
7 |
+
|
8 |
+
Args:
|
9 |
+
image: Input image (numpy array or file path)
|
10 |
+
scale_factor: Scaling multiplier (default=4)
|
11 |
+
|
12 |
+
Returns:
|
13 |
+
Upscaled image as numpy array (uint8)
|
14 |
+
"""
|
15 |
+
# Load image if path provided
|
16 |
+
if isinstance(image, str):
|
17 |
+
img = cv2.imread(image)
|
18 |
+
if img is None:
|
19 |
+
raise ValueError(f"Could not load image from {image}")
|
20 |
+
else:
|
21 |
+
img = image.copy()
|
22 |
+
|
23 |
+
# Get original dimensions
|
24 |
+
h, w = img.shape[:2]
|
25 |
+
new_h, new_w = h * scale_factor, w * scale_factor
|
26 |
+
|
27 |
+
# Create empty output image
|
28 |
+
if len(img.shape) == 3: # Color image
|
29 |
+
upscaled = np.zeros((new_h, new_w, 3), dtype=img.dtype)
|
30 |
+
else: # Grayscale
|
31 |
+
upscaled = np.zeros((new_h, new_w), dtype=img.dtype)
|
32 |
+
|
33 |
+
# Nearest Neighbor interpolation
|
34 |
+
for y in range(new_h):
|
35 |
+
for x in range(new_w):
|
36 |
+
orig_y = min(int(y / scale_factor), h - 1)
|
37 |
+
orig_x = min(int(x / scale_factor), w - 1)
|
38 |
+
upscaled[y, x] = img[orig_y, orig_x]
|
39 |
+
|
40 |
+
return upscaled
|
models/random_forest_sr.py
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
from skimage import transform, util, color
|
3 |
+
from sklearn.ensemble import RandomForestRegressor
|
4 |
+
from skimage.util import view_as_windows
|
5 |
+
from PIL import Image
|
6 |
+
|
7 |
+
# CONFIGURATION
|
8 |
+
PATCH_SIZE = (3, 3)
|
9 |
+
STEP = 1
|
10 |
+
N_ESTIMATORS = 10
|
11 |
+
MAX_DEPTH = 10
|
12 |
+
SCALE_FACTOR = 2
|
13 |
+
SAMPLE_PATCHES = 10000 # Controls speed/accuracy trade-off
|
14 |
+
|
15 |
+
def extract_patches(img, patch_size, step):
|
16 |
+
patches = view_as_windows(img, patch_size, step)
|
17 |
+
h, w = patches.shape[:2]
|
18 |
+
return patches.reshape(h * w, -1)
|
19 |
+
|
20 |
+
def train_rf(X, y):
|
21 |
+
rf = RandomForestRegressor(
|
22 |
+
n_estimators=N_ESTIMATORS,
|
23 |
+
max_depth=MAX_DEPTH,
|
24 |
+
n_jobs=-1
|
25 |
+
)
|
26 |
+
rf.fit(X, y)
|
27 |
+
return rf
|
28 |
+
|
29 |
+
def predict_and_reconstruct(model, lr_img, patch_size, step, out_shape):
|
30 |
+
lr_patches = extract_patches(lr_img, patch_size, step)
|
31 |
+
preds = model.predict(lr_patches)
|
32 |
+
|
33 |
+
patch_h, patch_w = patch_size
|
34 |
+
img_h = (lr_img.shape[0] - patch_h) // step + 1
|
35 |
+
img_w = (lr_img.shape[1] - patch_w) // step + 1
|
36 |
+
|
37 |
+
result = np.zeros(out_shape)
|
38 |
+
weight = np.zeros(out_shape)
|
39 |
+
|
40 |
+
idx = 0
|
41 |
+
for i in range(img_h):
|
42 |
+
for j in range(img_w):
|
43 |
+
patch = preds[idx].reshape(patch_h, patch_w)
|
44 |
+
result[i*step:i*step+patch_h, j*step:j*step+patch_w] += patch
|
45 |
+
weight[i*step:i*step+patch_h, j*step:j*step+patch_w] += 1
|
46 |
+
idx += 1
|
47 |
+
|
48 |
+
weight[weight == 0] = 1
|
49 |
+
return result / weight
|
50 |
+
|
51 |
+
def random_forest_upscale(pil_img: Image.Image) -> Image.Image:
|
52 |
+
img = np.array(pil_img) / 255.0 # Normalize
|
53 |
+
if img.ndim == 2:
|
54 |
+
img = np.expand_dims(img, axis=-1)
|
55 |
+
|
56 |
+
hr_shape = (img.shape[0] * SCALE_FACTOR, img.shape[1] * SCALE_FACTOR)
|
57 |
+
sr_channels = []
|
58 |
+
|
59 |
+
for c in range(img.shape[2]):
|
60 |
+
channel = img[:, :, c]
|
61 |
+
hr_channel = transform.resize(channel, hr_shape, anti_aliasing=True)
|
62 |
+
lr_channel = transform.resize(hr_channel, (hr_shape[0] // SCALE_FACTOR, hr_shape[1] // SCALE_FACTOR), anti_aliasing=True)
|
63 |
+
lr_channel_up = transform.resize(lr_channel, hr_shape, anti_aliasing=True)
|
64 |
+
|
65 |
+
X = extract_patches(lr_channel_up, PATCH_SIZE, STEP)
|
66 |
+
y = extract_patches(hr_channel, PATCH_SIZE, STEP)
|
67 |
+
|
68 |
+
if X.shape[0] > SAMPLE_PATCHES:
|
69 |
+
idx = np.random.choice(X.shape[0], SAMPLE_PATCHES, replace=False)
|
70 |
+
X = X[idx]
|
71 |
+
y = y[idx]
|
72 |
+
|
73 |
+
rf_model = train_rf(X, y)
|
74 |
+
sr = predict_and_reconstruct(rf_model, lr_channel_up, PATCH_SIZE, STEP, hr_shape)
|
75 |
+
sr_channels.append(sr)
|
76 |
+
|
77 |
+
sr_image = np.stack(sr_channels, axis=-1)
|
78 |
+
sr_image = np.clip(sr_image * 255, 0, 255).astype(np.uint8)
|
79 |
+
return Image.fromarray(sr_image)
|
models/random_forest_sr1.py
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
from skimage import transform, util
|
3 |
+
from sklearn.ensemble import RandomForestRegressor
|
4 |
+
from skimage.util import view_as_windows
|
5 |
+
from PIL import Image
|
6 |
+
|
7 |
+
# CONFIGURATION
|
8 |
+
PATCH_SIZE = (5, 5)
|
9 |
+
STEP = 1
|
10 |
+
N_ESTIMATORS = 50
|
11 |
+
MAX_DEPTH = 20
|
12 |
+
SCALE_FACTOR = 2
|
13 |
+
|
14 |
+
def extract_patches(img, patch_size, step):
|
15 |
+
patches = view_as_windows(img, patch_size, step)
|
16 |
+
h, w = patches.shape[:2]
|
17 |
+
return patches.reshape(h * w, -1)
|
18 |
+
|
19 |
+
def train_rf(X, y):
|
20 |
+
rf = RandomForestRegressor(n_estimators=N_ESTIMATORS, max_depth=MAX_DEPTH, n_jobs=-1)
|
21 |
+
rf.fit(X, y)
|
22 |
+
return rf
|
23 |
+
|
24 |
+
def predict_and_reconstruct(model, lr_img, patch_size, step, out_shape):
|
25 |
+
lr_patches = extract_patches(lr_img, patch_size, step)
|
26 |
+
preds = model.predict(lr_patches)
|
27 |
+
|
28 |
+
patch_h, patch_w = patch_size
|
29 |
+
img_h = (lr_img.shape[0] - patch_h) // step + 1
|
30 |
+
img_w = (lr_img.shape[1] - patch_w) // step + 1
|
31 |
+
|
32 |
+
result = np.zeros(out_shape)
|
33 |
+
weight = np.zeros(out_shape)
|
34 |
+
|
35 |
+
idx = 0
|
36 |
+
for i in range(img_h):
|
37 |
+
for j in range(img_w):
|
38 |
+
patch = preds[idx].reshape(patch_h, patch_w)
|
39 |
+
result[i*step:i*step+patch_h, j*step:j*step+patch_w] += patch
|
40 |
+
weight[i*step:i*step+patch_h, j*step:j*step+patch_w] += 1
|
41 |
+
idx += 1
|
42 |
+
|
43 |
+
weight[weight == 0] = 1
|
44 |
+
return result / weight
|
45 |
+
|
46 |
+
def random_forest_upscale(pil_img: Image.Image) -> Image.Image:
|
47 |
+
img = np.array(pil_img) / 255.0 # Normalize
|
48 |
+
if img.ndim == 2:
|
49 |
+
img = np.expand_dims(img, axis=-1)
|
50 |
+
|
51 |
+
hr_shape = (img.shape[0] * SCALE_FACTOR, img.shape[1] * SCALE_FACTOR)
|
52 |
+
sr_channels = []
|
53 |
+
|
54 |
+
for c in range(img.shape[2]):
|
55 |
+
channel = img[:, :, c]
|
56 |
+
hr_channel = transform.resize(channel, hr_shape)
|
57 |
+
lr_channel = transform.resize(hr_channel, (hr_shape[0] // SCALE_FACTOR, hr_shape[1] // SCALE_FACTOR))
|
58 |
+
lr_channel_up = transform.resize(lr_channel, hr_shape)
|
59 |
+
|
60 |
+
X = extract_patches(lr_channel_up, PATCH_SIZE, STEP)
|
61 |
+
y = extract_patches(hr_channel, PATCH_SIZE, STEP)
|
62 |
+
rf_model = train_rf(X, y)
|
63 |
+
sr = predict_and_reconstruct(rf_model, lr_channel_up, PATCH_SIZE, STEP, hr_shape)
|
64 |
+
sr_channels.append(sr)
|
65 |
+
|
66 |
+
sr_image = np.stack(sr_channels, axis=-1)
|
67 |
+
sr_image = np.clip(sr_image * 255, 0, 255).astype(np.uint8)
|
68 |
+
return Image.fromarray(sr_image)
|
models/rcan.py
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import torch.nn as nn
|
3 |
+
import torch.nn.functional as F
|
4 |
+
from torchvision.io import read_image
|
5 |
+
from torchvision.utils import save_image
|
6 |
+
import os
|
7 |
+
from torchvision.transforms.functional import to_tensor, to_pil_image
|
8 |
+
|
9 |
+
# === RCAN MODULES ===
|
10 |
+
|
11 |
+
class CALayer(nn.Module):
|
12 |
+
def __init__(self, channels, reduction=16):
|
13 |
+
super().__init__()
|
14 |
+
self.avg_pool = nn.AdaptiveAvgPool2d(1)
|
15 |
+
self.fc = nn.Sequential(
|
16 |
+
nn.Conv2d(channels, channels // reduction, 1),
|
17 |
+
nn.ReLU(inplace=True),
|
18 |
+
nn.Conv2d(channels // reduction, channels, 1),
|
19 |
+
nn.Sigmoid()
|
20 |
+
)
|
21 |
+
|
22 |
+
def forward(self, x):
|
23 |
+
w = self.fc(self.avg_pool(x))
|
24 |
+
return x * w
|
25 |
+
|
26 |
+
|
27 |
+
class RCAB(nn.Module):
|
28 |
+
def __init__(self, channels):
|
29 |
+
super().__init__()
|
30 |
+
self.body = nn.Sequential(
|
31 |
+
nn.Conv2d(channels, channels, 3, padding=1),
|
32 |
+
nn.ReLU(inplace=True),
|
33 |
+
nn.Conv2d(channels, channels, 3, padding=1),
|
34 |
+
CALayer(channels)
|
35 |
+
)
|
36 |
+
|
37 |
+
def forward(self, x):
|
38 |
+
return x + self.body(x)
|
39 |
+
|
40 |
+
|
41 |
+
class ResidualGroup(nn.Module):
|
42 |
+
def __init__(self, channels, n_rcab):
|
43 |
+
super().__init__()
|
44 |
+
modules = [RCAB(channels) for _ in range(n_rcab)]
|
45 |
+
modules.append(nn.Conv2d(channels, channels, 3, padding=1))
|
46 |
+
self.body = nn.Sequential(*modules)
|
47 |
+
|
48 |
+
def forward(self, x):
|
49 |
+
return x + self.body(x)
|
50 |
+
|
51 |
+
|
52 |
+
class Upsampler(nn.Sequential):
|
53 |
+
def __init__(self, scale, channels):
|
54 |
+
m = []
|
55 |
+
for _ in range(int(torch.log2(torch.tensor(scale)))):
|
56 |
+
m.append(nn.Conv2d(channels, channels * 4, 3, padding=1))
|
57 |
+
m.append(nn.PixelShuffle(2))
|
58 |
+
super().__init__(*m)
|
59 |
+
|
60 |
+
|
61 |
+
class RCAN(nn.Module):
|
62 |
+
def __init__(self, in_channels=3, out_channels=3, n_feat=64, n_rg=10, n_rcab=20, scale=4):
|
63 |
+
super().__init__()
|
64 |
+
self.head = nn.Conv2d(in_channels, n_feat, 3, padding=1)
|
65 |
+
self.body = nn.Sequential(
|
66 |
+
*[ResidualGroup(n_feat, n_rcab) for _ in range(n_rg)],
|
67 |
+
nn.Conv2d(n_feat, n_feat, 3, padding=1)
|
68 |
+
)
|
69 |
+
self.upsample = Upsampler(scale, n_feat)
|
70 |
+
self.tail = nn.Conv2d(n_feat, out_channels, 3, padding=1)
|
71 |
+
|
72 |
+
def forward(self, x):
|
73 |
+
x = self.head(x)
|
74 |
+
res = self.body(x)
|
75 |
+
x = x + res
|
76 |
+
x = self.upsample(x)
|
77 |
+
return self.tail(x)
|
78 |
+
|
79 |
+
# === INFERENCE ===
|
80 |
+
|
81 |
+
def rcan_upscale(lr_img_pil, model_path="weights/rcan_epoch_20.pth", device='cpu'):
|
82 |
+
"""
|
83 |
+
Super resolves a low-resolution PIL image using the RCAN model.
|
84 |
+
|
85 |
+
Args:
|
86 |
+
lr_img_pil (PIL.Image): Low-resolution input image.
|
87 |
+
model_path (str): Path to the model weights.
|
88 |
+
device (str): 'cuda' or 'cpu'.
|
89 |
+
|
90 |
+
Returns:
|
91 |
+
PIL.Image: High-resolution output image.
|
92 |
+
"""
|
93 |
+
# Load model
|
94 |
+
device = torch.device(device if torch.cuda.is_available() else 'cpu')
|
95 |
+
model = RCAN(scale=4)
|
96 |
+
model.load_state_dict(torch.load(model_path, map_location=device))
|
97 |
+
model.to(device).eval()
|
98 |
+
|
99 |
+
# Convert PIL image to normalized tensor
|
100 |
+
lr_tensor = to_tensor(lr_img_pil).unsqueeze(0).to(device) # Add batch dim
|
101 |
+
|
102 |
+
# Inference
|
103 |
+
with torch.no_grad():
|
104 |
+
sr_tensor = model(lr_tensor).squeeze(0).clamp(0, 1).cpu() # Remove batch
|
105 |
+
|
106 |
+
# Convert tensor back to PIL image
|
107 |
+
sr_img_pil = to_pil_image(sr_tensor)
|
108 |
+
|
109 |
+
return sr_img_pil
|
110 |
+
|
111 |
+
|
models/spline_interpolation.py
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
|
4 |
+
def spline_upscale(image, scale_factor=4):
|
5 |
+
"""
|
6 |
+
Upscale image using Bicubic spline interpolation
|
7 |
+
|
8 |
+
Args:
|
9 |
+
image: Input image (numpy array or file path)
|
10 |
+
scale_factor: Scaling multiplier (default=4)
|
11 |
+
|
12 |
+
Returns:
|
13 |
+
Upscaled image as numpy array (uint8)
|
14 |
+
"""
|
15 |
+
# Load image if path provided
|
16 |
+
if isinstance(image, str):
|
17 |
+
img = cv2.imread(image)
|
18 |
+
if img is None:
|
19 |
+
raise ValueError(f"Could not load image from {image}")
|
20 |
+
else:
|
21 |
+
img = image.copy()
|
22 |
+
|
23 |
+
# Get original dimensions
|
24 |
+
h, w = img.shape[:2]
|
25 |
+
|
26 |
+
# Calculate new dimensions
|
27 |
+
new_w = int(w * scale_factor)
|
28 |
+
new_h = int(h * scale_factor)
|
29 |
+
|
30 |
+
# Perform bicubic interpolation
|
31 |
+
upscaled = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_CUBIC)
|
32 |
+
|
33 |
+
return upscaled
|
models/sr_gan.py
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import torch.nn as nn
|
3 |
+
from collections import OrderedDict
|
4 |
+
import numpy as np
|
5 |
+
from PIL import Image,ImageOps
|
6 |
+
|
7 |
+
class ResidualBlock(nn.Module):
|
8 |
+
def __init__(self, channels):
|
9 |
+
super().__init__()
|
10 |
+
self.block = nn.Sequential(
|
11 |
+
nn.Conv2d(channels, channels, 3, 1, 1),
|
12 |
+
nn.BatchNorm2d(channels),
|
13 |
+
nn.PReLU(),
|
14 |
+
nn.Conv2d(channels, channels, 3, 1, 1),
|
15 |
+
nn.BatchNorm2d(channels)
|
16 |
+
)
|
17 |
+
|
18 |
+
def forward(self, x):
|
19 |
+
return x + self.block(x)
|
20 |
+
|
21 |
+
class Generator(nn.Module):
|
22 |
+
def __init__(self, in_channels=3, num_res_blocks=16):
|
23 |
+
super().__init__()
|
24 |
+
|
25 |
+
self.conv1 = nn.Sequential(
|
26 |
+
nn.Conv2d(in_channels, 64, kernel_size=9, stride=1, padding=4),
|
27 |
+
nn.PReLU()
|
28 |
+
)
|
29 |
+
|
30 |
+
self.res_blocks = nn.Sequential(*[ResidualBlock(64) for _ in range(num_res_blocks)])
|
31 |
+
|
32 |
+
self.conv2 = nn.Sequential(
|
33 |
+
nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1),
|
34 |
+
nn.BatchNorm2d(64)
|
35 |
+
)
|
36 |
+
|
37 |
+
# Upsampling by 2x → 256x256, then another 2x → 512x512
|
38 |
+
self.upsample = nn.Sequential(
|
39 |
+
nn.Conv2d(64, 256, kernel_size=3, stride=1, padding=1),
|
40 |
+
nn.PixelShuffle(2), # 128 → 256
|
41 |
+
nn.PReLU(),
|
42 |
+
|
43 |
+
nn.Conv2d(64, 256, kernel_size=3, stride=1, padding=1),
|
44 |
+
nn.PixelShuffle(2), # 256 → 512
|
45 |
+
nn.PReLU()
|
46 |
+
)
|
47 |
+
|
48 |
+
self.conv3 = nn.Conv2d(64, in_channels, kernel_size=9, stride=1, padding=4)
|
49 |
+
|
50 |
+
def forward(self, x):
|
51 |
+
initial = self.conv1(x)
|
52 |
+
res = self.res_blocks(initial)
|
53 |
+
res = self.conv2(res)
|
54 |
+
out = initial + res
|
55 |
+
out = self.upsample(out)
|
56 |
+
out = self.conv3(out)
|
57 |
+
return torch.clamp(out, 0.0, 1.0) # to keep output in [0,1]
|
58 |
+
|
59 |
+
|
60 |
+
def srgan_upscale(image):
|
61 |
+
image = Image.fromarray(image)
|
62 |
+
target_size = (128, 128)
|
63 |
+
pad_color=(0, 0, 0)
|
64 |
+
ImageOps.pad(image, target_size, method=Image.BICUBIC, color=pad_color)
|
65 |
+
image = np.array(image)
|
66 |
+
|
67 |
+
image = image/255
|
68 |
+
image = torch.from_numpy(image).float().unsqueeze(dim=0).permute(0,3,1,2)
|
69 |
+
|
70 |
+
model = Generator()
|
71 |
+
checkpoint = torch.load("weights/model_srgan.pth", map_location=torch.device('cpu')) # or 'cuda' if using GPU
|
72 |
+
state_dict = checkpoint['G_state_dict']
|
73 |
+
|
74 |
+
new_state_dict = OrderedDict()
|
75 |
+
for k, v in state_dict.items():
|
76 |
+
new_key = k.replace("module.", "") # Remove "module." from each key
|
77 |
+
new_state_dict[new_key] = v
|
78 |
+
|
79 |
+
model.load_state_dict(new_state_dict)
|
80 |
+
model.eval()
|
81 |
+
|
82 |
+
with torch.no_grad():
|
83 |
+
output = model(image)
|
84 |
+
|
85 |
+
output = output.squeeze(0).permute(1, 2, 0).cpu().numpy()
|
86 |
+
output = (output * 255.0).clip(0, 255).astype("uint8")
|
87 |
+
return output
|
requirements.txt
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
Pillow
|
3 |
+
numpy
|
4 |
+
opencv-python
|
5 |
+
scipy
|
6 |
+
scikit-learn
|
7 |
+
torch
|
8 |
+
scikit-image
|
9 |
+
torchvision
|
sample_images/0001.png
ADDED
![]() |
sample_images/0002.png
ADDED
![]() |
sample_images/0003.png
ADDED
![]() |
sample_images/0004.png
ADDED
![]() |
sample_images/0006.png
ADDED
![]() |
sample_images/0007.png
ADDED
![]() |
sample_images/0010.png
ADDED
![]() |
sample_images/0012.png
ADDED
![]() |
sample_images/0019.png
ADDED
![]() |
sample_images/0021.png
ADDED
![]() |
sample_images/0024.png
ADDED
![]() |
sample_images/0055.png
ADDED
![]() |
sample_images/0064.png
ADDED
![]() |
sample_images/0068.png
ADDED
![]() |
sample_images/0086.png
ADDED
![]() |
sample_images/0097.png
ADDED
![]() |
sample_images/0171.png
ADDED
![]() |
sample_images/0172.png
ADDED
![]() |
weights/model_auto_2.pth
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:78f0ca09d2f705b77b835d049c4492a07a89a28dcc59786452bc5406c26070fe
|
3 |
+
size 2090274
|
weights/model_cnn.pth
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:2f55c32baba0df6255a4527218100929984b39f444fd6419953ee9e1ac3ae8df
|
3 |
+
size 158582
|
weights/model_espcn.pth
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:844a4dc4e53d3db576b00d8ef5fa07fcdd26bbb1119738135f9fb04845f06df4
|
3 |
+
size 454114
|
weights/model_srgan.pth
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:4d056e0bf694b2e48e7aac4513fe2178b781865881b4f5dc0df5eda6e6f5f8e5
|
3 |
+
size 81484154
|
weights/rcan_epoch_20.pth
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:1efb22fe861cd418170c0b9527d968dc0f11aee46fa20fd3a58e57c8016418dc
|
3 |
+
size 63015800
|