Contents
Introduction
AnimeGANv3 allows you to transform images into anime-style artwork using pre-trained ONNX models.In this tutorial, we will build a Tkinter GUI application that:
- Automatically installs required Python packages
- Lets you select an image
- Converts it into anime style using ONNX models
- Allows you to view, save, or revert to the original image
No prior experience with ONNX or GUI programming is needed.
Understanding the Requirements
Required Packages:
Pillow>=10– for image processingnumpy– numerical operationsopencv-contrib-python– image manipulationonnxruntime– run ONNX modelstkinter– GUI (usually comes with Python)
Our app will:
- Ensure required packages are installed automatically
- Download AnimeGANv3 models if missing
- Resize images for GUI display
- Transform images to anime style
- Save results to your disk
Step 1: Setting Up the Environment
It is recommended to use Python >=3.8 and <=3.11. Open your terminal and install packages manually (optional, automatic installer included in code):
pip install Pillow>=10 numpy opencv-contrib-python onnxruntime
Step 2: Automatic Package Installer
Our code automatically installs missing packages:
import sys
import subprocess
required_packages = [
"Pillow>=10",
"numpy",
"opencv-contrib-python",
"onnxruntime"
]
def install_package(pkg):
print(f"[INFO] Installing {pkg} ...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", pkg])
for pkg in required_packages:
pkg_name = pkg.split('>=')[0]
try:
__import__(pkg_name)
except ImportError:
install_package(pkg)
Step 3: Loading and Downloading ONNX Models
AnimeGANv3 comes with two styles: Hayao and Shinkai.
import os
import urllib.request
STYLE_MODELS = {
"Hayao": "AnimeGANv3_Hayao_36.onnx",
"Shinkai": "AnimeGANv3_Shinkai_37.onnx",
}
BASE_URL = "https://github.com/TachibanaYoshino/AnimeGANv3/releases/download/v1.1.0/"
MAX_DISPLAY_SIZE = 512
def get_model_path(style_name):
fname = STYLE_MODELS.get(style_name)
if fname is None:
return None
return os.path.join("models", fname)
def ensure_model(style_name):
model_path = get_model_path(style_name)
if model_path is None:
return None
if not os.path.exists(model_path):
os.makedirs("models", exist_ok=True)
url = BASE_URL + STYLE_MODELS[style_name]
urllib.request.urlretrieve(url, model_path)
print(f"[INFO] Downloaded {style_name} model to {model_path}")
return model_path
Step 4: ONNXAnime Wrapper Class
This class handles image transformation:
import cv2
import numpy as np
import onnxruntime as ort
class ONNXAnime:
def __init__(self, onnx_path):
self.session = ort.InferenceSession(onnx_path)
inp = self.session.get_inputs()[0]
self.input_name = inp.name
self.input_shape = inp.shape
def transform(self, img_bgr_or_rgba):
img = img_bgr_or_rgba
if img.shape[2] == 4:
img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
target_h, target_w = self.input_shape[2], self.input_shape[3]
img_resized = cv2.resize(img, (target_w, target_h))
img_resized = img_resized.astype(np.float32)/127.5 - 1.0
tensor = np.transpose(img_resized, (2,0,1))[None, :, :, :]
out = self.session.run(None, {self.input_name: tensor})[0]
out_img = np.clip((out[0].transpose(1,2,0)+1.0)*127.5, 0, 255).astype(np.uint8)
return cv2.cvtColor(out_img, cv2.COLOR_RGB2BGR)
Step 5: Building the Tkinter GUI
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image, ImageTk
class AnimeApp:
def __init__(self, master):
self.master = master
master.title("PyShine Anime GUI")
master.geometry("450x700")
tk.Label(master, text="Select image → choose style → Convert").pack(pady=8)
frame = tk.Frame(master)
frame.pack(pady=5)
tk.Button(frame, text="Select Image", command=self.load_image).pack(side=tk.LEFT, padx=8)
self.style_var = tk.StringVar(value="Hayao")
ttk.OptionMenu(frame, self.style_var, self.style_var.get(), "Hayao", "Shinkai").pack(side=tk.LEFT)
tk.Button(frame, text="Convert", command=self.convert).pack(side=tk.LEFT)
tk.Button(frame, text="Save", command=self.save).pack(side=tk.LEFT)
tk.Button(frame, text="Original", command=self.show_original).pack(side=tk.LEFT)
self.canvas = tk.Canvas(master, width=MAX_DISPLAY_SIZE, height=MAX_DISPLAY_SIZE, bg="gray")
self.canvas.pack(pady=10)
self.input_path = None
self.output_img = None
self.orig_img_pil = None
Step 6: Loading and Displaying Images
def load_image(self):
path = filedialog.askopenfilename(filetypes=[("Image files", "*.jpg *.jpeg *.png")])
if not path: return
self.input_path = path
img = Image.open(path).convert("RGB")
self.orig_img_pil = img.copy()
display_w = int(img.width*MAX_DISPLAY_SIZE/max(img.width,img.height))
display_h = int(img.height*MAX_DISPLAY_SIZE/max(img.width,img.height))
img_display = img.resize((display_w, display_h), Image.Resampling.LANCZOS)
self.tk_img = ImageTk.PhotoImage(img_display)
self.canvas.config(width=display_w, height=display_h)
self.canvas.create_image(display_w//2, display_h//2, image=self.tk_img)
Step 7: Converting Images
def convert(self):
if not self.input_path: return
style = self.style_var.get()
model_path = ensure_model(style)
img = cv2.imread(self.input_path, cv2.IMREAD_UNCHANGED)
anime = ONNXAnime(model_path)
out = anime.transform(img)
self.output_img = out
out_display = Image.fromarray(cv2.cvtColor(out, cv2.COLOR_BGR2RGB))
out_display = out_display.resize((self.canvas.winfo_width(), self.canvas.winfo_height()), Image.Resampling.LANCZOS)
self.tk_img = ImageTk.PhotoImage(out_display)
self.canvas.create_image(self.canvas.winfo_width()//2, self.canvas.winfo_height()//2, image=self.tk_img)
Step 8: Saving and Showing Original Image
def save(self):
if self.output_img is None: return
path = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG","*.png"),("JPEG","*.jpg")])
if not path: return
cv2.imwrite(path, self.output_img)
messagebox.showinfo("Saved", f"Saved to {path}")
def show_original(self):
if self.orig_img_pil is None: return
img_display = self.orig_img_pil.resize((self.canvas.winfo_width(), self.canvas.winfo_height()), Image.Resampling.LANCZOS)
self.tk_img = ImageTk.PhotoImage(img_display)
self.canvas.create_image(self.canvas.winfo_width()//2, self.canvas.winfo_height()//2, image=self.tk_img)
Step 9: Running the App
if __name__ == "__main__":
root = tk.Tk()
AnimeApp(root)
root.mainloop()
Sample Images



Complete Code
import sys
import subprocess
# Fail-safe automatic installer for bare env
required_packages = [
"Pillow>=10",
"numpy",
"opencv-contrib-python",
"onnxruntime"
]
def install_package(pkg):
print(f"[INFO] Installing {pkg} ...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", pkg])
for pkg in required_packages:
pkg_name = pkg.split('>=')[0]
try:
__import__(pkg_name)
except ImportError:
install_package(pkg)
# Import all after installation
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image, ImageTk
import numpy as np
import cv2
import onnxruntime as ort
import os
import urllib.request
# Ensure Pillow has Resampling.LANCZOS
try:
_ = Image.Resampling.LANCZOS
except AttributeError:
print("[INFO] Pillow version is too old, upgrading...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", "Pillow>=10"])
from PIL import Image, ImageTk
print("[INFO] All required packages are installed and imported successfully!")
# AnimeGANv3 ONNX Model Config
STYLE_MODELS = {
"Hayao": "AnimeGANv3_Hayao_36.onnx",
"Shinkai": "AnimeGANv3_Shinkai_37.onnx",
}
BASE_URL = "https://github.com/TachibanaYoshino/AnimeGANv3/releases/download/v1.1.0/"
MAX_DISPLAY_SIZE = 512 # Max width/height for GUI display
def get_model_path(style_name):
fname = STYLE_MODELS.get(style_name)
if fname is None:
return None
return os.path.join("models", fname)
def ensure_model(style_name):
model_path = get_model_path(style_name)
if model_path is None:
return None
if not os.path.exists(model_path):
os.makedirs("models", exist_ok=True)
url = BASE_URL + STYLE_MODELS[style_name]
try:
urllib.request.urlretrieve(url, model_path)
print(f"[INFO] Downloaded {style_name} model to {model_path}")
except Exception as e:
messagebox.showerror("Download error", f"Could not download {style_name} model:\n{e}")
return None
return model_path
def safe_dim(d, default=512):
try:
return int(d)
except (TypeError, ValueError):
return default
# ONNX AnimeGANv3 Wrapper
class ONNXAnime:
def __init__(self, onnx_path):
self.session = ort.InferenceSession(onnx_path)
inp = self.session.get_inputs()[0]
self.input_name = inp.name
self.input_shape = inp.shape
print("[INFO] Model expects input shape:", self.input_shape)
def transform(self, img_bgr_or_rgba):
img = img_bgr_or_rgba
if img is None:
raise ValueError("Input image is None")
orig_h, orig_w = img.shape[:2]
print(f"[DEBUG] Original input image shape: {img.shape}")
if img.shape[2] == 4:
img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
if len(self.input_shape) != 4:
raise ValueError(f"Unsupported model input shape: {self.input_shape}")
# Determine target height/width
if self.input_shape[1] == 3: # NCHW
target_h = safe_dim(self.input_shape[2])
target_w = safe_dim(self.input_shape[3])
elif self.input_shape[3] == 3: # NHWC
target_h = safe_dim(self.input_shape[1])
target_w = safe_dim(self.input_shape[2])
else:
raise ValueError(f"Unsupported model input shape: {self.input_shape}")
print(f"[DEBUG] Resizing input for model: ({target_h}, {target_w})")
img_resized = cv2.resize(img, (target_w, target_h), interpolation=cv2.INTER_AREA)
img_resized = img_resized.astype(np.float32) / 127.5 - 1.0
if self.input_shape[1] == 3: # NCHW
tensor = np.transpose(img_resized, (2,0,1))[None, :, :, :]
else: # NHWC
tensor = img_resized[None, :, :, :]
out = self.session.run(None, {self.input_name: tensor})[0]
print(f"[DEBUG] Raw model output shape: {out.shape}")
if out.ndim == 4 and out.shape[1] == 3: # NCHW
out_img = out[0].transpose(1,2,0)
elif out.ndim == 4 and out.shape[3] == 3: # NHWC
out_img = out[0]
else:
raise ValueError(f"Unsupported model output shape: {out.shape}")
out_img = np.clip((out_img + 1.0) * 127.5, 0, 255).astype(np.uint8)
out_bgr = cv2.cvtColor(out_img, cv2.COLOR_RGB2BGR)
print(f"[DEBUG] Converted output image shape (before GUI resize): {out_bgr.shape}")
return out_bgr
# GUI App
class AnimeApp:
def __init__(self, master):
self.master = master
master.title("PyShine Anime GUI")
master.geometry("450x700")
tk.Label(master, text="Select image → choose style → Convert").pack(pady=8)
frame = tk.Frame(master)
frame.pack(pady=5)
# OptionMenu for style selection
self.style_var = tk.StringVar()
self.style_var.set("Hayao") # default value
ttk.OptionMenu(frame, self.style_var, self.style_var.get(), *STYLE_MODELS.keys()).pack(side=tk.LEFT, padx=0)
tk.Button(frame, text="Select", command=self.load_image).pack(side=tk.LEFT, padx=0)
tk.Button(frame, text="Convert", command=self.convert).pack(side=tk.LEFT, padx=0)
tk.Button(frame, text="Save", command=self.save).pack(side=tk.LEFT, padx=0)
tk.Button(frame, text="Original", command=self.show_original).pack(side=tk.LEFT, padx=0)
# Canvas for image display
self.canvas = tk.Canvas(master, width=MAX_DISPLAY_SIZE, height=MAX_DISPLAY_SIZE, bg="gray")
self.canvas.pack(pady=10)
# Variables
self.input_path = None
self.output_img = None
self.orig_img_pil = None # original PIL image for showing anytime
self.orig_w = 512
self.orig_h = 512
self.display_scale = 1.0
self.tk_img = None # reference to Tkinter image
def load_image(self):
path = filedialog.askopenfilename(filetypes=[("Image files", "*.jpg *.jpeg *.png")])
if not path:
return
self.input_path = path
img = Image.open(path).convert("RGB")
self.orig_img_pil = img.copy() # save original for Show Original
self.orig_w, self.orig_h = img.size
scale = min(MAX_DISPLAY_SIZE/self.orig_w, MAX_DISPLAY_SIZE/self.orig_h, 1.0)
self.display_scale = scale
display_w = int(self.orig_w * scale)
display_h = int(self.orig_h * scale)
img_display = img.resize((display_w, display_h), Image.Resampling.LANCZOS)
self.tk_img = ImageTk.PhotoImage(img_display)
self.canvas.config(width=display_w, height=display_h)
self.canvas.create_image(display_w//2, display_h//2, image=self.tk_img)
print(f"[DEBUG] GUI displaying input image shape: {display_w}x{display_h}")
def show_original(self):
if self.orig_img_pil is None:
messagebox.showwarning("No image", "Please load an image first.")
return
display_w = int(self.orig_w * self.display_scale)
display_h = int(self.orig_h * self.display_scale)
img_display = self.orig_img_pil.resize((display_w, display_h), Image.Resampling.LANCZOS)
self.tk_img = ImageTk.PhotoImage(img_display)
self.canvas.config(width=display_w, height=display_h)
self.canvas.create_image(display_w//2, display_h//2, image=self.tk_img)
print(f"[DEBUG] GUI displaying original input image shape: {display_w}x{display_h}")
def convert(self):
if not self.input_path:
messagebox.showwarning("No image", "Please select an image first.")
return
style = self.style_var.get()
model_path = ensure_model(style)
if not model_path:
return
try:
img = cv2.imread(self.input_path, cv2.IMREAD_UNCHANGED)
anime = ONNXAnime(model_path)
out = anime.transform(img)
self.output_img = out
# Resize to original image for saving
out_display = cv2.resize(out, (self.orig_w, self.orig_h), interpolation=cv2.INTER_AREA)
# Resize for GUI display
display_w = int(self.orig_w * self.display_scale)
display_h = int(self.orig_h * self.display_scale)
out_display_pil = Image.fromarray(cv2.cvtColor(out_display, cv2.COLOR_BGR2RGB))
out_display_pil = out_display_pil.resize((display_w, display_h), Image.Resampling.LANCZOS)
self.tk_img = ImageTk.PhotoImage(out_display_pil)
self.canvas.config(width=display_w, height=display_h)
self.canvas.create_image(display_w//2, display_h//2, image=self.tk_img)
print(f"[DEBUG] GUI displaying output image shape: {display_w}x{display_h}")
except Exception as e:
messagebox.showerror("Failed to convert image", f"{e}")
def save(self):
if self.output_img is None:
messagebox.showwarning("No output", "No anime image to save.")
return
path = filedialog.asksaveasfilename(defaultextension=".png",
filetypes=[("PNG","*.png"),("JPEG","*.jpg")])
if not path:
return
save_img = cv2.resize(self.output_img, (self.orig_w, self.orig_h), interpolation=cv2.INTER_AREA)
print(f"[DEBUG] Saving output image resized shape: {save_img.shape}")
cv2.imwrite(path, save_img)
messagebox.showinfo("Saved", f"Saved to {path}")
# Run App
if __name__ == "__main__":
root = tk.Tk()
# Define a style for the OptionMenu
style = ttk.Style(root)
style.theme_use('clam') # or 'default', 'alt'
style.configure('TMenubutton', foreground='black', background='white', font=('Arial', 12))
style_var = tk.StringVar(value="Hayao")
option = ttk.OptionMenu(root, style_var, style_var.get(), "Hayao", "Shinkai")
# option.pack(padx=20, pady=20)
AnimeApp(root)
root.mainloop()
Conclusion
You now have a fully functional AnimeGANv3 ONNX GUI!You learned:
- Automatic package installation
- Downloading ONNX models
- Transforming images with ONNX
- Displaying images in Tkinter
- Saving results and viewing original image
Experiment with different styles and images to create your own anime artwork!
Website: https://www.pyshine.com Author: PyShine