diff --git a/README.MD b/README.MD index 76de8a3..216ff2f 100644 --- a/README.MD +++ b/README.MD @@ -595,6 +595,27 @@ You also can change the fonts folder in config. +### Lens Bokeh +![image_effects_lens_bokeh](images/image_effects_lens_bokeh.png) +> Emulate a [bokeh](https://en.wikipedia.org/wiki/Bokeh) effect to images. + +
+ Params: + +* blades_shape `[3 - *]` - The number of blades at the lens. +* blades_radius `[1 - *]` - Size of blades. +* blades_rotation `[0.0 - 360.0]` - Blades rotation. +* blur_size `[2 - *]` - Blur strength. +* blur_type `[bilateral, stack, none]` + * **bilateral** - Blur is set up to preserve sharp and bright edges. + * **stack** - Blur with color correction. + * **none** - Without blur. +* method `[dilate, filter]` + * **dilate** - A good choice for initially bright images. Creates a strong bokeh effect, but spoils the details of the image. I can recommend it only for the background. + * **filter** - Very fast. A weak bokeh effect, I recommend it for dark images with bright rare details such as lamp lights or car headlights. Originally created under the impression of LensBlur in Adobe Photoshop and achieved about 80%~ compliance. I recommend setting `blur_type` as `none` since it blurs the image by itself. + +
+ ### Lens Optic Axis ![image_effects_lens_optic_axis](images/image_effects_lens_optic_axis.png) > Apply a camera lens distort to the images. diff --git a/modules/ImageEffects.py b/modules/ImageEffects.py index 0e9166d..a8a9e1b 100644 --- a/modules/ImageEffects.py +++ b/modules/ImageEffects.py @@ -1,6 +1,7 @@ import cv2 import torch import torchvision.transforms.functional as F +import numpy as np from .Utils import radialspace_1D, radialspace_2D, cv2_layer @@ -361,6 +362,90 @@ class ImageEffectsLensChromaticAberration: ]),) +class ImageEffectsLensBokeh: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE",), + "blades_shape": ("INT", { + "default": 5, + "min": 3, + }), + "blades_radius": ("INT", { + "default": 10, + "min": 1, + }), + "blades_rotation": ("FLOAT", { + "default": 0.0, + "min": 0.0, + "max": 360.0, + }), + "blur_size": ("INT", { + "default": 10, + "min": 1, + "step": 2 + }), + "blur_type": (["bilateral", "stack", "none"],), + "method": (["dilate", "filter"],), + }, + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "node" + CATEGORY = "image/effects/lens" + + # noinspection PyUnresolvedReferences + def lens_blur(self, image, blades_shape, blades_radius, blades_rotation, method): + angles = np.linspace(0, 2 * np.pi, blades_shape + 1)[:-1] + blades_rotation * np.pi / 180 + x = blades_radius * np.cos(angles) + blades_radius + y = blades_radius * np.sin(angles) + blades_radius + pts = np.stack([x, y], axis=1).astype(np.int32) + + mask = np.zeros((blades_radius * 2 + 1, blades_radius * 2 + 1), np.uint8) + cv2.fillPoly(mask, [pts], 255) + + gaussian_kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) + + if method == "dilate": + kernel = cv2.filter2D(mask, -1, gaussian_kernel) + result = cv2.dilate(image, kernel) + elif method == "filter": + height, width = image.shape[:2] + dilate_size = min(height, width) // 512 + + if dilate_size > 0: + image = cv2.dilate(image, np.ones((dilate_size, dilate_size), np.uint8)) + + kernel = mask.astype(np.float32) / np.sum(mask) + kernel = cv2.filter2D(kernel, -1, gaussian_kernel) + result = cv2.filter2D(image, -1, kernel) + else: + raise ValueError("Unsupported method.") + + return result + + def node(self, images, blades_shape, blades_radius, blades_rotation, blur_size, blur_type, method): + tensor = images.clone().detach() + blur_size -= 1 + + if blur_type == "bilateral": + tensor = cv2_layer(tensor, lambda x: cv2.bilateralFilter(x, blur_size, -100, 100)) + elif blur_type == "stack": + tensor = cv2_layer(tensor, lambda x: cv2.stackBlur(x, (blur_size, blur_size))) + elif blur_type == "none": + pass + else: + raise ValueError("Unsupported blur type.") + + return (cv2_layer(tensor, lambda x: self.lens_blur( + x, blades_shape, blades_radius, blades_rotation, method) + ),) + + class ImageEffectsLensOpticAxis: def __init__(self): pass @@ -528,6 +613,7 @@ NODE_CLASS_MAPPINGS = { "ImageEffectsNegative": ImageEffectsNegative, "ImageEffectsSepia": ImageEffectsSepia, "ImageEffectsLensChromaticAberration": ImageEffectsLensChromaticAberration, + "ImageEffectsLensBokeh": ImageEffectsLensBokeh, "ImageEffectsLensOpticAxis": ImageEffectsLensOpticAxis, "ImageEffectsLensVignette": ImageEffectsLensVignette }