scunet_model.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import os.path
  2. import sys
  3. import PIL.Image
  4. import numpy as np
  5. import torch
  6. from tqdm import tqdm
  7. from basicsr.utils.download_util import load_file_from_url
  8. import modules.upscaler
  9. from modules import devices, modelloader, script_callbacks, errors
  10. from scunet_model_arch import SCUNet as net
  11. from modules.shared import opts
  12. class UpscalerScuNET(modules.upscaler.Upscaler):
  13. def __init__(self, dirname):
  14. self.name = "ScuNET"
  15. self.model_name = "ScuNET GAN"
  16. self.model_name2 = "ScuNET PSNR"
  17. self.model_url = "https://github.com/cszn/KAIR/releases/download/v1.0/scunet_color_real_gan.pth"
  18. self.model_url2 = "https://github.com/cszn/KAIR/releases/download/v1.0/scunet_color_real_psnr.pth"
  19. self.user_path = dirname
  20. super().__init__()
  21. model_paths = self.find_models(ext_filter=[".pth"])
  22. scalers = []
  23. add_model2 = True
  24. for file in model_paths:
  25. if "http" in file:
  26. name = self.model_name
  27. else:
  28. name = modelloader.friendly_name(file)
  29. if name == self.model_name2 or file == self.model_url2:
  30. add_model2 = False
  31. try:
  32. scaler_data = modules.upscaler.UpscalerData(name, file, self, 4)
  33. scalers.append(scaler_data)
  34. except Exception:
  35. errors.report(f"Error loading ScuNET model: {file}", exc_info=True)
  36. if add_model2:
  37. scaler_data2 = modules.upscaler.UpscalerData(self.model_name2, self.model_url2, self)
  38. scalers.append(scaler_data2)
  39. self.scalers = scalers
  40. @staticmethod
  41. @torch.no_grad()
  42. def tiled_inference(img, model):
  43. # test the image tile by tile
  44. h, w = img.shape[2:]
  45. tile = opts.SCUNET_tile
  46. tile_overlap = opts.SCUNET_tile_overlap
  47. if tile == 0:
  48. return model(img)
  49. device = devices.get_device_for('scunet')
  50. assert tile % 8 == 0, "tile size should be a multiple of window_size"
  51. sf = 1
  52. stride = tile - tile_overlap
  53. h_idx_list = list(range(0, h - tile, stride)) + [h - tile]
  54. w_idx_list = list(range(0, w - tile, stride)) + [w - tile]
  55. E = torch.zeros(1, 3, h * sf, w * sf, dtype=img.dtype, device=device)
  56. W = torch.zeros_like(E, dtype=devices.dtype, device=device)
  57. with tqdm(total=len(h_idx_list) * len(w_idx_list), desc="ScuNET tiles") as pbar:
  58. for h_idx in h_idx_list:
  59. for w_idx in w_idx_list:
  60. in_patch = img[..., h_idx: h_idx + tile, w_idx: w_idx + tile]
  61. out_patch = model(in_patch)
  62. out_patch_mask = torch.ones_like(out_patch)
  63. E[
  64. ..., h_idx * sf: (h_idx + tile) * sf, w_idx * sf: (w_idx + tile) * sf
  65. ].add_(out_patch)
  66. W[
  67. ..., h_idx * sf: (h_idx + tile) * sf, w_idx * sf: (w_idx + tile) * sf
  68. ].add_(out_patch_mask)
  69. pbar.update(1)
  70. output = E.div_(W)
  71. return output
  72. def do_upscale(self, img: PIL.Image.Image, selected_file):
  73. torch.cuda.empty_cache()
  74. model = self.load_model(selected_file)
  75. if model is None:
  76. print(f"ScuNET: Unable to load model from {selected_file}", file=sys.stderr)
  77. return img
  78. device = devices.get_device_for('scunet')
  79. tile = opts.SCUNET_tile
  80. h, w = img.height, img.width
  81. np_img = np.array(img)
  82. np_img = np_img[:, :, ::-1] # RGB to BGR
  83. np_img = np_img.transpose((2, 0, 1)) / 255 # HWC to CHW
  84. torch_img = torch.from_numpy(np_img).float().unsqueeze(0).to(device) # type: ignore
  85. if tile > h or tile > w:
  86. _img = torch.zeros(1, 3, max(h, tile), max(w, tile), dtype=torch_img.dtype, device=torch_img.device)
  87. _img[:, :, :h, :w] = torch_img # pad image
  88. torch_img = _img
  89. torch_output = self.tiled_inference(torch_img, model).squeeze(0)
  90. torch_output = torch_output[:, :h * 1, :w * 1] # remove padding, if any
  91. np_output: np.ndarray = torch_output.float().cpu().clamp_(0, 1).numpy()
  92. del torch_img, torch_output
  93. torch.cuda.empty_cache()
  94. output = np_output.transpose((1, 2, 0)) # CHW to HWC
  95. output = output[:, :, ::-1] # BGR to RGB
  96. return PIL.Image.fromarray((output * 255).astype(np.uint8))
  97. def load_model(self, path: str):
  98. device = devices.get_device_for('scunet')
  99. if "http" in path:
  100. filename = load_file_from_url(url=self.model_url, model_dir=self.model_download_path, file_name="%s.pth" % self.name, progress=True)
  101. else:
  102. filename = path
  103. if not os.path.exists(os.path.join(self.model_path, filename)) or filename is None:
  104. print(f"ScuNET: Unable to load model from {filename}", file=sys.stderr)
  105. return None
  106. model = net(in_nc=3, config=[4, 4, 4, 4, 4, 4, 4], dim=64)
  107. model.load_state_dict(torch.load(filename), strict=True)
  108. model.eval()
  109. for _, v in model.named_parameters():
  110. v.requires_grad = False
  111. model = model.to(device)
  112. return model
  113. def on_ui_settings():
  114. import gradio as gr
  115. from modules import shared
  116. shared.opts.add_option("SCUNET_tile", shared.OptionInfo(256, "Tile size for SCUNET upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}, section=('upscaling', "Upscaling")).info("0 = no tiling"))
  117. shared.opts.add_option("SCUNET_tile_overlap", shared.OptionInfo(8, "Tile overlap for SCUNET upscalers.", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}, section=('upscaling', "Upscaling")).info("Low values = visible seam"))
  118. script_callbacks.on_ui_settings(on_ui_settings)