Spaces:
Runtime error
Runtime error
Upload 39 files
Browse files- .gitignore +6 -0
- LICENSE +21 -0
- README.md +102 -10
- RealESRGAN/__init__.py +1 -0
- RealESRGAN/__pycache__/__init__.cpython-311.pyc +0 -0
- RealESRGAN/__pycache__/arch_utils.cpython-311.pyc +0 -0
- RealESRGAN/__pycache__/model.cpython-311.pyc +0 -0
- RealESRGAN/__pycache__/rrdbnet_arch.cpython-311.pyc +0 -0
- RealESRGAN/__pycache__/utils.cpython-311.pyc +0 -0
- RealESRGAN/arch_utils.py +197 -0
- RealESRGAN/model.py +90 -0
- RealESRGAN/rrdbnet_arch.py +121 -0
- RealESRGAN/utils.py +133 -0
- __init__.py +0 -0
- app.py +55 -0
- face_enhancer.py +68 -0
- faceswap.py +214 -0
- images/Anushka.jpg +0 -0
- images/keerthi.jpg +0 -0
- images/result.png +0 -0
- images/swapseed.png +0 -0
- inswapper/.gitkeep +0 -0
- main.py +34 -0
- outputs/bramhi.jpg +0 -0
- pretrained_models/.gitkeep +0 -0
- requirements.txt +14 -0
- requirements_gpu.txt +14 -0
- upscaler/RealESRGAN/__init__.py +1 -0
- upscaler/RealESRGAN/__pycache__/__init__.cpython-311.pyc +0 -0
- upscaler/RealESRGAN/__pycache__/arch_utils.cpython-311.pyc +0 -0
- upscaler/RealESRGAN/__pycache__/model.cpython-311.pyc +0 -0
- upscaler/RealESRGAN/__pycache__/rrdbnet_arch.cpython-311.pyc +0 -0
- upscaler/RealESRGAN/__pycache__/utils.cpython-311.pyc +0 -0
- upscaler/RealESRGAN/arch_utils.py +197 -0
- upscaler/RealESRGAN/model.py +90 -0
- upscaler/RealESRGAN/rrdbnet_arch.py +121 -0
- upscaler/RealESRGAN/utils.py +133 -0
- upscaler/__init__.py +0 -0
- upscaler/__pycache__/__init__.cpython-311.pyc +0 -0
.gitignore
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
inswapper/inswapper_128.onnx
|
2 |
+
__pycache__/*
|
3 |
+
gfpgan/*
|
4 |
+
gfpgan/weights/*
|
5 |
+
outputs/*
|
6 |
+
pretrained_models/*
|
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) [2023] [kiranpranay]
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in
|
13 |
+
all copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21 |
+
THE SOFTWARE.
|
README.md
CHANGED
@@ -1,12 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
-
title: SwapSeed
|
3 |
-
emoji: 🚀
|
4 |
-
colorFrom: red
|
5 |
-
colorTo: gray
|
6 |
-
sdk: gradio
|
7 |
-
sdk_version: 4.31.5
|
8 |
-
app_file: app.py
|
9 |
-
pinned: false
|
10 |
-
---
|
11 |
|
12 |
-
|
|
|
|
|
|
1 |
+
# Face Swapping with InsightFace and ONNX
|
2 |
+
|
3 |
+
This project demonstrates face swapping using the InsightFace library and ONNX model. It allows you to swap faces between two images or even swap faces within the same image.
|
4 |
+
|
5 |
+
You can use the [available image Enhancers](#available-enhancers) to bring your output to the next level.
|
6 |
+
|
7 |
+
<p align="center">
|
8 |
+
<img src="images/result.png" width="700px" alt="Face Swap Result">
|
9 |
+
</p>
|
10 |
+
<p align="center">
|
11 |
+
<img src="images/swapseed.png" width="155" style="border-radius: 1em" alt="Face Swap Result">
|
12 |
+
</p>
|
13 |
+
|
14 |
+
## Installation
|
15 |
+
|
16 |
+
1. Clone the repository:
|
17 |
+
|
18 |
+
```bash
|
19 |
+
git clone https://github.com/KiranPranay/swapseed
|
20 |
+
cd swapseed
|
21 |
+
```
|
22 |
+
|
23 |
+
2. Install the required dependencies:
|
24 |
+
|
25 |
+
```pip
|
26 |
+
pip install -r requirements.txt
|
27 |
+
```
|
28 |
+
|
29 |
+
3. Execution
|
30 |
+
|
31 |
+
```python
|
32 |
+
python main.py
|
33 |
+
```
|
34 |
+
|
35 |
+
## Usage
|
36 |
+
|
37 |
+
### There are three main functions available for face swapping:
|
38 |
+
|
39 |
+
- swap_n_show(img1_fn, img2_fn, app, swapper, plot_before=True, plot_after=True): This function swaps faces between two input images.
|
40 |
+
|
41 |
+
- swap_n_show_same_img(img1_fn, app, swapper, plot_before=True, plot_after=True): This function swaps faces within the same image.
|
42 |
+
|
43 |
+
- swap_face_single(img1_fn, img2_fn, app, swapper): This function adds face from the source image to the target image and saves in output/ folder.
|
44 |
+
|
45 |
+
- fine_face_swap(img1_fn, img2_fn, app, swapper): This function has ability to finely select faces from image with multiple faces.
|
46 |
+
|
47 |
+
You can use these functions in your Python scripts or Jupyter notebooks.
|
48 |
+
|
49 |
+
## Example
|
50 |
+
|
51 |
+
```python
|
52 |
+
import cv2
|
53 |
+
import matplotlib.pyplot as plt
|
54 |
+
from faceswap import swap_n_show, swap_n_show_same_img, swap_face_single
|
55 |
+
|
56 |
+
# Load images
|
57 |
+
img1_fn = 'images/bramhi.jpg'
|
58 |
+
img2_fn = 'images/modi.jpg'
|
59 |
+
|
60 |
+
# Swap faces between two images
|
61 |
+
swap_n_show(img1_fn, img2_fn, app, swapper, enhance=True, enhancer='REAL-ESRGAN 2x')
|
62 |
+
|
63 |
+
# Swap faces within the same image
|
64 |
+
swap_n_show_same_img(img1_fn, app, swapper, enhance=True, enhancer='REAL-ESRGAN 2x')
|
65 |
+
|
66 |
+
# Add face to an image
|
67 |
+
swap_face_single(img1_fn, img2_fn, app, swapper, enhance=True, enhancer='REAL-ESRGAN 2x')
|
68 |
+
|
69 |
+
# Swap faces in images with multiple faces
|
70 |
+
fine_face_swap(img1_fn, img2_fn, app, swapper, enhance=True, enhancer='REAL-ESRGAN 2x')
|
71 |
+
```
|
72 |
+
|
73 |
+
## Available Enhancers
|
74 |
+
|
75 |
+
- GFPGAN
|
76 |
+
- REAL-ESRGAN 2x
|
77 |
+
- REAL-ESRGAN 4x
|
78 |
+
- REAL-ESRGAN 8x
|
79 |
+
|
80 |
+
## GPU Support
|
81 |
+
|
82 |
+
- cuda
|
83 |
+
**_(set 'device=cuda' to run with gpu)_**
|
84 |
+
|
85 |
+
## Acknowledgments
|
86 |
+
|
87 |
+
This project uses the InsightFace library and ONNX model for face analysis and swapping. Thanks to the developers of these libraries for their contributions.
|
88 |
+
|
89 |
+
- [Insightface](https://github.com/deepinsight)
|
90 |
+
- [Real-ESRGAN (ai-forever)](https://github.com/ai-forever/Real-ESRGAN)
|
91 |
+
|
92 |
+
## License
|
93 |
+
|
94 |
+
[MIT License](https://github.com/KiranPranay/faceswap/blob/main/LICENSE)
|
95 |
+
|
96 |
+
## Disclaimmer
|
97 |
+
|
98 |
+
**This project is for educational purposes only. The face swapping techniques demonstrated here are intended to showcase the capabilities of the InsightFace library and ONNX model for educational and research purposes. The project should not be used for any malicious or illegal activities.**
|
99 |
+
|
100 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
|
102 |
+
<b> If you like my content or find anything useful, give it a :star: or support me by buying me a coffee :coffee::grinning: </b>
|
103 |
+
|
104 |
+
<a href='https://ko-fi.com/R6R57A2ZT' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi3.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
RealESRGAN/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from .model import RealESRGAN
|
RealESRGAN/__pycache__/__init__.cpython-311.pyc
ADDED
Binary file (219 Bytes). View file
|
|
RealESRGAN/__pycache__/arch_utils.cpython-311.pyc
ADDED
Binary file (11.5 kB). View file
|
|
RealESRGAN/__pycache__/model.cpython-311.pyc
ADDED
Binary file (5.89 kB). View file
|
|
RealESRGAN/__pycache__/rrdbnet_arch.cpython-311.pyc
ADDED
Binary file (8.19 kB). View file
|
|
RealESRGAN/__pycache__/utils.cpython-311.pyc
ADDED
Binary file (6.29 kB). View file
|
|
RealESRGAN/arch_utils.py
ADDED
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
import torch
|
3 |
+
from torch import nn as nn
|
4 |
+
from torch.nn import functional as F
|
5 |
+
from torch.nn import init as init
|
6 |
+
from torch.nn.modules.batchnorm import _BatchNorm
|
7 |
+
|
8 |
+
@torch.no_grad()
|
9 |
+
def default_init_weights(module_list, scale=1, bias_fill=0, **kwargs):
|
10 |
+
"""Initialize network weights.
|
11 |
+
|
12 |
+
Args:
|
13 |
+
module_list (list[nn.Module] | nn.Module): Modules to be initialized.
|
14 |
+
scale (float): Scale initialized weights, especially for residual
|
15 |
+
blocks. Default: 1.
|
16 |
+
bias_fill (float): The value to fill bias. Default: 0
|
17 |
+
kwargs (dict): Other arguments for initialization function.
|
18 |
+
"""
|
19 |
+
if not isinstance(module_list, list):
|
20 |
+
module_list = [module_list]
|
21 |
+
for module in module_list:
|
22 |
+
for m in module.modules():
|
23 |
+
if isinstance(m, nn.Conv2d):
|
24 |
+
init.kaiming_normal_(m.weight, **kwargs)
|
25 |
+
m.weight.data *= scale
|
26 |
+
if m.bias is not None:
|
27 |
+
m.bias.data.fill_(bias_fill)
|
28 |
+
elif isinstance(m, nn.Linear):
|
29 |
+
init.kaiming_normal_(m.weight, **kwargs)
|
30 |
+
m.weight.data *= scale
|
31 |
+
if m.bias is not None:
|
32 |
+
m.bias.data.fill_(bias_fill)
|
33 |
+
elif isinstance(m, _BatchNorm):
|
34 |
+
init.constant_(m.weight, 1)
|
35 |
+
if m.bias is not None:
|
36 |
+
m.bias.data.fill_(bias_fill)
|
37 |
+
|
38 |
+
|
39 |
+
def make_layer(basic_block, num_basic_block, **kwarg):
|
40 |
+
"""Make layers by stacking the same blocks.
|
41 |
+
|
42 |
+
Args:
|
43 |
+
basic_block (nn.module): nn.module class for basic block.
|
44 |
+
num_basic_block (int): number of blocks.
|
45 |
+
|
46 |
+
Returns:
|
47 |
+
nn.Sequential: Stacked blocks in nn.Sequential.
|
48 |
+
"""
|
49 |
+
layers = []
|
50 |
+
for _ in range(num_basic_block):
|
51 |
+
layers.append(basic_block(**kwarg))
|
52 |
+
return nn.Sequential(*layers)
|
53 |
+
|
54 |
+
|
55 |
+
class ResidualBlockNoBN(nn.Module):
|
56 |
+
"""Residual block without BN.
|
57 |
+
|
58 |
+
It has a style of:
|
59 |
+
---Conv-ReLU-Conv-+-
|
60 |
+
|________________|
|
61 |
+
|
62 |
+
Args:
|
63 |
+
num_feat (int): Channel number of intermediate features.
|
64 |
+
Default: 64.
|
65 |
+
res_scale (float): Residual scale. Default: 1.
|
66 |
+
pytorch_init (bool): If set to True, use pytorch default init,
|
67 |
+
otherwise, use default_init_weights. Default: False.
|
68 |
+
"""
|
69 |
+
|
70 |
+
def __init__(self, num_feat=64, res_scale=1, pytorch_init=False):
|
71 |
+
super(ResidualBlockNoBN, self).__init__()
|
72 |
+
self.res_scale = res_scale
|
73 |
+
self.conv1 = nn.Conv2d(num_feat, num_feat, 3, 1, 1, bias=True)
|
74 |
+
self.conv2 = nn.Conv2d(num_feat, num_feat, 3, 1, 1, bias=True)
|
75 |
+
self.relu = nn.ReLU(inplace=True)
|
76 |
+
|
77 |
+
if not pytorch_init:
|
78 |
+
default_init_weights([self.conv1, self.conv2], 0.1)
|
79 |
+
|
80 |
+
def forward(self, x):
|
81 |
+
identity = x
|
82 |
+
out = self.conv2(self.relu(self.conv1(x)))
|
83 |
+
return identity + out * self.res_scale
|
84 |
+
|
85 |
+
|
86 |
+
class Upsample(nn.Sequential):
|
87 |
+
"""Upsample module.
|
88 |
+
|
89 |
+
Args:
|
90 |
+
scale (int): Scale factor. Supported scales: 2^n and 3.
|
91 |
+
num_feat (int): Channel number of intermediate features.
|
92 |
+
"""
|
93 |
+
|
94 |
+
def __init__(self, scale, num_feat):
|
95 |
+
m = []
|
96 |
+
if (scale & (scale - 1)) == 0: # scale = 2^n
|
97 |
+
for _ in range(int(math.log(scale, 2))):
|
98 |
+
m.append(nn.Conv2d(num_feat, 4 * num_feat, 3, 1, 1))
|
99 |
+
m.append(nn.PixelShuffle(2))
|
100 |
+
elif scale == 3:
|
101 |
+
m.append(nn.Conv2d(num_feat, 9 * num_feat, 3, 1, 1))
|
102 |
+
m.append(nn.PixelShuffle(3))
|
103 |
+
else:
|
104 |
+
raise ValueError(f'scale {scale} is not supported. ' 'Supported scales: 2^n and 3.')
|
105 |
+
super(Upsample, self).__init__(*m)
|
106 |
+
|
107 |
+
|
108 |
+
def flow_warp(x, flow, interp_mode='bilinear', padding_mode='zeros', align_corners=True):
|
109 |
+
"""Warp an image or feature map with optical flow.
|
110 |
+
|
111 |
+
Args:
|
112 |
+
x (Tensor): Tensor with size (n, c, h, w).
|
113 |
+
flow (Tensor): Tensor with size (n, h, w, 2), normal value.
|
114 |
+
interp_mode (str): 'nearest' or 'bilinear'. Default: 'bilinear'.
|
115 |
+
padding_mode (str): 'zeros' or 'border' or 'reflection'.
|
116 |
+
Default: 'zeros'.
|
117 |
+
align_corners (bool): Before pytorch 1.3, the default value is
|
118 |
+
align_corners=True. After pytorch 1.3, the default value is
|
119 |
+
align_corners=False. Here, we use the True as default.
|
120 |
+
|
121 |
+
Returns:
|
122 |
+
Tensor: Warped image or feature map.
|
123 |
+
"""
|
124 |
+
assert x.size()[-2:] == flow.size()[1:3]
|
125 |
+
_, _, h, w = x.size()
|
126 |
+
# create mesh grid
|
127 |
+
grid_y, grid_x = torch.meshgrid(torch.arange(0, h).type_as(x), torch.arange(0, w).type_as(x))
|
128 |
+
grid = torch.stack((grid_x, grid_y), 2).float() # W(x), H(y), 2
|
129 |
+
grid.requires_grad = False
|
130 |
+
|
131 |
+
vgrid = grid + flow
|
132 |
+
# scale grid to [-1,1]
|
133 |
+
vgrid_x = 2.0 * vgrid[:, :, :, 0] / max(w - 1, 1) - 1.0
|
134 |
+
vgrid_y = 2.0 * vgrid[:, :, :, 1] / max(h - 1, 1) - 1.0
|
135 |
+
vgrid_scaled = torch.stack((vgrid_x, vgrid_y), dim=3)
|
136 |
+
output = F.grid_sample(x, vgrid_scaled, mode=interp_mode, padding_mode=padding_mode, align_corners=align_corners)
|
137 |
+
|
138 |
+
# TODO, what if align_corners=False
|
139 |
+
return output
|
140 |
+
|
141 |
+
|
142 |
+
def resize_flow(flow, size_type, sizes, interp_mode='bilinear', align_corners=False):
|
143 |
+
"""Resize a flow according to ratio or shape.
|
144 |
+
|
145 |
+
Args:
|
146 |
+
flow (Tensor): Precomputed flow. shape [N, 2, H, W].
|
147 |
+
size_type (str): 'ratio' or 'shape'.
|
148 |
+
sizes (list[int | float]): the ratio for resizing or the final output
|
149 |
+
shape.
|
150 |
+
1) The order of ratio should be [ratio_h, ratio_w]. For
|
151 |
+
downsampling, the ratio should be smaller than 1.0 (i.e., ratio
|
152 |
+
< 1.0). For upsampling, the ratio should be larger than 1.0 (i.e.,
|
153 |
+
ratio > 1.0).
|
154 |
+
2) The order of output_size should be [out_h, out_w].
|
155 |
+
interp_mode (str): The mode of interpolation for resizing.
|
156 |
+
Default: 'bilinear'.
|
157 |
+
align_corners (bool): Whether align corners. Default: False.
|
158 |
+
|
159 |
+
Returns:
|
160 |
+
Tensor: Resized flow.
|
161 |
+
"""
|
162 |
+
_, _, flow_h, flow_w = flow.size()
|
163 |
+
if size_type == 'ratio':
|
164 |
+
output_h, output_w = int(flow_h * sizes[0]), int(flow_w * sizes[1])
|
165 |
+
elif size_type == 'shape':
|
166 |
+
output_h, output_w = sizes[0], sizes[1]
|
167 |
+
else:
|
168 |
+
raise ValueError(f'Size type should be ratio or shape, but got type {size_type}.')
|
169 |
+
|
170 |
+
input_flow = flow.clone()
|
171 |
+
ratio_h = output_h / flow_h
|
172 |
+
ratio_w = output_w / flow_w
|
173 |
+
input_flow[:, 0, :, :] *= ratio_w
|
174 |
+
input_flow[:, 1, :, :] *= ratio_h
|
175 |
+
resized_flow = F.interpolate(
|
176 |
+
input=input_flow, size=(output_h, output_w), mode=interp_mode, align_corners=align_corners)
|
177 |
+
return resized_flow
|
178 |
+
|
179 |
+
|
180 |
+
# TODO: may write a cpp file
|
181 |
+
def pixel_unshuffle(x, scale):
|
182 |
+
""" Pixel unshuffle.
|
183 |
+
|
184 |
+
Args:
|
185 |
+
x (Tensor): Input feature with shape (b, c, hh, hw).
|
186 |
+
scale (int): Downsample ratio.
|
187 |
+
|
188 |
+
Returns:
|
189 |
+
Tensor: the pixel unshuffled feature.
|
190 |
+
"""
|
191 |
+
b, c, hh, hw = x.size()
|
192 |
+
out_channel = c * (scale**2)
|
193 |
+
assert hh % scale == 0 and hw % scale == 0
|
194 |
+
h = hh // scale
|
195 |
+
w = hw // scale
|
196 |
+
x_view = x.view(b, c, h, scale, w, scale)
|
197 |
+
return x_view.permute(0, 1, 3, 5, 2, 4).reshape(b, out_channel, h, w)
|
RealESRGAN/model.py
ADDED
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import torch
|
3 |
+
from torch.nn import functional as F
|
4 |
+
from PIL import Image
|
5 |
+
import numpy as np
|
6 |
+
import cv2
|
7 |
+
|
8 |
+
from .rrdbnet_arch import RRDBNet
|
9 |
+
from .utils import pad_reflect, split_image_into_overlapping_patches, stich_together, \
|
10 |
+
unpad_image
|
11 |
+
|
12 |
+
|
13 |
+
HF_MODELS = {
|
14 |
+
2: dict(
|
15 |
+
repo_id='sberbank-ai/Real-ESRGAN',
|
16 |
+
filename='RealESRGAN_x2.pth',
|
17 |
+
),
|
18 |
+
4: dict(
|
19 |
+
repo_id='sberbank-ai/Real-ESRGAN',
|
20 |
+
filename='RealESRGAN_x4.pth',
|
21 |
+
),
|
22 |
+
8: dict(
|
23 |
+
repo_id='sberbank-ai/Real-ESRGAN',
|
24 |
+
filename='RealESRGAN_x8.pth',
|
25 |
+
),
|
26 |
+
}
|
27 |
+
|
28 |
+
|
29 |
+
class RealESRGAN:
|
30 |
+
def __init__(self, device, scale=4):
|
31 |
+
self.device = device
|
32 |
+
self.scale = scale
|
33 |
+
self.model = RRDBNet(
|
34 |
+
num_in_ch=3, num_out_ch=3, num_feat=64,
|
35 |
+
num_block=23, num_grow_ch=32, scale=scale
|
36 |
+
)
|
37 |
+
|
38 |
+
def load_weights(self, model_path, download=True):
|
39 |
+
if not os.path.exists(model_path) and download:
|
40 |
+
from huggingface_hub import hf_hub_url, cached_download
|
41 |
+
assert self.scale in [2,4,8], 'You can download models only with scales: 2, 4, 8'
|
42 |
+
config = HF_MODELS[self.scale]
|
43 |
+
cache_dir = os.path.dirname(model_path)
|
44 |
+
local_filename = os.path.basename(model_path)
|
45 |
+
config_file_url = hf_hub_url(repo_id=config['repo_id'], filename=config['filename'])
|
46 |
+
cached_download(config_file_url, cache_dir=cache_dir, force_filename=local_filename)
|
47 |
+
print('Weights downloaded to:', os.path.join(cache_dir, local_filename))
|
48 |
+
|
49 |
+
loadnet = torch.load(model_path)
|
50 |
+
if 'params' in loadnet:
|
51 |
+
self.model.load_state_dict(loadnet['params'], strict=True)
|
52 |
+
elif 'params_ema' in loadnet:
|
53 |
+
self.model.load_state_dict(loadnet['params_ema'], strict=True)
|
54 |
+
else:
|
55 |
+
self.model.load_state_dict(loadnet, strict=True)
|
56 |
+
self.model.eval()
|
57 |
+
self.model.to(self.device)
|
58 |
+
|
59 |
+
@torch.cuda.amp.autocast()
|
60 |
+
def predict(self, lr_image, batch_size=4, patches_size=192,
|
61 |
+
padding=24, pad_size=15):
|
62 |
+
scale = self.scale
|
63 |
+
device = self.device
|
64 |
+
lr_image = np.array(lr_image)
|
65 |
+
lr_image = pad_reflect(lr_image, pad_size)
|
66 |
+
|
67 |
+
patches, p_shape = split_image_into_overlapping_patches(
|
68 |
+
lr_image, patch_size=patches_size, padding_size=padding
|
69 |
+
)
|
70 |
+
img = torch.FloatTensor(patches/255).permute((0,3,1,2)).to(device).detach()
|
71 |
+
|
72 |
+
with torch.no_grad():
|
73 |
+
res = self.model(img[0:batch_size])
|
74 |
+
for i in range(batch_size, img.shape[0], batch_size):
|
75 |
+
res = torch.cat((res, self.model(img[i:i+batch_size])), 0)
|
76 |
+
|
77 |
+
sr_image = res.permute((0,2,3,1)).clamp_(0, 1).cpu()
|
78 |
+
np_sr_image = sr_image.numpy()
|
79 |
+
|
80 |
+
padded_size_scaled = tuple(np.multiply(p_shape[0:2], scale)) + (3,)
|
81 |
+
scaled_image_shape = tuple(np.multiply(lr_image.shape[0:2], scale)) + (3,)
|
82 |
+
np_sr_image = stich_together(
|
83 |
+
np_sr_image, padded_image_shape=padded_size_scaled,
|
84 |
+
target_shape=scaled_image_shape, padding_size=padding * scale
|
85 |
+
)
|
86 |
+
sr_img = (np_sr_image*255).astype(np.uint8)
|
87 |
+
sr_img = unpad_image(sr_img, pad_size*scale)
|
88 |
+
#sr_img = Image.fromarray(sr_img)
|
89 |
+
|
90 |
+
return sr_img
|
RealESRGAN/rrdbnet_arch.py
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from torch import nn as nn
|
3 |
+
from torch.nn import functional as F
|
4 |
+
|
5 |
+
from .arch_utils import default_init_weights, make_layer, pixel_unshuffle
|
6 |
+
|
7 |
+
|
8 |
+
class ResidualDenseBlock(nn.Module):
|
9 |
+
"""Residual Dense Block.
|
10 |
+
|
11 |
+
Used in RRDB block in ESRGAN.
|
12 |
+
|
13 |
+
Args:
|
14 |
+
num_feat (int): Channel number of intermediate features.
|
15 |
+
num_grow_ch (int): Channels for each growth.
|
16 |
+
"""
|
17 |
+
|
18 |
+
def __init__(self, num_feat=64, num_grow_ch=32):
|
19 |
+
super(ResidualDenseBlock, self).__init__()
|
20 |
+
self.conv1 = nn.Conv2d(num_feat, num_grow_ch, 3, 1, 1)
|
21 |
+
self.conv2 = nn.Conv2d(num_feat + num_grow_ch, num_grow_ch, 3, 1, 1)
|
22 |
+
self.conv3 = nn.Conv2d(num_feat + 2 * num_grow_ch, num_grow_ch, 3, 1, 1)
|
23 |
+
self.conv4 = nn.Conv2d(num_feat + 3 * num_grow_ch, num_grow_ch, 3, 1, 1)
|
24 |
+
self.conv5 = nn.Conv2d(num_feat + 4 * num_grow_ch, num_feat, 3, 1, 1)
|
25 |
+
|
26 |
+
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
|
27 |
+
|
28 |
+
# initialization
|
29 |
+
default_init_weights([self.conv1, self.conv2, self.conv3, self.conv4, self.conv5], 0.1)
|
30 |
+
|
31 |
+
def forward(self, x):
|
32 |
+
x1 = self.lrelu(self.conv1(x))
|
33 |
+
x2 = self.lrelu(self.conv2(torch.cat((x, x1), 1)))
|
34 |
+
x3 = self.lrelu(self.conv3(torch.cat((x, x1, x2), 1)))
|
35 |
+
x4 = self.lrelu(self.conv4(torch.cat((x, x1, x2, x3), 1)))
|
36 |
+
x5 = self.conv5(torch.cat((x, x1, x2, x3, x4), 1))
|
37 |
+
# Emperically, we use 0.2 to scale the residual for better performance
|
38 |
+
return x5 * 0.2 + x
|
39 |
+
|
40 |
+
|
41 |
+
class RRDB(nn.Module):
|
42 |
+
"""Residual in Residual Dense Block.
|
43 |
+
|
44 |
+
Used in RRDB-Net in ESRGAN.
|
45 |
+
|
46 |
+
Args:
|
47 |
+
num_feat (int): Channel number of intermediate features.
|
48 |
+
num_grow_ch (int): Channels for each growth.
|
49 |
+
"""
|
50 |
+
|
51 |
+
def __init__(self, num_feat, num_grow_ch=32):
|
52 |
+
super(RRDB, self).__init__()
|
53 |
+
self.rdb1 = ResidualDenseBlock(num_feat, num_grow_ch)
|
54 |
+
self.rdb2 = ResidualDenseBlock(num_feat, num_grow_ch)
|
55 |
+
self.rdb3 = ResidualDenseBlock(num_feat, num_grow_ch)
|
56 |
+
|
57 |
+
def forward(self, x):
|
58 |
+
out = self.rdb1(x)
|
59 |
+
out = self.rdb2(out)
|
60 |
+
out = self.rdb3(out)
|
61 |
+
# Emperically, we use 0.2 to scale the residual for better performance
|
62 |
+
return out * 0.2 + x
|
63 |
+
|
64 |
+
|
65 |
+
class RRDBNet(nn.Module):
|
66 |
+
"""Networks consisting of Residual in Residual Dense Block, which is used
|
67 |
+
in ESRGAN.
|
68 |
+
|
69 |
+
ESRGAN: Enhanced Super-Resolution Generative Adversarial Networks.
|
70 |
+
|
71 |
+
We extend ESRGAN for scale x2 and scale x1.
|
72 |
+
Note: This is one option for scale 1, scale 2 in RRDBNet.
|
73 |
+
We first employ the pixel-unshuffle (an inverse operation of pixelshuffle to reduce the spatial size
|
74 |
+
and enlarge the channel size before feeding inputs into the main ESRGAN architecture.
|
75 |
+
|
76 |
+
Args:
|
77 |
+
num_in_ch (int): Channel number of inputs.
|
78 |
+
num_out_ch (int): Channel number of outputs.
|
79 |
+
num_feat (int): Channel number of intermediate features.
|
80 |
+
Default: 64
|
81 |
+
num_block (int): Block number in the trunk network. Defaults: 23
|
82 |
+
num_grow_ch (int): Channels for each growth. Default: 32.
|
83 |
+
"""
|
84 |
+
|
85 |
+
def __init__(self, num_in_ch, num_out_ch, scale=4, num_feat=64, num_block=23, num_grow_ch=32):
|
86 |
+
super(RRDBNet, self).__init__()
|
87 |
+
self.scale = scale
|
88 |
+
if scale == 2:
|
89 |
+
num_in_ch = num_in_ch * 4
|
90 |
+
elif scale == 1:
|
91 |
+
num_in_ch = num_in_ch * 16
|
92 |
+
self.conv_first = nn.Conv2d(num_in_ch, num_feat, 3, 1, 1)
|
93 |
+
self.body = make_layer(RRDB, num_block, num_feat=num_feat, num_grow_ch=num_grow_ch)
|
94 |
+
self.conv_body = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
95 |
+
# upsample
|
96 |
+
self.conv_up1 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
97 |
+
self.conv_up2 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
98 |
+
if scale == 8:
|
99 |
+
self.conv_up3 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
100 |
+
self.conv_hr = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
101 |
+
self.conv_last = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1)
|
102 |
+
|
103 |
+
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
|
104 |
+
|
105 |
+
def forward(self, x):
|
106 |
+
if self.scale == 2:
|
107 |
+
feat = pixel_unshuffle(x, scale=2)
|
108 |
+
elif self.scale == 1:
|
109 |
+
feat = pixel_unshuffle(x, scale=4)
|
110 |
+
else:
|
111 |
+
feat = x
|
112 |
+
feat = self.conv_first(feat)
|
113 |
+
body_feat = self.conv_body(self.body(feat))
|
114 |
+
feat = feat + body_feat
|
115 |
+
# upsample
|
116 |
+
feat = self.lrelu(self.conv_up1(F.interpolate(feat, scale_factor=2, mode='nearest')))
|
117 |
+
feat = self.lrelu(self.conv_up2(F.interpolate(feat, scale_factor=2, mode='nearest')))
|
118 |
+
if self.scale == 8:
|
119 |
+
feat = self.lrelu(self.conv_up3(F.interpolate(feat, scale_factor=2, mode='nearest')))
|
120 |
+
out = self.conv_last(self.lrelu(self.conv_hr(feat)))
|
121 |
+
return out
|
RealESRGAN/utils.py
ADDED
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import torch
|
3 |
+
from PIL import Image
|
4 |
+
import os
|
5 |
+
import io
|
6 |
+
|
7 |
+
def pad_reflect(image, pad_size):
|
8 |
+
imsize = image.shape
|
9 |
+
height, width = imsize[:2]
|
10 |
+
new_img = np.zeros([height+pad_size*2, width+pad_size*2, imsize[2]]).astype(np.uint8)
|
11 |
+
new_img[pad_size:-pad_size, pad_size:-pad_size, :] = image
|
12 |
+
|
13 |
+
new_img[0:pad_size, pad_size:-pad_size, :] = np.flip(image[0:pad_size, :, :], axis=0) #top
|
14 |
+
new_img[-pad_size:, pad_size:-pad_size, :] = np.flip(image[-pad_size:, :, :], axis=0) #bottom
|
15 |
+
new_img[:, 0:pad_size, :] = np.flip(new_img[:, pad_size:pad_size*2, :], axis=1) #left
|
16 |
+
new_img[:, -pad_size:, :] = np.flip(new_img[:, -pad_size*2:-pad_size, :], axis=1) #right
|
17 |
+
|
18 |
+
return new_img
|
19 |
+
|
20 |
+
def unpad_image(image, pad_size):
|
21 |
+
return image[pad_size:-pad_size, pad_size:-pad_size, :]
|
22 |
+
|
23 |
+
|
24 |
+
def process_array(image_array, expand=True):
|
25 |
+
""" Process a 3-dimensional array into a scaled, 4 dimensional batch of size 1. """
|
26 |
+
|
27 |
+
image_batch = image_array / 255.0
|
28 |
+
if expand:
|
29 |
+
image_batch = np.expand_dims(image_batch, axis=0)
|
30 |
+
return image_batch
|
31 |
+
|
32 |
+
|
33 |
+
def process_output(output_tensor):
|
34 |
+
""" Transforms the 4-dimensional output tensor into a suitable image format. """
|
35 |
+
|
36 |
+
sr_img = output_tensor.clip(0, 1) * 255
|
37 |
+
sr_img = np.uint8(sr_img)
|
38 |
+
return sr_img
|
39 |
+
|
40 |
+
|
41 |
+
def pad_patch(image_patch, padding_size, channel_last=True):
|
42 |
+
""" Pads image_patch with with padding_size edge values. """
|
43 |
+
|
44 |
+
if channel_last:
|
45 |
+
return np.pad(
|
46 |
+
image_patch,
|
47 |
+
((padding_size, padding_size), (padding_size, padding_size), (0, 0)),
|
48 |
+
'edge',
|
49 |
+
)
|
50 |
+
else:
|
51 |
+
return np.pad(
|
52 |
+
image_patch,
|
53 |
+
((0, 0), (padding_size, padding_size), (padding_size, padding_size)),
|
54 |
+
'edge',
|
55 |
+
)
|
56 |
+
|
57 |
+
|
58 |
+
def unpad_patches(image_patches, padding_size):
|
59 |
+
return image_patches[:, padding_size:-padding_size, padding_size:-padding_size, :]
|
60 |
+
|
61 |
+
|
62 |
+
def split_image_into_overlapping_patches(image_array, patch_size, padding_size=2):
|
63 |
+
""" Splits the image into partially overlapping patches.
|
64 |
+
The patches overlap by padding_size pixels.
|
65 |
+
Pads the image twice:
|
66 |
+
- first to have a size multiple of the patch size,
|
67 |
+
- then to have equal padding at the borders.
|
68 |
+
Args:
|
69 |
+
image_array: numpy array of the input image.
|
70 |
+
patch_size: size of the patches from the original image (without padding).
|
71 |
+
padding_size: size of the overlapping area.
|
72 |
+
"""
|
73 |
+
|
74 |
+
xmax, ymax, _ = image_array.shape
|
75 |
+
x_remainder = xmax % patch_size
|
76 |
+
y_remainder = ymax % patch_size
|
77 |
+
|
78 |
+
# modulo here is to avoid extending of patch_size instead of 0
|
79 |
+
x_extend = (patch_size - x_remainder) % patch_size
|
80 |
+
y_extend = (patch_size - y_remainder) % patch_size
|
81 |
+
|
82 |
+
# make sure the image is divisible into regular patches
|
83 |
+
extended_image = np.pad(image_array, ((0, x_extend), (0, y_extend), (0, 0)), 'edge')
|
84 |
+
|
85 |
+
# add padding around the image to simplify computations
|
86 |
+
padded_image = pad_patch(extended_image, padding_size, channel_last=True)
|
87 |
+
|
88 |
+
xmax, ymax, _ = padded_image.shape
|
89 |
+
patches = []
|
90 |
+
|
91 |
+
x_lefts = range(padding_size, xmax - padding_size, patch_size)
|
92 |
+
y_tops = range(padding_size, ymax - padding_size, patch_size)
|
93 |
+
|
94 |
+
for x in x_lefts:
|
95 |
+
for y in y_tops:
|
96 |
+
x_left = x - padding_size
|
97 |
+
y_top = y - padding_size
|
98 |
+
x_right = x + patch_size + padding_size
|
99 |
+
y_bottom = y + patch_size + padding_size
|
100 |
+
patch = padded_image[x_left:x_right, y_top:y_bottom, :]
|
101 |
+
patches.append(patch)
|
102 |
+
|
103 |
+
return np.array(patches), padded_image.shape
|
104 |
+
|
105 |
+
|
106 |
+
def stich_together(patches, padded_image_shape, target_shape, padding_size=4):
|
107 |
+
""" Reconstruct the image from overlapping patches.
|
108 |
+
After scaling, shapes and padding should be scaled too.
|
109 |
+
Args:
|
110 |
+
patches: patches obtained with split_image_into_overlapping_patches
|
111 |
+
padded_image_shape: shape of the padded image contructed in split_image_into_overlapping_patches
|
112 |
+
target_shape: shape of the final image
|
113 |
+
padding_size: size of the overlapping area.
|
114 |
+
"""
|
115 |
+
|
116 |
+
xmax, ymax, _ = padded_image_shape
|
117 |
+
patches = unpad_patches(patches, padding_size)
|
118 |
+
patch_size = patches.shape[1]
|
119 |
+
n_patches_per_row = ymax // patch_size
|
120 |
+
|
121 |
+
complete_image = np.zeros((xmax, ymax, 3))
|
122 |
+
|
123 |
+
row = -1
|
124 |
+
col = 0
|
125 |
+
for i in range(len(patches)):
|
126 |
+
if i % n_patches_per_row == 0:
|
127 |
+
row += 1
|
128 |
+
col = 0
|
129 |
+
complete_image[
|
130 |
+
row * patch_size: (row + 1) * patch_size, col * patch_size: (col + 1) * patch_size,:
|
131 |
+
] = patches[i]
|
132 |
+
col += 1
|
133 |
+
return complete_image[0: target_shape[0], 0: target_shape[1], :]
|
__init__.py
ADDED
File without changes
|
app.py
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Created By: ishwor subedi
|
3 |
+
Date: 2024-05-27
|
4 |
+
"""
|
5 |
+
import os
|
6 |
+
import matplotlib.pyplot as plt
|
7 |
+
import gdown
|
8 |
+
import insightface
|
9 |
+
from insightface.app import FaceAnalysis
|
10 |
+
from insightface.data import get_image as ins_get_image
|
11 |
+
from faceswap import swap_n_show, swap_n_show_same_img, swap_face_single, fine_face_swap
|
12 |
+
import gradio as gr
|
13 |
+
|
14 |
+
app = FaceAnalysis(name='buffalo_l')
|
15 |
+
app.prepare(ctx_id=0, det_size=(640, 640))
|
16 |
+
|
17 |
+
# Download 'inswapper_128.onnx' file using gdown
|
18 |
+
model_url = 'https://drive.google.com/uc?id=1HvZ4MAtzlY74Dk4ASGIS9L6Rg5oZdqvu'
|
19 |
+
model_output_path = 'inswapper/inswapper_128.onnx'
|
20 |
+
if not os.path.exists(model_output_path):
|
21 |
+
gdown.download(model_url, model_output_path, quiet=False)
|
22 |
+
|
23 |
+
swapper = insightface.model_zoo.get_model('inswapper/inswapper_128.onnx', download=False, download_zip=False)
|
24 |
+
|
25 |
+
|
26 |
+
# # Load images
|
27 |
+
# img1_fn = 'images/Anushka.jpg'
|
28 |
+
# img2_fn = 'images/keerthi.jpg'
|
29 |
+
#
|
30 |
+
# # Swap faces between two images
|
31 |
+
# # swap_n_show(img1_fn, img2_fn, app, swapper)
|
32 |
+
#
|
33 |
+
# # Swap faces within the same image
|
34 |
+
# # swap_n_show_same_img(img1_fn, app, swapper)
|
35 |
+
#
|
36 |
+
# # Add face to an image
|
37 |
+
# # swap_face_single(img1_fn, img2_fn, app, swapper, enhance=True, enhancer='REAL-ESRGAN 2x',device="cpu")
|
38 |
+
|
39 |
+
# Fine face swapper
|
40 |
+
def swap_face(img1_fn, img2_fn):
|
41 |
+
fine_face_swap(img1_fn, img2_fn, app, swapper, enhance=True, enhancer='REAL-ESRGAN 2x', device="cpu")
|
42 |
+
|
43 |
+
|
44 |
+
with gr.Blocks() as face_swap:
|
45 |
+
with gr.Row():
|
46 |
+
input_image1 = gr.Image(label="Source Image")
|
47 |
+
input_image2 = gr.Image(label="Target Image")
|
48 |
+
output_image = gr.Image(label="Output Image")
|
49 |
+
|
50 |
+
with gr.Row():
|
51 |
+
button = gr.Button(text="Swap Face", onclick=swap_face)
|
52 |
+
|
53 |
+
button.click(fn=swap_face, inputs=[input_image1, input_image2], outputs=[output_image])
|
54 |
+
|
55 |
+
face_swap.launch(debug=True, share=True)
|
face_enhancer.py
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import cv2
|
3 |
+
import torch
|
4 |
+
import gfpgan
|
5 |
+
import gdown
|
6 |
+
from PIL import Image
|
7 |
+
from upscaler.RealESRGAN import RealESRGAN
|
8 |
+
|
9 |
+
|
10 |
+
def gfpgan_runner(img, model):
|
11 |
+
_, imgs, _ = model.enhance(img, paste_back=True, has_aligned=True)
|
12 |
+
return imgs[0]
|
13 |
+
|
14 |
+
|
15 |
+
def realesrgan_runner(img, model):
|
16 |
+
img = model.predict(img)
|
17 |
+
return img
|
18 |
+
|
19 |
+
|
20 |
+
supported_enhancers = {
|
21 |
+
"GFPGAN": ("./pretrained_models/GFPGANv1.4.pth", gfpgan_runner),
|
22 |
+
"REAL-ESRGAN 2x": ("./pretrained_models/RealESRGAN_x2.pth", realesrgan_runner),
|
23 |
+
"REAL-ESRGAN 4x": ("./pretrained_models/RealESRGAN_x4.pth", realesrgan_runner),
|
24 |
+
"REAL-ESRGAN 8x": ("./pretrained_models/RealESRGAN_x8.pth", realesrgan_runner)
|
25 |
+
}
|
26 |
+
|
27 |
+
cv2_interpolations = ["LANCZOS4", "CUBIC", "NEAREST"]
|
28 |
+
|
29 |
+
def model_check(model_url, model_path):
|
30 |
+
if not os.path.exists(model_path):
|
31 |
+
gdown.download(model_url, model_path, quiet=False)
|
32 |
+
|
33 |
+
|
34 |
+
def load_face_enhancer_model(name='GFPGAN', device="cpu"):
|
35 |
+
if name in supported_enhancers.keys():
|
36 |
+
model_path, model_runner = supported_enhancers.get(name)
|
37 |
+
model_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), model_path)
|
38 |
+
if name == 'GFPGAN':
|
39 |
+
model_url = 'https://drive.google.com/uc?id=1QsJPgvZNwFsBktbeYENVsEq663UgBQRj'
|
40 |
+
model_check(model_url, model_path)
|
41 |
+
model = gfpgan.GFPGANer(model_path=model_path, upscale=1, device=device)
|
42 |
+
elif name == 'REAL-ESRGAN 2x':
|
43 |
+
model_url = 'https://drive.google.com/uc?id=1BYFc4ttYGHmA-GZMmgXW9NdgPkXkgjtv'
|
44 |
+
model_check(model_url, model_path)
|
45 |
+
model = RealESRGAN(device, scale=2)
|
46 |
+
model.load_weights(model_path, download=False)
|
47 |
+
elif name == 'REAL-ESRGAN 4x':
|
48 |
+
model_url = 'https://drive.google.com/uc?id=1N4MNjfGhrz-CHq99WCp6NEfgzMIGxAE0'
|
49 |
+
model_check(model_url, model_path)
|
50 |
+
model = RealESRGAN(device, scale=4)
|
51 |
+
model.load_weights(model_path, download=False)
|
52 |
+
elif name == 'REAL-ESRGAN 8x':
|
53 |
+
model_url = 'https://drive.google.com/uc?id=14FtSjtgtl8iySVrrvFDX-HxCCkdbsoPh'
|
54 |
+
model_check(model_url, model_path)
|
55 |
+
model = RealESRGAN(device, scale=8)
|
56 |
+
model.load_weights(model_path, download=False)
|
57 |
+
elif name == 'LANCZOS4':
|
58 |
+
model = None
|
59 |
+
model_runner = lambda img, _: cv2.resize(img, (512,512), interpolation=cv2.INTER_LANCZOS4)
|
60 |
+
elif name == 'CUBIC':
|
61 |
+
model = None
|
62 |
+
model_runner = lambda img, _: cv2.resize(img, (512,512), interpolation=cv2.INTER_CUBIC)
|
63 |
+
elif name == 'NEAREST':
|
64 |
+
model = None
|
65 |
+
model_runner = lambda img, _: cv2.resize(img, (512,512), interpolation=cv2.INTER_NEAREST)
|
66 |
+
else:
|
67 |
+
model = None
|
68 |
+
return (model, model_runner)
|
faceswap.py
ADDED
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import cv2
|
3 |
+
import matplotlib.pyplot as plt
|
4 |
+
|
5 |
+
from face_enhancer import load_face_enhancer_model
|
6 |
+
|
7 |
+
def validate_image(img):
|
8 |
+
if not os.path.exists(img):
|
9 |
+
raise ValueError(f'Image {img} does not exist')
|
10 |
+
# check if img is a valid image file
|
11 |
+
if not os.path.isfile(img):
|
12 |
+
raise ValueError(f'Image {img} is not a valid image file')
|
13 |
+
# validate it to be jpg jpeg, png formats
|
14 |
+
if not img.lower().endswith(('.jpg', '.jpeg', '.png')):
|
15 |
+
raise ValueError(f'Image {img} is not a valid image file')
|
16 |
+
|
17 |
+
def cpu_warning(device):
|
18 |
+
if device == "cpu":
|
19 |
+
print("Using CPU for face enhancer. If you have a GPU, you can set device='cuda' to speed up the process. You can also set enhance=False to skip the enhancement.")
|
20 |
+
|
21 |
+
def swap_n_show(img1_fn, img2_fn, app, swapper,
|
22 |
+
plot_before=False, plot_after=True, enhance=False, enhancer='REAL-ESRGAN 2x',device="cpu"):
|
23 |
+
|
24 |
+
validate_image(img1_fn)
|
25 |
+
validate_image(img2_fn)
|
26 |
+
|
27 |
+
img1 = cv2.imread(img1_fn)
|
28 |
+
img2 = cv2.imread(img2_fn)
|
29 |
+
|
30 |
+
if plot_before:
|
31 |
+
fig, axs = plt.subplots(1, 2, figsize=(10, 5))
|
32 |
+
axs[0].imshow(img1[:,:,::-1])
|
33 |
+
axs[0].axis('off')
|
34 |
+
axs[1].imshow(img2[:,:,::-1])
|
35 |
+
axs[1].axis('off')
|
36 |
+
plt.show()
|
37 |
+
|
38 |
+
# Do the swap
|
39 |
+
face1 = app.get(img1)[0]
|
40 |
+
face2 = app.get(img2)[0]
|
41 |
+
|
42 |
+
img1_ = img1.copy()
|
43 |
+
img2_ = img2.copy()
|
44 |
+
if plot_after:
|
45 |
+
img1_ = swapper.get(img1_, face1, face2, paste_back=True)
|
46 |
+
img2_ = swapper.get(img2_, face2, face1, paste_back=True)
|
47 |
+
if enhance:
|
48 |
+
cpu_warning(device)
|
49 |
+
model, model_runner = load_face_enhancer_model(enhancer,device)
|
50 |
+
img1_ = model_runner(img1_, model)
|
51 |
+
img2_ = model_runner(img2_, model)
|
52 |
+
fig, axs = plt.subplots(1, 2, figsize=(10, 5))
|
53 |
+
axs[0].imshow(img1_[:,:,::-1])
|
54 |
+
axs[0].axis('off')
|
55 |
+
axs[1].imshow(img2_[:,:,::-1])
|
56 |
+
axs[1].axis('off')
|
57 |
+
plt.show()
|
58 |
+
return img1_, img2_
|
59 |
+
|
60 |
+
def swap_n_show_same_img(img1_fn,
|
61 |
+
app, swapper,
|
62 |
+
plot_before=False,
|
63 |
+
plot_after=True, enhance=False, enhancer='REAL-ESRGAN 2x',device="cpu"):
|
64 |
+
|
65 |
+
validate_image(img1_fn)
|
66 |
+
img1 = cv2.imread(img1_fn)
|
67 |
+
|
68 |
+
if plot_before:
|
69 |
+
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
|
70 |
+
ax.imshow(img1[:,:,::-1])
|
71 |
+
ax.axis('off')
|
72 |
+
plt.show()
|
73 |
+
|
74 |
+
# Do the swap
|
75 |
+
faces = app.get(img1)
|
76 |
+
face1, face2 = faces[0], faces[1]
|
77 |
+
|
78 |
+
img1_ = img1.copy()
|
79 |
+
if plot_after:
|
80 |
+
img1_ = swapper.get(img1_, face1, face2, paste_back=True)
|
81 |
+
img1_ = swapper.get(img1_, face2, face1, paste_back=True)
|
82 |
+
if enhance:
|
83 |
+
cpu_warning(device)
|
84 |
+
model, model_runner = load_face_enhancer_model(enhancer,device)
|
85 |
+
img1_ = model_runner(img1_, model)
|
86 |
+
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
|
87 |
+
ax.imshow(img1_[:,:,::-1])
|
88 |
+
ax.axis('off')
|
89 |
+
plt.show()
|
90 |
+
return img1_
|
91 |
+
|
92 |
+
def swap_face_single(img1_fn, img2_fn, app, swapper,
|
93 |
+
plot_before=False, plot_after=True, enhance=False, enhancer='REAL-ESRGAN 2x',device="cpu"):
|
94 |
+
|
95 |
+
validate_image(img1_fn)
|
96 |
+
validate_image(img2_fn)
|
97 |
+
|
98 |
+
img1 = cv2.imread(img1_fn)
|
99 |
+
img2 = cv2.imread(img2_fn)
|
100 |
+
|
101 |
+
if plot_before:
|
102 |
+
axs = plt.subplots(1, 2, figsize=(10, 5))
|
103 |
+
axs[0].imshow(img1[:,:,::-1])
|
104 |
+
axs[0].axis('off')
|
105 |
+
axs[1].imshow(img2[:,:,::-1])
|
106 |
+
axs[1].axis('off')
|
107 |
+
plt.show()
|
108 |
+
|
109 |
+
# Do the swap
|
110 |
+
face1 = app.get(img1)[0]
|
111 |
+
face2 = app.get(img2)[0]
|
112 |
+
|
113 |
+
img1_ = img1.copy()
|
114 |
+
if plot_after:
|
115 |
+
img1_ = swapper.get(img1_, face1, face2, paste_back=True)
|
116 |
+
if enhance:
|
117 |
+
cpu_warning(device)
|
118 |
+
model, model_runner = load_face_enhancer_model(enhancer,device)
|
119 |
+
img1_ = model_runner(img1_, model)
|
120 |
+
# Save the image
|
121 |
+
output_fn = os.path.join('outputs', os.path.basename(img1_fn))
|
122 |
+
cv2.imwrite(output_fn, img1_)
|
123 |
+
print(f'Image saved to {output_fn}')
|
124 |
+
return img1_
|
125 |
+
def fine_face_swap(img1_fn, img2_fn, app, swapper,enhance=False, enhancer='REAL-ESRGAN 2x',device="cpu"):
|
126 |
+
img1 = cv2.imread(img1_fn)
|
127 |
+
facesimg1 = app.get(img1)
|
128 |
+
total_faces_img1 = len(facesimg1)
|
129 |
+
if total_faces_img1 > 1:
|
130 |
+
print(f'{total_faces_img1} faces detected')
|
131 |
+
fig, axs = plt.subplots(1, total_faces_img1, figsize=(12, 5))
|
132 |
+
for i, face in enumerate(facesimg1):
|
133 |
+
bbox = face['bbox']
|
134 |
+
bbox = [int(b) for b in bbox]
|
135 |
+
axs[i].imshow(img1[bbox[1]:bbox[3],bbox[0]:bbox[2],::-1])
|
136 |
+
axs[i].axis('off')
|
137 |
+
axs[i].set_title(f'Face {i+1}')
|
138 |
+
plt.suptitle('Select a face to swap')
|
139 |
+
plt.show()
|
140 |
+
else:
|
141 |
+
print(f'{total_faces_img1} face detected')
|
142 |
+
bbox = facesimg1[0]['bbox']
|
143 |
+
bbox = [int(b) for b in bbox]
|
144 |
+
plt.imshow(img1[bbox[1]:bbox[3],bbox[0]:bbox[2],::-1])
|
145 |
+
plt.axis('off')
|
146 |
+
plt.title('Face 1')
|
147 |
+
plt.show()
|
148 |
+
|
149 |
+
# Select a face from img1
|
150 |
+
face_idximg1 = int(input(f'Enter face number (1-{total_faces_img1}): '))
|
151 |
+
if face_idximg1 < 1 or face_idximg1 > total_faces_img1:
|
152 |
+
raise ValueError(f'Invalid face number {face_idximg1}')
|
153 |
+
face = facesimg1[face_idximg1-1]
|
154 |
+
bbox = face['bbox']
|
155 |
+
bbox = [int(b) for b in bbox]
|
156 |
+
face_img = img1[bbox[1]:bbox[3],bbox[0]:bbox[2],::-1]
|
157 |
+
plt.imshow(face_img)
|
158 |
+
plt.axis('off')
|
159 |
+
plt.title(f'Face {face_idximg1}')
|
160 |
+
plt.suptitle('Selected face')
|
161 |
+
plt.show()
|
162 |
+
|
163 |
+
img2 = cv2.imread(img2_fn)
|
164 |
+
facesimg2 = app.get(img2)
|
165 |
+
total_faces_img2 = len(facesimg2)
|
166 |
+
if total_faces_img2 > 1:
|
167 |
+
print(f'{total_faces_img2} faces detected')
|
168 |
+
fig, axs = plt.subplots(1, total_faces_img2, figsize=(12, 5))
|
169 |
+
for i, face in enumerate(facesimg2):
|
170 |
+
bbox = face['bbox']
|
171 |
+
bbox = [int(b) for b in bbox]
|
172 |
+
axs[i].imshow(img2[bbox[1]:bbox[3],bbox[0]:bbox[2],::-1])
|
173 |
+
axs[i].axis('off')
|
174 |
+
axs[i].set_title(f'Face {i+1}')
|
175 |
+
plt.suptitle('Select a face to swap')
|
176 |
+
plt.show()
|
177 |
+
else:
|
178 |
+
print(f'{total_faces_img2} face detected')
|
179 |
+
bbox = facesimg2[0]['bbox']
|
180 |
+
bbox = [int(b) for b in bbox]
|
181 |
+
plt.imshow(img2[bbox[1]:bbox[3],bbox[0]:bbox[2],::-1])
|
182 |
+
plt.axis('off')
|
183 |
+
plt.title('Face 1')
|
184 |
+
plt.show()
|
185 |
+
|
186 |
+
# Select a face from img2
|
187 |
+
face_idximg2 = int(input(f'Enter face number (1-{total_faces_img2}): '))
|
188 |
+
if face_idximg2 < 1 or face_idximg2 > total_faces_img2:
|
189 |
+
raise ValueError(f'Invalid face number {face_idximg2}')
|
190 |
+
face = facesimg2[face_idximg2-1]
|
191 |
+
bbox = face['bbox']
|
192 |
+
bbox = [int(b) for b in bbox]
|
193 |
+
face_img = img2[bbox[1]:bbox[3],bbox[0]:bbox[2],::-1]
|
194 |
+
plt.imshow(face_img)
|
195 |
+
plt.axis('off')
|
196 |
+
plt.title(f'Face {face_idximg2}')
|
197 |
+
plt.suptitle('Selected face')
|
198 |
+
plt.show()
|
199 |
+
|
200 |
+
# source face
|
201 |
+
face1 = app.get(img1)[face_idximg1-1]
|
202 |
+
face2 = app.get(img2)[face_idximg2-1]
|
203 |
+
|
204 |
+
img1_ = img1.copy()
|
205 |
+
img1_ = swapper.get(img1_, face1, face2, paste_back=True)
|
206 |
+
if enhance:
|
207 |
+
cpu_warning(device)
|
208 |
+
model, model_runner = load_face_enhancer_model(enhancer,device)
|
209 |
+
img1_ = model_runner(img1_, model)
|
210 |
+
# Save the image
|
211 |
+
output_fn = os.path.join('outputs', os.path.basename(img1_fn))
|
212 |
+
cv2.imwrite(output_fn, img1_)
|
213 |
+
print(f'Image saved to {output_fn}')
|
214 |
+
return img1_
|
images/Anushka.jpg
ADDED
![]() |
images/keerthi.jpg
ADDED
![]() |
images/result.png
ADDED
![]() |
images/swapseed.png
ADDED
![]() |
inswapper/.gitkeep
ADDED
File without changes
|
main.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import matplotlib.pyplot as plt
|
3 |
+
import gdown
|
4 |
+
import insightface
|
5 |
+
from insightface.app import FaceAnalysis
|
6 |
+
from insightface.data import get_image as ins_get_image
|
7 |
+
from faceswap import swap_n_show, swap_n_show_same_img, swap_face_single,fine_face_swap
|
8 |
+
|
9 |
+
app = FaceAnalysis(name='buffalo_l')
|
10 |
+
app.prepare(ctx_id=0, det_size=(640, 640))
|
11 |
+
|
12 |
+
# Download 'inswapper_128.onnx' file using gdown
|
13 |
+
model_url = 'https://drive.google.com/uc?id=1HvZ4MAtzlY74Dk4ASGIS9L6Rg5oZdqvu'
|
14 |
+
model_output_path = 'inswapper/inswapper_128.onnx'
|
15 |
+
if not os.path.exists(model_output_path):
|
16 |
+
gdown.download(model_url, model_output_path, quiet=False)
|
17 |
+
|
18 |
+
swapper = insightface.model_zoo.get_model('inswapper/inswapper_128.onnx', download=False, download_zip=False)
|
19 |
+
|
20 |
+
# Load images
|
21 |
+
img1_fn = 'images/Anushka.jpg'
|
22 |
+
img2_fn = 'images/keerthi.jpg'
|
23 |
+
|
24 |
+
# Swap faces between two images
|
25 |
+
# swap_n_show(img1_fn, img2_fn, app, swapper)
|
26 |
+
|
27 |
+
# Swap faces within the same image
|
28 |
+
# swap_n_show_same_img(img1_fn, app, swapper)
|
29 |
+
|
30 |
+
# Add face to an image
|
31 |
+
swap_face_single(img1_fn, img2_fn, app, swapper, enhance=True, enhancer='REAL-ESRGAN 2x',device="cpu")
|
32 |
+
|
33 |
+
# Fine face swapper
|
34 |
+
fine_face_swap(img1_fn, img2_fn, app, swapper, enhance=True, enhancer='REAL-ESRGAN 2x',device="cpu")
|
outputs/bramhi.jpg
ADDED
![]() |
pretrained_models/.gitkeep
ADDED
File without changes
|
requirements.txt
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
cv
|
2 |
+
matplotlib
|
3 |
+
gdown
|
4 |
+
gradio>=3.33.1
|
5 |
+
insightface==0.7.3
|
6 |
+
moviepy>=1.0.3
|
7 |
+
numpy
|
8 |
+
opencv-python>=4.7.0.72
|
9 |
+
opencv-python-headless>=4.7.0.72
|
10 |
+
onnx==1.14.0
|
11 |
+
onnxruntime==1.15.0
|
12 |
+
gfpgan==1.3.8
|
13 |
+
timm==0.9.2
|
14 |
+
torch==2.0.1
|
requirements_gpu.txt
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
cv
|
2 |
+
matplotlib
|
3 |
+
gdown
|
4 |
+
gradio>=3.33.1
|
5 |
+
insightface==0.7.3
|
6 |
+
moviepy>=1.0.3
|
7 |
+
numpy
|
8 |
+
opencv-python>=4.7.0.72
|
9 |
+
opencv-python-headless>=4.7.0.72
|
10 |
+
onnx==1.14.0
|
11 |
+
onnxruntime-gpu==1.15.0
|
12 |
+
gfpgan==1.3.8
|
13 |
+
timm==0.9.2
|
14 |
+
torch==2.0.1
|
upscaler/RealESRGAN/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from .model import RealESRGAN
|
upscaler/RealESRGAN/__pycache__/__init__.cpython-311.pyc
ADDED
Binary file (219 Bytes). View file
|
|
upscaler/RealESRGAN/__pycache__/arch_utils.cpython-311.pyc
ADDED
Binary file (11.5 kB). View file
|
|
upscaler/RealESRGAN/__pycache__/model.cpython-311.pyc
ADDED
Binary file (5.89 kB). View file
|
|
upscaler/RealESRGAN/__pycache__/rrdbnet_arch.cpython-311.pyc
ADDED
Binary file (8.19 kB). View file
|
|
upscaler/RealESRGAN/__pycache__/utils.cpython-311.pyc
ADDED
Binary file (6.29 kB). View file
|
|
upscaler/RealESRGAN/arch_utils.py
ADDED
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
import torch
|
3 |
+
from torch import nn as nn
|
4 |
+
from torch.nn import functional as F
|
5 |
+
from torch.nn import init as init
|
6 |
+
from torch.nn.modules.batchnorm import _BatchNorm
|
7 |
+
|
8 |
+
@torch.no_grad()
|
9 |
+
def default_init_weights(module_list, scale=1, bias_fill=0, **kwargs):
|
10 |
+
"""Initialize network weights.
|
11 |
+
|
12 |
+
Args:
|
13 |
+
module_list (list[nn.Module] | nn.Module): Modules to be initialized.
|
14 |
+
scale (float): Scale initialized weights, especially for residual
|
15 |
+
blocks. Default: 1.
|
16 |
+
bias_fill (float): The value to fill bias. Default: 0
|
17 |
+
kwargs (dict): Other arguments for initialization function.
|
18 |
+
"""
|
19 |
+
if not isinstance(module_list, list):
|
20 |
+
module_list = [module_list]
|
21 |
+
for module in module_list:
|
22 |
+
for m in module.modules():
|
23 |
+
if isinstance(m, nn.Conv2d):
|
24 |
+
init.kaiming_normal_(m.weight, **kwargs)
|
25 |
+
m.weight.data *= scale
|
26 |
+
if m.bias is not None:
|
27 |
+
m.bias.data.fill_(bias_fill)
|
28 |
+
elif isinstance(m, nn.Linear):
|
29 |
+
init.kaiming_normal_(m.weight, **kwargs)
|
30 |
+
m.weight.data *= scale
|
31 |
+
if m.bias is not None:
|
32 |
+
m.bias.data.fill_(bias_fill)
|
33 |
+
elif isinstance(m, _BatchNorm):
|
34 |
+
init.constant_(m.weight, 1)
|
35 |
+
if m.bias is not None:
|
36 |
+
m.bias.data.fill_(bias_fill)
|
37 |
+
|
38 |
+
|
39 |
+
def make_layer(basic_block, num_basic_block, **kwarg):
|
40 |
+
"""Make layers by stacking the same blocks.
|
41 |
+
|
42 |
+
Args:
|
43 |
+
basic_block (nn.module): nn.module class for basic block.
|
44 |
+
num_basic_block (int): number of blocks.
|
45 |
+
|
46 |
+
Returns:
|
47 |
+
nn.Sequential: Stacked blocks in nn.Sequential.
|
48 |
+
"""
|
49 |
+
layers = []
|
50 |
+
for _ in range(num_basic_block):
|
51 |
+
layers.append(basic_block(**kwarg))
|
52 |
+
return nn.Sequential(*layers)
|
53 |
+
|
54 |
+
|
55 |
+
class ResidualBlockNoBN(nn.Module):
|
56 |
+
"""Residual block without BN.
|
57 |
+
|
58 |
+
It has a style of:
|
59 |
+
---Conv-ReLU-Conv-+-
|
60 |
+
|________________|
|
61 |
+
|
62 |
+
Args:
|
63 |
+
num_feat (int): Channel number of intermediate features.
|
64 |
+
Default: 64.
|
65 |
+
res_scale (float): Residual scale. Default: 1.
|
66 |
+
pytorch_init (bool): If set to True, use pytorch default init,
|
67 |
+
otherwise, use default_init_weights. Default: False.
|
68 |
+
"""
|
69 |
+
|
70 |
+
def __init__(self, num_feat=64, res_scale=1, pytorch_init=False):
|
71 |
+
super(ResidualBlockNoBN, self).__init__()
|
72 |
+
self.res_scale = res_scale
|
73 |
+
self.conv1 = nn.Conv2d(num_feat, num_feat, 3, 1, 1, bias=True)
|
74 |
+
self.conv2 = nn.Conv2d(num_feat, num_feat, 3, 1, 1, bias=True)
|
75 |
+
self.relu = nn.ReLU(inplace=True)
|
76 |
+
|
77 |
+
if not pytorch_init:
|
78 |
+
default_init_weights([self.conv1, self.conv2], 0.1)
|
79 |
+
|
80 |
+
def forward(self, x):
|
81 |
+
identity = x
|
82 |
+
out = self.conv2(self.relu(self.conv1(x)))
|
83 |
+
return identity + out * self.res_scale
|
84 |
+
|
85 |
+
|
86 |
+
class Upsample(nn.Sequential):
|
87 |
+
"""Upsample module.
|
88 |
+
|
89 |
+
Args:
|
90 |
+
scale (int): Scale factor. Supported scales: 2^n and 3.
|
91 |
+
num_feat (int): Channel number of intermediate features.
|
92 |
+
"""
|
93 |
+
|
94 |
+
def __init__(self, scale, num_feat):
|
95 |
+
m = []
|
96 |
+
if (scale & (scale - 1)) == 0: # scale = 2^n
|
97 |
+
for _ in range(int(math.log(scale, 2))):
|
98 |
+
m.append(nn.Conv2d(num_feat, 4 * num_feat, 3, 1, 1))
|
99 |
+
m.append(nn.PixelShuffle(2))
|
100 |
+
elif scale == 3:
|
101 |
+
m.append(nn.Conv2d(num_feat, 9 * num_feat, 3, 1, 1))
|
102 |
+
m.append(nn.PixelShuffle(3))
|
103 |
+
else:
|
104 |
+
raise ValueError(f'scale {scale} is not supported. ' 'Supported scales: 2^n and 3.')
|
105 |
+
super(Upsample, self).__init__(*m)
|
106 |
+
|
107 |
+
|
108 |
+
def flow_warp(x, flow, interp_mode='bilinear', padding_mode='zeros', align_corners=True):
|
109 |
+
"""Warp an image or feature map with optical flow.
|
110 |
+
|
111 |
+
Args:
|
112 |
+
x (Tensor): Tensor with size (n, c, h, w).
|
113 |
+
flow (Tensor): Tensor with size (n, h, w, 2), normal value.
|
114 |
+
interp_mode (str): 'nearest' or 'bilinear'. Default: 'bilinear'.
|
115 |
+
padding_mode (str): 'zeros' or 'border' or 'reflection'.
|
116 |
+
Default: 'zeros'.
|
117 |
+
align_corners (bool): Before pytorch 1.3, the default value is
|
118 |
+
align_corners=True. After pytorch 1.3, the default value is
|
119 |
+
align_corners=False. Here, we use the True as default.
|
120 |
+
|
121 |
+
Returns:
|
122 |
+
Tensor: Warped image or feature map.
|
123 |
+
"""
|
124 |
+
assert x.size()[-2:] == flow.size()[1:3]
|
125 |
+
_, _, h, w = x.size()
|
126 |
+
# create mesh grid
|
127 |
+
grid_y, grid_x = torch.meshgrid(torch.arange(0, h).type_as(x), torch.arange(0, w).type_as(x))
|
128 |
+
grid = torch.stack((grid_x, grid_y), 2).float() # W(x), H(y), 2
|
129 |
+
grid.requires_grad = False
|
130 |
+
|
131 |
+
vgrid = grid + flow
|
132 |
+
# scale grid to [-1,1]
|
133 |
+
vgrid_x = 2.0 * vgrid[:, :, :, 0] / max(w - 1, 1) - 1.0
|
134 |
+
vgrid_y = 2.0 * vgrid[:, :, :, 1] / max(h - 1, 1) - 1.0
|
135 |
+
vgrid_scaled = torch.stack((vgrid_x, vgrid_y), dim=3)
|
136 |
+
output = F.grid_sample(x, vgrid_scaled, mode=interp_mode, padding_mode=padding_mode, align_corners=align_corners)
|
137 |
+
|
138 |
+
# TODO, what if align_corners=False
|
139 |
+
return output
|
140 |
+
|
141 |
+
|
142 |
+
def resize_flow(flow, size_type, sizes, interp_mode='bilinear', align_corners=False):
|
143 |
+
"""Resize a flow according to ratio or shape.
|
144 |
+
|
145 |
+
Args:
|
146 |
+
flow (Tensor): Precomputed flow. shape [N, 2, H, W].
|
147 |
+
size_type (str): 'ratio' or 'shape'.
|
148 |
+
sizes (list[int | float]): the ratio for resizing or the final output
|
149 |
+
shape.
|
150 |
+
1) The order of ratio should be [ratio_h, ratio_w]. For
|
151 |
+
downsampling, the ratio should be smaller than 1.0 (i.e., ratio
|
152 |
+
< 1.0). For upsampling, the ratio should be larger than 1.0 (i.e.,
|
153 |
+
ratio > 1.0).
|
154 |
+
2) The order of output_size should be [out_h, out_w].
|
155 |
+
interp_mode (str): The mode of interpolation for resizing.
|
156 |
+
Default: 'bilinear'.
|
157 |
+
align_corners (bool): Whether align corners. Default: False.
|
158 |
+
|
159 |
+
Returns:
|
160 |
+
Tensor: Resized flow.
|
161 |
+
"""
|
162 |
+
_, _, flow_h, flow_w = flow.size()
|
163 |
+
if size_type == 'ratio':
|
164 |
+
output_h, output_w = int(flow_h * sizes[0]), int(flow_w * sizes[1])
|
165 |
+
elif size_type == 'shape':
|
166 |
+
output_h, output_w = sizes[0], sizes[1]
|
167 |
+
else:
|
168 |
+
raise ValueError(f'Size type should be ratio or shape, but got type {size_type}.')
|
169 |
+
|
170 |
+
input_flow = flow.clone()
|
171 |
+
ratio_h = output_h / flow_h
|
172 |
+
ratio_w = output_w / flow_w
|
173 |
+
input_flow[:, 0, :, :] *= ratio_w
|
174 |
+
input_flow[:, 1, :, :] *= ratio_h
|
175 |
+
resized_flow = F.interpolate(
|
176 |
+
input=input_flow, size=(output_h, output_w), mode=interp_mode, align_corners=align_corners)
|
177 |
+
return resized_flow
|
178 |
+
|
179 |
+
|
180 |
+
# TODO: may write a cpp file
|
181 |
+
def pixel_unshuffle(x, scale):
|
182 |
+
""" Pixel unshuffle.
|
183 |
+
|
184 |
+
Args:
|
185 |
+
x (Tensor): Input feature with shape (b, c, hh, hw).
|
186 |
+
scale (int): Downsample ratio.
|
187 |
+
|
188 |
+
Returns:
|
189 |
+
Tensor: the pixel unshuffled feature.
|
190 |
+
"""
|
191 |
+
b, c, hh, hw = x.size()
|
192 |
+
out_channel = c * (scale**2)
|
193 |
+
assert hh % scale == 0 and hw % scale == 0
|
194 |
+
h = hh // scale
|
195 |
+
w = hw // scale
|
196 |
+
x_view = x.view(b, c, h, scale, w, scale)
|
197 |
+
return x_view.permute(0, 1, 3, 5, 2, 4).reshape(b, out_channel, h, w)
|
upscaler/RealESRGAN/model.py
ADDED
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import torch
|
3 |
+
from torch.nn import functional as F
|
4 |
+
from PIL import Image
|
5 |
+
import numpy as np
|
6 |
+
import cv2
|
7 |
+
|
8 |
+
from .rrdbnet_arch import RRDBNet
|
9 |
+
from .utils import pad_reflect, split_image_into_overlapping_patches, stich_together, \
|
10 |
+
unpad_image
|
11 |
+
|
12 |
+
|
13 |
+
HF_MODELS = {
|
14 |
+
2: dict(
|
15 |
+
repo_id='sberbank-ai/Real-ESRGAN',
|
16 |
+
filename='RealESRGAN_x2.pth',
|
17 |
+
),
|
18 |
+
4: dict(
|
19 |
+
repo_id='sberbank-ai/Real-ESRGAN',
|
20 |
+
filename='RealESRGAN_x4.pth',
|
21 |
+
),
|
22 |
+
8: dict(
|
23 |
+
repo_id='sberbank-ai/Real-ESRGAN',
|
24 |
+
filename='RealESRGAN_x8.pth',
|
25 |
+
),
|
26 |
+
}
|
27 |
+
|
28 |
+
|
29 |
+
class RealESRGAN:
|
30 |
+
def __init__(self, device, scale=4):
|
31 |
+
self.device = device
|
32 |
+
self.scale = scale
|
33 |
+
self.model = RRDBNet(
|
34 |
+
num_in_ch=3, num_out_ch=3, num_feat=64,
|
35 |
+
num_block=23, num_grow_ch=32, scale=scale
|
36 |
+
)
|
37 |
+
|
38 |
+
def load_weights(self, model_path, download=True):
|
39 |
+
if not os.path.exists(model_path) and download:
|
40 |
+
from huggingface_hub import hf_hub_url, cached_download
|
41 |
+
assert self.scale in [2,4,8], 'You can download models only with scales: 2, 4, 8'
|
42 |
+
config = HF_MODELS[self.scale]
|
43 |
+
cache_dir = os.path.dirname(model_path)
|
44 |
+
local_filename = os.path.basename(model_path)
|
45 |
+
config_file_url = hf_hub_url(repo_id=config['repo_id'], filename=config['filename'])
|
46 |
+
cached_download(config_file_url, cache_dir=cache_dir, force_filename=local_filename)
|
47 |
+
print('Weights downloaded to:', os.path.join(cache_dir, local_filename))
|
48 |
+
|
49 |
+
loadnet = torch.load(model_path)
|
50 |
+
if 'params' in loadnet:
|
51 |
+
self.model.load_state_dict(loadnet['params'], strict=True)
|
52 |
+
elif 'params_ema' in loadnet:
|
53 |
+
self.model.load_state_dict(loadnet['params_ema'], strict=True)
|
54 |
+
else:
|
55 |
+
self.model.load_state_dict(loadnet, strict=True)
|
56 |
+
self.model.eval()
|
57 |
+
self.model.to(self.device)
|
58 |
+
|
59 |
+
@torch.cuda.amp.autocast()
|
60 |
+
def predict(self, lr_image, batch_size=4, patches_size=192,
|
61 |
+
padding=24, pad_size=15):
|
62 |
+
scale = self.scale
|
63 |
+
device = self.device
|
64 |
+
lr_image = np.array(lr_image)
|
65 |
+
lr_image = pad_reflect(lr_image, pad_size)
|
66 |
+
|
67 |
+
patches, p_shape = split_image_into_overlapping_patches(
|
68 |
+
lr_image, patch_size=patches_size, padding_size=padding
|
69 |
+
)
|
70 |
+
img = torch.FloatTensor(patches/255).permute((0,3,1,2)).to(device).detach()
|
71 |
+
|
72 |
+
with torch.no_grad():
|
73 |
+
res = self.model(img[0:batch_size])
|
74 |
+
for i in range(batch_size, img.shape[0], batch_size):
|
75 |
+
res = torch.cat((res, self.model(img[i:i+batch_size])), 0)
|
76 |
+
|
77 |
+
sr_image = res.permute((0,2,3,1)).clamp_(0, 1).cpu()
|
78 |
+
np_sr_image = sr_image.numpy()
|
79 |
+
|
80 |
+
padded_size_scaled = tuple(np.multiply(p_shape[0:2], scale)) + (3,)
|
81 |
+
scaled_image_shape = tuple(np.multiply(lr_image.shape[0:2], scale)) + (3,)
|
82 |
+
np_sr_image = stich_together(
|
83 |
+
np_sr_image, padded_image_shape=padded_size_scaled,
|
84 |
+
target_shape=scaled_image_shape, padding_size=padding * scale
|
85 |
+
)
|
86 |
+
sr_img = (np_sr_image*255).astype(np.uint8)
|
87 |
+
sr_img = unpad_image(sr_img, pad_size*scale)
|
88 |
+
#sr_img = Image.fromarray(sr_img)
|
89 |
+
|
90 |
+
return sr_img
|
upscaler/RealESRGAN/rrdbnet_arch.py
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from torch import nn as nn
|
3 |
+
from torch.nn import functional as F
|
4 |
+
|
5 |
+
from .arch_utils import default_init_weights, make_layer, pixel_unshuffle
|
6 |
+
|
7 |
+
|
8 |
+
class ResidualDenseBlock(nn.Module):
|
9 |
+
"""Residual Dense Block.
|
10 |
+
|
11 |
+
Used in RRDB block in ESRGAN.
|
12 |
+
|
13 |
+
Args:
|
14 |
+
num_feat (int): Channel number of intermediate features.
|
15 |
+
num_grow_ch (int): Channels for each growth.
|
16 |
+
"""
|
17 |
+
|
18 |
+
def __init__(self, num_feat=64, num_grow_ch=32):
|
19 |
+
super(ResidualDenseBlock, self).__init__()
|
20 |
+
self.conv1 = nn.Conv2d(num_feat, num_grow_ch, 3, 1, 1)
|
21 |
+
self.conv2 = nn.Conv2d(num_feat + num_grow_ch, num_grow_ch, 3, 1, 1)
|
22 |
+
self.conv3 = nn.Conv2d(num_feat + 2 * num_grow_ch, num_grow_ch, 3, 1, 1)
|
23 |
+
self.conv4 = nn.Conv2d(num_feat + 3 * num_grow_ch, num_grow_ch, 3, 1, 1)
|
24 |
+
self.conv5 = nn.Conv2d(num_feat + 4 * num_grow_ch, num_feat, 3, 1, 1)
|
25 |
+
|
26 |
+
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
|
27 |
+
|
28 |
+
# initialization
|
29 |
+
default_init_weights([self.conv1, self.conv2, self.conv3, self.conv4, self.conv5], 0.1)
|
30 |
+
|
31 |
+
def forward(self, x):
|
32 |
+
x1 = self.lrelu(self.conv1(x))
|
33 |
+
x2 = self.lrelu(self.conv2(torch.cat((x, x1), 1)))
|
34 |
+
x3 = self.lrelu(self.conv3(torch.cat((x, x1, x2), 1)))
|
35 |
+
x4 = self.lrelu(self.conv4(torch.cat((x, x1, x2, x3), 1)))
|
36 |
+
x5 = self.conv5(torch.cat((x, x1, x2, x3, x4), 1))
|
37 |
+
# Emperically, we use 0.2 to scale the residual for better performance
|
38 |
+
return x5 * 0.2 + x
|
39 |
+
|
40 |
+
|
41 |
+
class RRDB(nn.Module):
|
42 |
+
"""Residual in Residual Dense Block.
|
43 |
+
|
44 |
+
Used in RRDB-Net in ESRGAN.
|
45 |
+
|
46 |
+
Args:
|
47 |
+
num_feat (int): Channel number of intermediate features.
|
48 |
+
num_grow_ch (int): Channels for each growth.
|
49 |
+
"""
|
50 |
+
|
51 |
+
def __init__(self, num_feat, num_grow_ch=32):
|
52 |
+
super(RRDB, self).__init__()
|
53 |
+
self.rdb1 = ResidualDenseBlock(num_feat, num_grow_ch)
|
54 |
+
self.rdb2 = ResidualDenseBlock(num_feat, num_grow_ch)
|
55 |
+
self.rdb3 = ResidualDenseBlock(num_feat, num_grow_ch)
|
56 |
+
|
57 |
+
def forward(self, x):
|
58 |
+
out = self.rdb1(x)
|
59 |
+
out = self.rdb2(out)
|
60 |
+
out = self.rdb3(out)
|
61 |
+
# Emperically, we use 0.2 to scale the residual for better performance
|
62 |
+
return out * 0.2 + x
|
63 |
+
|
64 |
+
|
65 |
+
class RRDBNet(nn.Module):
|
66 |
+
"""Networks consisting of Residual in Residual Dense Block, which is used
|
67 |
+
in ESRGAN.
|
68 |
+
|
69 |
+
ESRGAN: Enhanced Super-Resolution Generative Adversarial Networks.
|
70 |
+
|
71 |
+
We extend ESRGAN for scale x2 and scale x1.
|
72 |
+
Note: This is one option for scale 1, scale 2 in RRDBNet.
|
73 |
+
We first employ the pixel-unshuffle (an inverse operation of pixelshuffle to reduce the spatial size
|
74 |
+
and enlarge the channel size before feeding inputs into the main ESRGAN architecture.
|
75 |
+
|
76 |
+
Args:
|
77 |
+
num_in_ch (int): Channel number of inputs.
|
78 |
+
num_out_ch (int): Channel number of outputs.
|
79 |
+
num_feat (int): Channel number of intermediate features.
|
80 |
+
Default: 64
|
81 |
+
num_block (int): Block number in the trunk network. Defaults: 23
|
82 |
+
num_grow_ch (int): Channels for each growth. Default: 32.
|
83 |
+
"""
|
84 |
+
|
85 |
+
def __init__(self, num_in_ch, num_out_ch, scale=4, num_feat=64, num_block=23, num_grow_ch=32):
|
86 |
+
super(RRDBNet, self).__init__()
|
87 |
+
self.scale = scale
|
88 |
+
if scale == 2:
|
89 |
+
num_in_ch = num_in_ch * 4
|
90 |
+
elif scale == 1:
|
91 |
+
num_in_ch = num_in_ch * 16
|
92 |
+
self.conv_first = nn.Conv2d(num_in_ch, num_feat, 3, 1, 1)
|
93 |
+
self.body = make_layer(RRDB, num_block, num_feat=num_feat, num_grow_ch=num_grow_ch)
|
94 |
+
self.conv_body = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
95 |
+
# upsample
|
96 |
+
self.conv_up1 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
97 |
+
self.conv_up2 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
98 |
+
if scale == 8:
|
99 |
+
self.conv_up3 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
100 |
+
self.conv_hr = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
101 |
+
self.conv_last = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1)
|
102 |
+
|
103 |
+
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
|
104 |
+
|
105 |
+
def forward(self, x):
|
106 |
+
if self.scale == 2:
|
107 |
+
feat = pixel_unshuffle(x, scale=2)
|
108 |
+
elif self.scale == 1:
|
109 |
+
feat = pixel_unshuffle(x, scale=4)
|
110 |
+
else:
|
111 |
+
feat = x
|
112 |
+
feat = self.conv_first(feat)
|
113 |
+
body_feat = self.conv_body(self.body(feat))
|
114 |
+
feat = feat + body_feat
|
115 |
+
# upsample
|
116 |
+
feat = self.lrelu(self.conv_up1(F.interpolate(feat, scale_factor=2, mode='nearest')))
|
117 |
+
feat = self.lrelu(self.conv_up2(F.interpolate(feat, scale_factor=2, mode='nearest')))
|
118 |
+
if self.scale == 8:
|
119 |
+
feat = self.lrelu(self.conv_up3(F.interpolate(feat, scale_factor=2, mode='nearest')))
|
120 |
+
out = self.conv_last(self.lrelu(self.conv_hr(feat)))
|
121 |
+
return out
|
upscaler/RealESRGAN/utils.py
ADDED
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import torch
|
3 |
+
from PIL import Image
|
4 |
+
import os
|
5 |
+
import io
|
6 |
+
|
7 |
+
def pad_reflect(image, pad_size):
|
8 |
+
imsize = image.shape
|
9 |
+
height, width = imsize[:2]
|
10 |
+
new_img = np.zeros([height+pad_size*2, width+pad_size*2, imsize[2]]).astype(np.uint8)
|
11 |
+
new_img[pad_size:-pad_size, pad_size:-pad_size, :] = image
|
12 |
+
|
13 |
+
new_img[0:pad_size, pad_size:-pad_size, :] = np.flip(image[0:pad_size, :, :], axis=0) #top
|
14 |
+
new_img[-pad_size:, pad_size:-pad_size, :] = np.flip(image[-pad_size:, :, :], axis=0) #bottom
|
15 |
+
new_img[:, 0:pad_size, :] = np.flip(new_img[:, pad_size:pad_size*2, :], axis=1) #left
|
16 |
+
new_img[:, -pad_size:, :] = np.flip(new_img[:, -pad_size*2:-pad_size, :], axis=1) #right
|
17 |
+
|
18 |
+
return new_img
|
19 |
+
|
20 |
+
def unpad_image(image, pad_size):
|
21 |
+
return image[pad_size:-pad_size, pad_size:-pad_size, :]
|
22 |
+
|
23 |
+
|
24 |
+
def process_array(image_array, expand=True):
|
25 |
+
""" Process a 3-dimensional array into a scaled, 4 dimensional batch of size 1. """
|
26 |
+
|
27 |
+
image_batch = image_array / 255.0
|
28 |
+
if expand:
|
29 |
+
image_batch = np.expand_dims(image_batch, axis=0)
|
30 |
+
return image_batch
|
31 |
+
|
32 |
+
|
33 |
+
def process_output(output_tensor):
|
34 |
+
""" Transforms the 4-dimensional output tensor into a suitable image format. """
|
35 |
+
|
36 |
+
sr_img = output_tensor.clip(0, 1) * 255
|
37 |
+
sr_img = np.uint8(sr_img)
|
38 |
+
return sr_img
|
39 |
+
|
40 |
+
|
41 |
+
def pad_patch(image_patch, padding_size, channel_last=True):
|
42 |
+
""" Pads image_patch with with padding_size edge values. """
|
43 |
+
|
44 |
+
if channel_last:
|
45 |
+
return np.pad(
|
46 |
+
image_patch,
|
47 |
+
((padding_size, padding_size), (padding_size, padding_size), (0, 0)),
|
48 |
+
'edge',
|
49 |
+
)
|
50 |
+
else:
|
51 |
+
return np.pad(
|
52 |
+
image_patch,
|
53 |
+
((0, 0), (padding_size, padding_size), (padding_size, padding_size)),
|
54 |
+
'edge',
|
55 |
+
)
|
56 |
+
|
57 |
+
|
58 |
+
def unpad_patches(image_patches, padding_size):
|
59 |
+
return image_patches[:, padding_size:-padding_size, padding_size:-padding_size, :]
|
60 |
+
|
61 |
+
|
62 |
+
def split_image_into_overlapping_patches(image_array, patch_size, padding_size=2):
|
63 |
+
""" Splits the image into partially overlapping patches.
|
64 |
+
The patches overlap by padding_size pixels.
|
65 |
+
Pads the image twice:
|
66 |
+
- first to have a size multiple of the patch size,
|
67 |
+
- then to have equal padding at the borders.
|
68 |
+
Args:
|
69 |
+
image_array: numpy array of the input image.
|
70 |
+
patch_size: size of the patches from the original image (without padding).
|
71 |
+
padding_size: size of the overlapping area.
|
72 |
+
"""
|
73 |
+
|
74 |
+
xmax, ymax, _ = image_array.shape
|
75 |
+
x_remainder = xmax % patch_size
|
76 |
+
y_remainder = ymax % patch_size
|
77 |
+
|
78 |
+
# modulo here is to avoid extending of patch_size instead of 0
|
79 |
+
x_extend = (patch_size - x_remainder) % patch_size
|
80 |
+
y_extend = (patch_size - y_remainder) % patch_size
|
81 |
+
|
82 |
+
# make sure the image is divisible into regular patches
|
83 |
+
extended_image = np.pad(image_array, ((0, x_extend), (0, y_extend), (0, 0)), 'edge')
|
84 |
+
|
85 |
+
# add padding around the image to simplify computations
|
86 |
+
padded_image = pad_patch(extended_image, padding_size, channel_last=True)
|
87 |
+
|
88 |
+
xmax, ymax, _ = padded_image.shape
|
89 |
+
patches = []
|
90 |
+
|
91 |
+
x_lefts = range(padding_size, xmax - padding_size, patch_size)
|
92 |
+
y_tops = range(padding_size, ymax - padding_size, patch_size)
|
93 |
+
|
94 |
+
for x in x_lefts:
|
95 |
+
for y in y_tops:
|
96 |
+
x_left = x - padding_size
|
97 |
+
y_top = y - padding_size
|
98 |
+
x_right = x + patch_size + padding_size
|
99 |
+
y_bottom = y + patch_size + padding_size
|
100 |
+
patch = padded_image[x_left:x_right, y_top:y_bottom, :]
|
101 |
+
patches.append(patch)
|
102 |
+
|
103 |
+
return np.array(patches), padded_image.shape
|
104 |
+
|
105 |
+
|
106 |
+
def stich_together(patches, padded_image_shape, target_shape, padding_size=4):
|
107 |
+
""" Reconstruct the image from overlapping patches.
|
108 |
+
After scaling, shapes and padding should be scaled too.
|
109 |
+
Args:
|
110 |
+
patches: patches obtained with split_image_into_overlapping_patches
|
111 |
+
padded_image_shape: shape of the padded image contructed in split_image_into_overlapping_patches
|
112 |
+
target_shape: shape of the final image
|
113 |
+
padding_size: size of the overlapping area.
|
114 |
+
"""
|
115 |
+
|
116 |
+
xmax, ymax, _ = padded_image_shape
|
117 |
+
patches = unpad_patches(patches, padding_size)
|
118 |
+
patch_size = patches.shape[1]
|
119 |
+
n_patches_per_row = ymax // patch_size
|
120 |
+
|
121 |
+
complete_image = np.zeros((xmax, ymax, 3))
|
122 |
+
|
123 |
+
row = -1
|
124 |
+
col = 0
|
125 |
+
for i in range(len(patches)):
|
126 |
+
if i % n_patches_per_row == 0:
|
127 |
+
row += 1
|
128 |
+
col = 0
|
129 |
+
complete_image[
|
130 |
+
row * patch_size: (row + 1) * patch_size, col * patch_size: (col + 1) * patch_size,:
|
131 |
+
] = patches[i]
|
132 |
+
col += 1
|
133 |
+
return complete_image[0: target_shape[0], 0: target_shape[1], :]
|
upscaler/__init__.py
ADDED
File without changes
|
upscaler/__pycache__/__init__.cpython-311.pyc
ADDED
Binary file (154 Bytes). View file
|
|