diff --git a/MANIFEST.in b/MANIFEST.in index dcd7e5e..8960918 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,8 @@ include pyproject.toml include *.md +include *.sh +include *.bat include LICENSE recursive-include tests test*.py -recursive-include doc *.png \ No newline at end of file +recursive-include doc *.png +recursive-include doc *.jpg \ No newline at end of file diff --git a/README.md b/README.md index ac9b1aa..f5e2f95 100644 --- a/README.md +++ b/README.md @@ -19,16 +19,12 @@ Check it on [Pypi](https://pypi.org/project/BlurGenerator/). ## Usage ```bash -usage: blurgenerator [-h] [--input INPUT] [--output OUTPUT] [--type TYPE] [--motion_blur_size MOTION_BLUR_SIZE] [--motion_blur_angle MOTION_BLUR_ANGLE] - [--lens_radius LENS_RADIUS] [--lens_components LENS_COMPONENTS] [--lens_exposure_gamma LENS_EXPOSURE_GAMMA] - [--gaussian_kernel GAUSSIAN_KERNEL] - -optional arguments: - -h, --help show this help message and exit - --input INPUT Specific path of image as `input`. - --output OUTPUT Specific path for `output`. Default is `./result.png`. - --type TYPE Blur type of `motion`, `lens`, or `gaussian`. Default is `motion`. - --motion_blur_size MOTION_BLUR_SIZE +blurgenerator --help +``` + +```bash +usage: blurgenerator [-h] [--input INPUT] [--input_depth_map INPUT_DEPTH_MAP] [--output OUTPUT] [--type TYPE] [--motion_blur_size MOTION_BLUR_SIZE] [--motion_blur_angle MOTION_BLUR_ANGLE] [--lens_radius LENS_RADIUS] [--lens_components LENS_COMPONENTS] + [--lens_exposure_gamma LENS_EXPOSURE_GAMMA] [--gaussian_kernel GAUSSIAN_KERNEL] [--depth_num_layers DEPTH_NUM_LAYERS] [--depth_min_blur DEPTH_MIN_BLUR] [--depth_max_blur DEPTH_MAX_BLUR] Size for motion blur. Default is 100. --motion_blur_angle MOTION_BLUR_ANGLE Angle for motion blur. Default is 30. @@ -40,13 +36,23 @@ optional arguments: Exposure gamma for lens blur. Default is 2. --gaussian_kernel GAUSSIAN_KERNEL Kernel for gaussian. Default is 100. + --depth_num_layers DEPTH_NUM_LAYERS + Layer for depth blur. Default is 3. + --depth_min_blur DEPTH_MIN_BLUR + Min. blur for depth blur. Default is 1. + --depth_max_blur DEPTH_MAX_BLUR + Max. blur for depth blur. Default is 100. ``` ## Example and Result +### Common use + - Original image -![original image](https://github.com/NatLee/Blur-Generator/blob/main/doc/test.png?raw=true) +![original image](./doc/test.png) + +#### Usage - Motion blur @@ -57,22 +63,24 @@ import cv2 from blurgenerator import motion_blur img = cv2.imread('test.png') result = motion_blur(img, size=100, angle=30) +cv2.imwrite('./output.png', result) ``` -![motion blur image](https://github.com/NatLee/Blur-Generator/blob/main/doc/motion.png?raw=true) +![motion blur image](./doc/motion.png) - Lens blur -`blurgenerator--type lens --input ./doc/test.png --output ./doc/lens.png` +`blurgenerator --type lens --input ./doc/test.png --output ./doc/lens.png` ```python import cv2 from blurgenerator import lens_blur img = cv2.imread('test.png') result = lens_blur(img, radius=5, components=4, exposure_gamma=2) +cv2.imwrite('./output.png', result) ``` -![lens blur image](https://github.com/NatLee/Blur-Generator/blob/main/doc/lens.png?raw=true) +![lens blur image](./doc/lens.png) - Gaussian blur @@ -83,6 +91,88 @@ import cv2 from blurgenerator import gaussian_blur img = cv2.imread('test.png') result = gaussian_blur(img, 100) +cv2.imwrite('./output.png', result) +``` + +![gaussian blur image](./doc/gaussian.png) + +### With depth map + +Feature from this [issue](https://github.com/NatLee/Blur-Generator/issues/1). + +- Original image + +![photo](./doc/depth-test.jpg) + +- Depth map + +![depth map](./doc/depth-map-test.png) + +#### Usage + +- Motion blur with depth map + +`blurgenerator --input .\doc\depth-test.jpg --type motion --input_depth_map .\doc\depth-map-test.png --depth_num_layers 5 --depth_min_blur 1 --depth_max_blur 50 --output .\doc\depth-motion-output.png` + +```python +import cv2 +from blurgenerator import motion_blur_with_depth_map +img = cv2.imread('test.jpg') +depth_img = cv2.imread('test-depth.png') +result = motion_blur_with_depth_map( + img, + depth_map=depth_img, + angle=30, + num_layers=10, + min_blur=1, + max_blur=50 +) +cv2.imwrite('./output.png', result) +``` + +![depth motion blur image](./doc/depth-motion-output.png) + +- Lens blur with depth map + +`blurgenerator --input .\doc\depth-test.jpg --type lens --input_depth_map .\doc\depth-map-test.png --depth_num_layers 3 --depth_min_blur 1 --depth_max_blur 50 --output .\doc\depth-lens-output.png` + +```python +import cv2 +from blurgenerator import lens_blur_with_depth_map +img = cv2.imread('test.jpg') +depth_img = cv2.imread('test-depth.png') +result = lens_blur_with_depth_map( + img, + depth_map=depth_img, + components=5, + exposure_gamma=5, + num_layers=10, + min_blur=1, + max_blur=50 +) +cv2.imwrite('./output.png', result) +``` + +![depth lens blur image](./doc/depth-lens-output.png) + +- Gaussian blur with depth map + +`blurgenerator --input .\doc\depth-test.jpg --type gaussian --input_depth_map .\doc\depth-map-test.png --depth_num_layers 3 --depth_min_blur 1 --depth_max_blur 50 --output .\doc\depth-gaussian-output.png` + +```python +import cv2 +from blurgenerator import gaussian_blur_with_depth_map +img = cv2.imread('test.jpg') +depth_img = cv2.imread('test-depth.png') +result = gaussian_blur_with_depth_map( + img, + depth_map=depth_img, + sigma=5, + num_layers=10, + min_blur=1, + max_blur=50 +) +cv2.imwrite('./output.png', result) ``` -![gaussian blur image](https://github.com/NatLee/Blur-Generator/blob/main/doc/gaussian.png?raw=true) +![depth gaussian blur image](./doc/depth-gaussian-output.png) diff --git a/doc/depth-gaussian-output.png b/doc/depth-gaussian-output.png new file mode 100644 index 0000000..4ab2764 Binary files /dev/null and b/doc/depth-gaussian-output.png differ diff --git a/doc/depth-lens-output.png b/doc/depth-lens-output.png new file mode 100644 index 0000000..ab3e540 Binary files /dev/null and b/doc/depth-lens-output.png differ diff --git a/doc/depth-map-test.png b/doc/depth-map-test.png new file mode 100644 index 0000000..e94987c Binary files /dev/null and b/doc/depth-map-test.png differ diff --git a/doc/depth-motion-output.png b/doc/depth-motion-output.png new file mode 100644 index 0000000..affc794 Binary files /dev/null and b/doc/depth-motion-output.png differ diff --git a/doc/depth-test.jpg b/doc/depth-test.jpg new file mode 100644 index 0000000..62ea0f2 Binary files /dev/null and b/doc/depth-test.jpg differ diff --git a/reinstall.bat b/reinstall.bat new file mode 100644 index 0000000..265c0d8 --- /dev/null +++ b/reinstall.bat @@ -0,0 +1 @@ +python setup.py clean --all install clean --all \ No newline at end of file diff --git a/reinstall.sh b/reinstall.sh new file mode 100644 index 0000000..9b193db --- /dev/null +++ b/reinstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python setup.py clean --all install clean --all \ No newline at end of file diff --git a/setup.py b/setup.py index fa18e33..3ff279c 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ 'Operating System :: OS Independent', ], python_requires='>=3.7', - install_requires=['opencv-python', 'scipy'], + install_requires=['opencv-python'], extras_require={ 'dev': ['check-manifest'], # 'test': ['coverage'], diff --git a/src/blurgenerator/__init__.py b/src/blurgenerator/__init__.py index cb07b49..1106b66 100644 --- a/src/blurgenerator/__init__.py +++ b/src/blurgenerator/__init__.py @@ -1,10 +1,14 @@ """ Blur maker init """ -__version__ = "1.0.1" +__version__ = "1.0.2" from .motion_blur import motion_blur from .lens_blur import lens_blur from .gaussian_blur import gaussian_blur +from .depth import motion_blur_with_depth_map +from .depth import lens_blur_with_depth_map +from .depth import gaussian_blur_with_depth_map + from .cli import main diff --git a/src/blurgenerator/cli.py b/src/blurgenerator/cli.py index d5a3a0c..055e989 100644 --- a/src/blurgenerator/cli.py +++ b/src/blurgenerator/cli.py @@ -5,6 +5,7 @@ from pathlib import Path import cv2 +import numpy as np from blurgenerator import motion_blur, lens_blur, gaussian_blur @@ -13,6 +14,8 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument('--input', type=str, default=None, help='Specific path of image as `input`.') + parser.add_argument('--input_depth_map', type=str, default=None, help='Specific path of depth image as `input_depth_map`.') + parser.add_argument('--output', type=str, default='./result.png', help='Specific path for `output`. Default is `./result.png`.') parser.add_argument('--type', type=str, default='motion', help='Blur type of `motion`, `lens`, or `gaussian`. Default is `motion`.') @@ -26,34 +29,104 @@ def main(): parser.add_argument('--gaussian_kernel', type=int, default=100, help='Kernel for gaussian. Default is 100.') - args = parser.parse_args() - - if args.input: - img_path = Path(args.input) - if img_path.is_file(): - if img_path.suffix in ['.jpg', '.jpeg', '.png']: - - img = cv2.imread(img_path.absolute().as_posix()) - img = img / 255. - - if args.type not in ['motion', 'lens', 'gaussian']: - print('----- No type has been selected. Please specific `motion`, `lens`, or `gaussian`.') - else: - if args.type == 'motion': - result = motion_blur(img, size=args.motion_blur_size, angle=args.motion_blur_angle) + # depth blur settings + parser.add_argument('--depth_num_layers', type=int, default=10, help='Layer for depth blur. Default is 3.') + parser.add_argument('--depth_min_blur', type=int, default=1, help='Min. blur for depth blur. Default is 1.') + parser.add_argument('--depth_max_blur', type=int, default=100, help='Max. blur for depth blur. Default is 100.') - elif args.type == 'lens': - result = lens_blur(img, radius=args.lens_radius, components=args.lens_components, exposure_gamma=args.lens_exposure_gamma) + # --------------------------------------------------------------- - elif args.type == 'gaussian': - result = gaussian_blur(img, args.gaussian_kernel) - - cv2.imwrite(args.output, result*255) - - else: - print('----- Only support common types of image `.jpg` and `.png`.') + args = parser.parse_args() - else: - print('----- File not exists!') - else: + if not args.input: print('----- Please specific image for input.') + return + + img_path = Path(args.input) + + if not img_path.is_file(): + print('----- `img_path` is not a file!') + return + + if img_path.suffix not in ['.jpg', '.jpeg', '.png']: + print('----- Only support common types of image `.jpg` and `.png`.') + return + + if args.type not in ['motion', 'lens', 'gaussian']: + print('----- No type has been selected. Please specific `motion`, `lens`, or `gaussian`.') + return + + if args.type == 'motion': + def blur_job(img, size=args.motion_blur_size): + return motion_blur(img, size=size, angle=args.motion_blur_angle) + + if args.type == 'lens': + def blur_job(img, radius=args.lens_radius): + return lens_blur(img, radius=radius, components=args.lens_components, exposure_gamma=args.lens_exposure_gamma) + + if args.type == 'gaussian': + def blur_job(img, kernel=args.gaussian_kernel): + return gaussian_blur(img, kernel) + + # --------------------------------------------------------------- + + img = cv2.imread(img_path.absolute().as_posix()) + + depth_map_path = args.input_depth_map + + if depth_map_path is None: + print(f'----- Generating `{args.type}` blur.') + result = blur_job(img) + cv2.imwrite(args.output, result) + return + + depth_map_path = Path(depth_map_path) + if not depth_map_path.is_file(): + print('----- `input_depth_map` is not a file!') + return + if depth_map_path.suffix not in ['.jpg', '.jpeg', '.png']: + print('----- Only support common types of image `.jpg` and `.png`.') + return + + print(f'----- Generating `{args.type}` blur with depth map.') + print('----- `motion_blur_size` will be ignored.') + print('----- `lens_radius` will be ignored.') + print('----- `gaussian_kernel` will be ignored.') + + depth_map = cv2.imread(depth_map_path.absolute().as_posix()) + + def map_range(value, inMin, inMax, outMin, outMax): + return outMin + (((value - inMin) / (inMax - inMin)) * (outMax - outMin)) + + def blur_with_depth(img, depth, num_layers=10, min_blur=1, max_blur=100): + min_depth = np.min(np.unique(depth)) + max_depth = np.max(np.unique(depth)) + step = (max_depth - min_depth) // num_layers + layers = np.array(range(min_depth, max_depth, step)) + out = np.zeros(img.shape) + + for value in layers: + dm = cv2.cvtColor(depth, cv2.COLOR_BGR2GRAY) + m = np.zeros(dm.shape) + m[dm > value] = 255 + m[dm > (value + step)] = 0 + l_mask = depth.copy() + l_mask[:,:,0] = m[:,:] + l_mask[:,:,1] = m[:,:] + l_mask[:,:,2] = m[:,:] + blur_amount = int(map_range(value, 0, 255, min_blur, max_blur)) + slice = blur_job(img, blur_amount) + _, mask = cv2.threshold(l_mask, 100, 255, cv2.THRESH_BINARY) + layer = cv2.bitwise_and(slice, slice, mask = mask[:,:,0]) + out = cv2.add(out, layer, dtype=0) + return out + + num_layers = args.depth_num_layers + min_blur = args.depth_min_blur + max_blur = args.depth_max_blur + + result = blur_with_depth(img, depth_map, num_layers=num_layers, min_blur=min_blur, max_blur=max_blur) + cv2.imwrite(args.output, result) + + return + diff --git a/src/blurgenerator/depth.py b/src/blurgenerator/depth.py new file mode 100644 index 0000000..7e18120 --- /dev/null +++ b/src/blurgenerator/depth.py @@ -0,0 +1,100 @@ +import numpy as np +import cv2 + +from blurgenerator import motion_blur, lens_blur, gaussian_blur + +def map_range(value, inMin, inMax, outMin, outMax): + return outMin + (((value - inMin) / (inMax - inMin)) * (outMax - outMin)) + +def get_depth_step_and_layer(depth_map, num_layers): + min_depth = np.min(np.unique(depth_map)) + max_depth = np.max(np.unique(depth_map)) + step = (max_depth - min_depth) // num_layers + layers = np.array(range(min_depth, max_depth, step)) + return step, layers + +def get_blur_amount_and_l_mask(depth_map, value, step, min_blur, max_blur): + dm = cv2.cvtColor(depth_map, cv2.COLOR_BGR2GRAY) + m = np.zeros(dm.shape) + m[dm > value] = 255 + m[dm > (value + step)] = 0 + l_mask = depth_map.copy() + l_mask[:,:,0] = m[:,:] + l_mask[:,:,1] = m[:,:] + l_mask[:,:,2] = m[:,:] + blur_amount = int(map_range(value, 0, 255, min_blur, max_blur)) + return blur_amount, l_mask + +def blur_with_depth(depth_map, num_layers=10, min_blur=1, max_blur=100): + step, layers = get_depth_step_and_layer(depth_map, num_layers) + blur_masks = [] + for value in layers: + blur_amount, l_mask = get_blur_amount_and_l_mask( + depth_map, + value, + step, + min_blur, + max_blur + ) + blur_masks.append((blur_amount, l_mask)) + return blur_masks + +def motion_blur_with_depth_map(img, depth_map, angle=30, num_layers=10, min_blur=1, max_blur=100): + out = np.zeros(img.shape) + blur_masks = blur_with_depth( + depth_map, + num_layers=num_layers, + min_blur=min_blur, + max_blur=max_blur + ) + for blur_amount, l_mask in blur_masks: + slice = motion_blur( + img, + size=blur_amount, + angle=angle + ) + _, mask = cv2.threshold(l_mask, 100, 255, cv2.THRESH_BINARY) + layer = cv2.bitwise_and(slice, slice, mask=mask[:,:,0]) + out = cv2.add(out, layer, dtype=0) + return out + +def lens_blur_with_depth_map(img, depth_map, components=5, exposure_gamma=5, num_layers=10, min_blur=1, max_blur=100): + out = np.zeros(img.shape) + blur_masks = blur_with_depth( + depth_map, + num_layers=num_layers, + min_blur=min_blur, + max_blur=max_blur + ) + + for blur_amount, l_mask in blur_masks: + slice = lens_blur( + img, + radius=blur_amount, + components=components, + exposure_gamma=exposure_gamma + ) + _, mask = cv2.threshold(l_mask, 100, 255, cv2.THRESH_BINARY) + layer = cv2.bitwise_and(slice, slice, mask=mask[:,:,0]) + out = cv2.add(out, layer, dtype=0) + return out + +def gaussian_blur_with_depth_map(img, depth_map, sigma=5, num_layers=10, min_blur=1, max_blur=100): + out = np.zeros(img.shape) + blur_masks = blur_with_depth( + depth_map, + num_layers=num_layers, + min_blur=min_blur, + max_blur=max_blur + ) + + for blur_amount, l_mask in blur_masks: + slice = gaussian_blur( + img, + blur_amount, + sigma=sigma + ) + _, mask = cv2.threshold(l_mask, 100, 255, cv2.THRESH_BINARY) + layer = cv2.bitwise_and(slice, slice, mask=mask[:,:,0]) + out = cv2.add(out, layer, dtype=0) + return out \ No newline at end of file diff --git a/src/blurgenerator/lens_blur.py b/src/blurgenerator/lens_blur.py index 9a3353e..ca26912 100644 --- a/src/blurgenerator/lens_blur.py +++ b/src/blurgenerator/lens_blur.py @@ -121,6 +121,8 @@ def filter_task(idx, channel, img_channel, component, component_params): def lens_blur(img, radius=3, components=5, exposure_gamma=5): + img = img/255. + img = np.ascontiguousarray(img.transpose(2,0,1), dtype=np.float32) # Obtain component parameters / scale values parameters, scale = get_parameters(component_count = components)