#blur #convolution #computer-vision

bokeh

A Rust implementation of image-blurring, focussing on disc-shaped kernels to produce a bokeh lens-effect

1 unstable release

0.1.0 Jan 27, 2023

#805 in Images

MIT license

320KB
618 lines

Bokeh

A Rust implementation of image-blurring using disc-shaped kernels to produce a 'Bokeh' lens-effect.

Draws heavily on the work done here by Mike Pound.

The disc-shaped kernels are approximated by a sum of complex Gaussian kernels. As a Gaussian blur is separable, instead of applying a single 2-D kernel during convolution, a 1-D kernel on each axis can be applied. To form a Bokeh blur effect, a disc-shaped kernel is used. This kernel is created by summing multiple complex Gaussian kernels, with the number of components improving the quality of the approximation. These relative weights of these components are found by attempting to minimise the deviation from a perfectly shaped kernel. See more here and here.

To view the shapes of the kernels for different numbers of compoents and for different kernel radii, see here. As can be see from this after 4/5 components there are very much diminishing returns in using more. Each additional component used increases the number of convolutions carried out on the image, i.e. using 8 components is 2 times slower than using 4.

Currently only images with 4 channels are supported.

Seperate APIs are available which allow a mask to be passed. This mask allows pixels of the original image to be retained. This should be a iterable of bool's, where true's correspond to the convolved image and false's corresponsed to the original.

Examples

Using the image library (requires the default image feature):

use bokeh::{params::KERNEL9_PARAM_SET, Blur};
use image::{io::Reader as ImageReader, ImageError};

// read the image
let mut img = ImageReader::open("myimage.jpg")?.decode()?;
// as the `bokeh::Blur` trait is imported
img.bokeh_blur(1.0, &KERNEL9_PARAM_SET, 3.0);
// save the image
img.save("output.png")?;

Using functions directly:

use bokeh::{bokeh_blur, params::KERNEL9_PARAM_SET};

// create simple 'image'
let mut pixels = vec![[0., 0., 0., 0.]; 9];
pixels[4] = [255., 255., 255., 255.];

// blur the image using 9 components
bokeh_blur(&mut pixels, 3, 3, 1.0, &KERNEL9_PARAM_SET, 3.0);

// pixels now blurred
assert_eq!(
    vec![
        1.6428886692061846,
        14.80242203513296,
        1.6428886692061846,
        14.802422035132915,
        254.93338630375473,
        14.802422035132915,
        1.6428886692061846,
        14.80242203513296,
        1.6428886692061846
    ]
    .iter()
    .map(|&i| [i, i, i, i])
    .collect::<Vec<_>>(),
    pixels
);

A utility struct Image is also provided:

use bokeh::{Blur, Image, params::KERNEL9_PARAM_SET};

let mut pixels = vec![[0., 0., 0., 0.]; 9];
pixels[4] = [255., 255., 255., 255.];
// same as above but using the struct
let mut img = Image::new(&mut pixels, 3, 3);

img.bokeh_blur(1.0, &KERNEL9_PARAM_SET, 3.0);

assert_eq!(
    vec![
        1.6428886692061846,
        14.80242203513296,
        1.6428886692061846,
        14.802422035132915,
        254.93338630375473,
        14.802422035132915,
        1.6428886692061846,
        14.80242203513296,
        1.6428886692061846
    ]
    .iter()
    .map(|&i| [i, i, i, i])
    .collect::<Vec<_>>(),
    img.pixels
);

Providing a mask:

use bokeh::{Blur, Image, params::KERNEL9_PARAM_SET};

let mut pixels = vec![[0., 0., 0., 0.]; 9];
pixels[4] = [255., 255., 255., 255.];
// creating a mask
let mask = vec![false, true, false, true, false, true, false, true, false];
let mut img = Image::new(&mut pixels, 3, 3);

img.bokeh_blur_with_mask(&mask, 1.0, &KERNEL9_PARAM_SET, 3.0);

assert_eq!(
    vec![
        0.,
        14.80242203513296,
        0.,
        14.802422035132915,
        255.,
        14.802422035132915,
        0.,
        14.80242203513296,
        0.
    ]
    .iter()
    .map(|&i| [i, i, i, i])
    .collect::<Vec<_>>(),
    img.pixels
);

In the assert! statement above, comparing it to the previous example, it can be seen that the original pixel values are retained.

License: MIT

Dependencies

~14MB
~92K SLoC