vericudebuget commited on
Commit
584ce58
·
verified ·
1 Parent(s): f719b30

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +241 -3
README.md CHANGED
@@ -1,3 +1,241 @@
1
- ---
2
- license: gpl-3.0
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: gpl-3.0
3
+ tags:
4
+ - img2img
5
+ - denoiser
6
+ - image
7
+ ---
8
+
9
+ # denoise_medium_v1
10
+
11
+ denoise_medium_v1 is an image denoiser made for images that have low-light noise.
12
+
13
+ It performs slightly better than [denoise_small_v1](https://huggingface.co/vericudebuget/denoise_small_v1) on images that have less colorfull noise and can reconstruct a higher level of detail from the original.
14
+
15
+ For better results, try re-proccesing the same image a few times in a loop (2 times is recommended for really noisy images).
16
+
17
+ ## Model Details
18
+
19
+
20
+
21
+ ### Model Description
22
+
23
+ <!-- Provide a longer summary of what this model is. -->
24
+
25
+
26
+
27
+ - **Developed by:** [ConvoLite AI]
28
+ - **Funded by:** [VDB]
29
+ - **Model type:** [img2img]
30
+ - **License:** [gpl-3.0]
31
+
32
+
33
+ ## Uses
34
+
35
+ <!-- Address questions around how the model is intended to be used, including the foreseeable users of the model and those affected by the model. -->
36
+ For comercial and noncomercial use.
37
+
38
+ ### Direct Use
39
+ For CPU, use the code below:
40
+ ``` python
41
+ import os
42
+ import torch
43
+ import torch.nn as nn
44
+ from PIL import Image
45
+ from torchvision.transforms import ToTensor
46
+ import numpy as np
47
+ from concurrent.futures import ThreadPoolExecutor
48
+
49
+ class DenoisingModel(nn.Module):
50
+ def __init__(self):
51
+ super(DenoisingModel, self).__init__()
52
+ self.enc1 = nn.Sequential(
53
+ nn.Conv2d(3, 64, 3, padding=1),
54
+ nn.ReLU(),
55
+ nn.Conv2d(64, 64, 3, padding=1),
56
+ nn.ReLU()
57
+ )
58
+ self.pool1 = nn.MaxPool2d(2, 2)
59
+
60
+ self.up1 = nn.ConvTranspose2d(64, 64, 2, stride=2)
61
+ self.dec1 = nn.Sequential(
62
+ nn.Conv2d(64, 64, 3, padding=1),
63
+ nn.ReLU(),
64
+ nn.Conv2d(64, 3, 3, padding=1)
65
+ )
66
+
67
+ def forward(self, x):
68
+ e1 = self.enc1(x)
69
+ p1 = self.pool1(e1)
70
+ u1 = self.up1(p1)
71
+ d1 = self.dec1(u1)
72
+ return d1
73
+
74
+ def denoise_patch(model, patch):
75
+ transform = ToTensor()
76
+ input_patch = transform(patch).unsqueeze(0)
77
+
78
+ with torch.no_grad():
79
+ output_patch = model(input_patch)
80
+
81
+ denoised_patch = output_patch.squeeze(0).permute(1, 2, 0).numpy() * 255
82
+ denoised_patch = np.clip(denoised_patch, 0, 255).astype(np.uint8)
83
+
84
+ original_patch = np.array(patch)
85
+ very_bright_mask = original_patch > 240
86
+ bright_mask = (original_patch > 220) & (original_patch <= 240)
87
+
88
+ denoised_patch[very_bright_mask] = original_patch[very_bright_mask]
89
+
90
+ blend_factor = 0.7
91
+ denoised_patch[bright_mask] = (
92
+ blend_factor * original_patch[bright_mask] +
93
+ (1 - blend_factor) * denoised_patch[bright_mask]
94
+ )
95
+
96
+ return denoised_patch
97
+
98
+ def denoise_image(image_path, model_path, patch_size=256, num_threads=4, overlap=32):
99
+ model = DenoisingModel()
100
+ checkpoint = torch.load(model_path, map_location=torch.device('cpu'))
101
+ model.load_state_dict(checkpoint['model_state_dict'])
102
+ model.eval()
103
+
104
+ # Load and get original image dimensions
105
+ image = Image.open(image_path).convert("RGB")
106
+ width, height = image.size
107
+
108
+ # Calculate padding needed
109
+ pad_right = patch_size - (width % patch_size) if width % patch_size != 0 else 0
110
+ pad_bottom = patch_size - (height % patch_size) if height % patch_size != 0 else 0
111
+
112
+ # Add padding with reflection instead of zeros
113
+ padded_width = width + pad_right
114
+ padded_height = height + pad_bottom
115
+
116
+ # Create padded image using reflection padding
117
+ padded_image = Image.new("RGB", (padded_width, padded_height))
118
+ padded_image.paste(image, (0, 0))
119
+
120
+ # Fill right border with reflected content
121
+ if pad_right > 0:
122
+ right_border = image.crop((width - pad_right, 0, width, height))
123
+ padded_image.paste(right_border.transpose(Image.FLIP_LEFT_RIGHT), (width, 0))
124
+
125
+ # Fill bottom border with reflected content
126
+ if pad_bottom > 0:
127
+ bottom_border = image.crop((0, height - pad_bottom, width, height))
128
+ padded_image.paste(bottom_border.transpose(Image.FLIP_TOP_BOTTOM), (0, height))
129
+
130
+ # Fill corner if needed
131
+ if pad_right > 0 and pad_bottom > 0:
132
+ corner = image.crop((width - pad_right, height - pad_bottom, width, height))
133
+ padded_image.paste(corner.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.FLIP_TOP_BOTTOM),
134
+ (width, height))
135
+
136
+ # Generate patches with positions
137
+ patches = []
138
+ positions = []
139
+ for i in range(0, padded_height, patch_size - overlap):
140
+ for j in range(0, padded_width, patch_size - overlap):
141
+ patch = padded_image.crop((j, i, min(j + patch_size, padded_width), min(i + patch_size, padded_height)))
142
+ patches.append(patch)
143
+ positions.append((i, j))
144
+
145
+ # Process patches in parallel
146
+ with ThreadPoolExecutor(max_workers=num_threads) as executor:
147
+ denoised_patches = list(executor.map(lambda p: denoise_patch(model, p), patches))
148
+
149
+ # Initialize output arrays
150
+ denoised_image = np.zeros((padded_height, padded_width, 3), dtype=np.float32)
151
+ weight_map = np.zeros((padded_height, padded_width), dtype=np.float32)
152
+
153
+ # Create smooth blending weights
154
+ for (i, j), denoised_patch in zip(positions, denoised_patches):
155
+ patch_height, patch_width, _ = denoised_patch.shape
156
+ patch_weights = np.ones((patch_height, patch_width), dtype=np.float32)
157
+ if i > 0:
158
+ patch_weights[:overlap, :] *= np.linspace(0, 1, overlap)[:, np.newaxis]
159
+ if j > 0:
160
+ patch_weights[:, :overlap] *= np.linspace(0, 1, overlap)[np.newaxis, :]
161
+ if i + patch_height < padded_height:
162
+ patch_weights[-overlap:, :] *= np.linspace(1, 0, overlap)[:, np.newaxis]
163
+ if j + patch_width < padded_width:
164
+ patch_weights[:, -overlap:] *= np.linspace(1, 0, overlap)[np.newaxis, :]
165
+
166
+ # Clip the patch values to prevent very bright pixels
167
+ denoised_patch = np.clip(denoised_patch, 0, 255)
168
+
169
+ denoised_image[i:i + patch_height, j:j + patch_width] += (
170
+ denoised_patch * patch_weights[:, :, np.newaxis]
171
+ )
172
+ weight_map[i:i + patch_height, j:j + patch_width] += patch_weights
173
+
174
+ # Normalize by weights
175
+ mask = weight_map > 0
176
+ denoised_image[mask] = denoised_image[mask] / weight_map[mask, np.newaxis]
177
+
178
+ # Crop to original size
179
+ denoised_image = denoised_image[:height, :width]
180
+ denoised_image = np.clip(denoised_image, 0, 255).astype(np.uint8)
181
+
182
+ # Save the result
183
+ denoised_image_path = os.path.splitext(image_path)[0] + "_denoised.png"
184
+ print(f"Saving denoised image to {denoised_image_path}")
185
+
186
+ Image.fromarray(denoised_image).save(denoised_image_path)
187
+
188
+ if __name__ == "__main__":
189
+ image_path = input("Enter the path of the image: ")
190
+ model_path = r"path/to/model.pkl"
191
+ denoise_image(image_path, model_path, num_threads=12)
192
+ print("Denoising completed.") # Use the number of threads your processor has.)
193
+ ```
194
+
195
+
196
+ ### Out-of-Scope Use
197
+
198
+ <!-- This section addresses misuse, malicious use, and uses that the model will not work well for. -->
199
+
200
+ If the image does not have a high level of noise, it is not recommended to use this model, as it will produce less than ideal results.
201
+
202
+
203
+ ## Training Details
204
+
205
+ This model was trained on a single Nvidia T4 GPU for around one hour.
206
+
207
+ ### Training Data
208
+
209
+ Around 10 GB of publicly available images under the Creative Commons license.
210
+
211
+ #### Speed
212
+
213
+ With an AMD Ryzen 5 5500 it can denoise a 2k image in approx. 2 seconds using multithreading. Still have not tested it out with CUDA, but it's probably faster.
214
+
215
+
216
+
217
+ #### Hardware
218
+
219
+
220
+ | Specifications | Minimum | Recommended |
221
+ |----------|----------|----------|
222
+ | CPU | Intel Core i7-2700K or something else that can run Python | AMD Ryzen 5 5500 |
223
+ | RAM | 4 GB | 16 GB |
224
+ | GPU | not needed | Nvidia GTX 1660 Ti |
225
+
226
+
227
+ #### Software
228
+
229
+ Python
230
+
231
+
232
+
233
+ ## Model Card Authors
234
+
235
+ Vericu de Buget
236
+
237
+
238
+ ## Model Card Contact
239
+
240
+ [convolite@europe.com](mailto:convolite@europe.com)
241
+ [ConvoLite](https://convolite.github.io/selector.html)