Skip to content

Commit 703f4af

Browse files
committed
Adds Kuwahara Blur and Parabolize
1 parent ed3aebb commit 703f4af

File tree

5 files changed

+503
-95
lines changed

5 files changed

+503
-95
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
wip/
2+
.vscode/*

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,23 @@ Both images have the workflow attached, and it is included so feel free to use i
1818
- Blur: Applies a Gaussian blur to the input image, softening the details
1919
- CannyEdgeDetection: Applies Canny edge detection to the input image
2020
- Chromatic Aberration: Shifts the color channels in an image, creating a glitch aesthetic
21-
- ColorCorrect: Adjusts the color balance, temperature, hue, brightness, contrast, saturation, and gamma of an image
21+
- **ColorCorrect: Adjusts the color balance, temperature, hue, brightness, contrast, saturation, and gamma of an image**
2222
- Dissolve: Creates a grainy blend of two images using random pixels based on a dissolve factor.
2323
- DodgeAndBurn: Adjusts image brightness using dodge and burn effects based on a mask and intensity.
2424
- FilmGrain: Adds a film grain effect to the image, along with options to control the temperature, and vignetting
2525
- Glow: Applies a blur with a specified radius and then blends it with the original image. Creates a nice glowing effect.
26+
- **KuwaharaBlur: Applies an edge preserving blur, creating a stunning and unique effect.**
27+
- Parabolize: Applies a color transformation effect using a parabolic formula
2628
- PencilSketch: Converts an image into a hand-drawn pencil sketch style.
27-
- PixelSort: Rearranges the pixels in the input image based on their values, and input mask. Creates a cool glitch like effect.
29+
- **PixelSort: Rearranges the pixels in the input image based on their values, and input mask. Creates a cool glitch like effect.**
2830
- Pixelize: Applies a pixelization effect, simulating the reducing of resolution
29-
- Quantize: Set and dither the amount of colors in an image from 0-256, reducing color information
31+
- **Quantize: Set and dither the amount of colors in an image from 0-256, reducing color information**
3032
- Sharpen: Enhances the details in an image by applying a sharpening filter
31-
- Solarize: Inverts image colors based on a threshold for a striking, high-contrast effect
33+
- **Solarize: Inverts image colors based on a threshold for a striking, high-contrast effect**
3234
- Vignette: Applies a vignette effect, putting the corners of the image in shadow
3335

36+
**Bolded Nodes are my Personal Favorites, and highly recommended to expirement with**
37+
3438
## Combine Nodes
3539

3640
By default `post_processing_nodes.py` should have all of the combined nodes. If you want a subset of nodes, you can run

post_processing/kuwahara_blur.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import cv2
2+
import numpy as np
3+
import multiprocessing as mp
4+
import torch
5+
6+
class KuwaharaBlur:
7+
def __init__(self):
8+
pass
9+
10+
@classmethod
11+
def INPUT_TYPES(s):
12+
return {
13+
"required": {
14+
"image": ("IMAGE",),
15+
"blur_radius": ("INT", {
16+
"default": 3,
17+
"min": 0,
18+
"max": 31,
19+
"step": 1
20+
}),
21+
"method": (["mean", "gaussian"],),
22+
},
23+
}
24+
25+
RETURN_TYPES = ("IMAGE",)
26+
FUNCTION = "apply_kuwahara_filter"
27+
28+
CATEGORY = "postprocessing"
29+
30+
def apply_kuwahara_filter(self, image: np.ndarray, blur_radius: int, method: str):
31+
if blur_radius == 0:
32+
return (image,)
33+
34+
out = torch.zeros_like(image)
35+
batch_size, height, width, channels = image.shape
36+
37+
for b in range(batch_size):
38+
image = image[b].cpu().numpy() * 255.0
39+
image = image.astype(np.uint8)
40+
41+
out[b] = torch.from_numpy(kuwahara(image, method=method, radius=blur_radius)) / 255.0
42+
43+
return (out,)
44+
45+
def kuwahara(orig_img, method="mean", radius=3, sigma=None):
46+
if method == "gaussian" and sigma is None:
47+
sigma = -1
48+
49+
image = orig_img.astype(np.float32, copy=False)
50+
avgs = np.empty((4, *image.shape), dtype=image.dtype)
51+
stddevs = np.empty((4, *image.shape[:2]), dtype=image.dtype)
52+
image_2d = cv2.cvtColor(orig_img, cv2.COLOR_BGR2GRAY).astype(image.dtype, copy=False)
53+
avgs_2d = np.empty((4, *image.shape[:2]), dtype=image.dtype)
54+
55+
squared_img = image_2d ** 2
56+
57+
if method == "mean":
58+
kxy = np.ones(radius + 1, dtype=image.dtype) / (radius + 1)
59+
elif method == "gaussian":
60+
kxy = cv2.getGaussianKernel(2 * radius + 1, sigma, ktype=cv2.CV_32F)
61+
kxy /= kxy[radius:].sum()
62+
klr = np.array([kxy[:radius+1], kxy[radius:]])
63+
kindexes = [[1, 1], [1, 0], [0, 1], [0, 0]]
64+
65+
shift = [(0, 0), (0, radius), (radius, 0), (radius, radius)]
66+
67+
for k in range(4):
68+
if method == "mean":
69+
kx, ky = kxy, kxy
70+
else:
71+
kx, ky = klr[kindexes[k]]
72+
cv2.sepFilter2D(image, -1, kx, ky, avgs[k], shift[k])
73+
cv2.sepFilter2D(image_2d, -1, kx, ky, avgs_2d[k], shift[k])
74+
cv2.sepFilter2D(squared_img, -1, kx, ky, stddevs[k], shift[k])
75+
stddevs[k] = stddevs[k] - avgs_2d[k] ** 2
76+
77+
indices = np.argmin(stddevs, axis=0)
78+
filtered = np.take_along_axis(avgs, indices[None,...,None], 0).reshape(image.shape)
79+
80+
return filtered.astype(orig_img.dtype)
81+
82+
NODE_CLASS_MAPPINGS = {
83+
"KuwaharaBlur": KuwaharaBlur
84+
}

post_processing/parabolize.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import torch
2+
3+
class Parabolize:
4+
def __init__(self):
5+
pass
6+
7+
@classmethod
8+
def INPUT_TYPES(cls):
9+
return {
10+
"required": {
11+
"image": ("IMAGE",),
12+
"coeff": ("FLOAT", {
13+
"default": 1.0,
14+
"min": -10.0,
15+
"max": 10.0,
16+
"step": 0.1
17+
}),
18+
"vertex_x": ("FLOAT", {
19+
"default": 0.5,
20+
"min": 0.0,
21+
"max": 1.0,
22+
"step": 0.1
23+
}),
24+
"vertex_y": ("FLOAT", {
25+
"default": 0.5,
26+
"min": 0.0,
27+
"max": 1.0,
28+
"step": 0.1
29+
}),
30+
},
31+
}
32+
33+
RETURN_TYPES = ("IMAGE",)
34+
FUNCTION = "parabolize_image"
35+
36+
CATEGORY = "postprocessing"
37+
38+
def parabolize_image(self, image: torch.Tensor, coeff: float, vertex_x: float, vertex_y: float):
39+
parabolized_image = coeff * torch.pow(image - vertex_x, 2) + vertex_y
40+
parabolized_image = torch.clamp(parabolized_image, 0, 1)
41+
return (parabolized_image,)
42+
43+
NODE_CLASS_MAPPINGS = {
44+
"Parabolize": Parabolize,
45+
}

0 commit comments

Comments
 (0)