| from pathlib import Path |
|
|
| import gradio as gr |
| import torch |
| from PIL import Image, ImageOps |
| from torchvision import transforms |
|
|
| from utils.models import Decoder, VGGEncoder |
| from utils.utils import adaptive_instance_normalization |
|
|
|
|
| BASE_DIR = Path(__file__).resolve().parent |
| VGG_PATH = BASE_DIR / "weights" / "vgg_normalised.pth" |
| DECODER_PATH = BASE_DIR / "weights" / "decoder_final.pth" |
| MAX_IMAGE_SIZE = 512 |
|
|
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| torch.set_num_threads(min(4, max(1, torch.get_num_threads()))) |
|
|
|
|
| def load_models(): |
| if not VGG_PATH.exists(): |
| raise FileNotFoundError(f"Missing VGG weights: {VGG_PATH}") |
| if not DECODER_PATH.exists(): |
| raise FileNotFoundError(f"Missing decoder weights: {DECODER_PATH}") |
|
|
| encoder = VGGEncoder(str(VGG_PATH)).to(DEVICE).eval() |
| decoder = Decoder().to(DEVICE).eval() |
| decoder_state = torch.load(str(DECODER_PATH), map_location=DEVICE) |
| decoder.load_state_dict(decoder_state) |
| return encoder, decoder |
|
|
|
|
| ENCODER, DECODER = load_models() |
|
|
|
|
| def prepare_image(image: Image.Image) -> torch.Tensor: |
| image = ImageOps.exif_transpose(image).convert("RGB") |
| image.thumbnail((MAX_IMAGE_SIZE, MAX_IMAGE_SIZE), Image.Resampling.LANCZOS) |
| return transforms.ToTensor()(image).unsqueeze(0).to(DEVICE) |
|
|
|
|
| def tensor_to_image(tensor: torch.Tensor) -> Image.Image: |
| tensor = tensor.squeeze(0).detach().cpu().clamp(0, 1) |
| return transforms.ToPILImage()(tensor) |
|
|
|
|
| def stylize(content_image, style_image, alpha): |
| if content_image is None or style_image is None: |
| raise gr.Error("Upload both a content image and a style image.") |
|
|
| alpha = float(alpha) |
| content = prepare_image(content_image) |
| style = prepare_image(style_image) |
|
|
| with torch.inference_mode(): |
| content_features = ENCODER(content, is_test=True) |
| style_features = ENCODER(style, is_test=True) |
| stylized_features = adaptive_instance_normalization( |
| content_features, |
| style_features, |
| ) |
| blended_features = alpha * stylized_features + (1.0 - alpha) * content_features |
| output = DECODER(blended_features) |
|
|
| return tensor_to_image(output) |
|
|
|
|
| example_dir = BASE_DIR / "examples" |
| examples = [ |
| [ |
| str(example_dir / "content_lenna.jpg"), |
| str(example_dir / "style_sketch.png"), |
| 1.0, |
| ], |
| [ |
| str(example_dir / "content_golden_gate.jpg"), |
| str(example_dir / "style_la_muse.jpg"), |
| 0.8, |
| ], |
| ] |
|
|
|
|
| with gr.Blocks(title="AdaIN Neural Style Transfer") as demo: |
| gr.Markdown("# AdaIN Neural Style Transfer") |
|
|
| with gr.Row(): |
| content_input = gr.Image(type="pil", label="Content image") |
| style_input = gr.Image(type="pil", label="Style image") |
|
|
| alpha_input = gr.Slider( |
| minimum=0.0, |
| maximum=1.0, |
| value=1.0, |
| step=0.05, |
| label="Style strength", |
| ) |
| run_button = gr.Button("Transfer style", variant="primary") |
| output_image = gr.Image(type="pil", label="Stylized output") |
|
|
| run_button.click( |
| fn=stylize, |
| inputs=[content_input, style_input, alpha_input], |
| outputs=output_image, |
| ) |
|
|
| gr.Examples( |
| examples=examples, |
| inputs=[content_input, style_input, alpha_input], |
| outputs=output_image, |
| fn=stylize, |
| cache_examples=False, |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| demo.queue(max_size=8).launch(ssr_mode=False) |
|
|
|
|