-
Notifications
You must be signed in to change notification settings - Fork 618
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fast Blur #2302
Fast Blur #2302
Conversation
Wow great work!
Long-term I think this function should belong in Maybe we could add a note in the docs of the "slow" blur method in this crate that points to this faster method in the Regarding use image::{DynamicImage, ImageBuffer, Pixel};
macro_rules! dynamic_map(
($dynimage: expr, $image: pat => $action: expr) => ({
use DynamicImage::*;
match $dynimage {
ImageLuma8($image) => ImageLuma8($action),
ImageLumaA8($image) => ImageLumaA8($action),
ImageRgb8($image) => ImageRgb8($action),
ImageRgba8($image) => ImageRgba8($action),
ImageLuma16($image) => ImageLuma16($action),
ImageLumaA16($image) => ImageLumaA16($action),
ImageRgb16($image) => ImageRgb16($action),
ImageRgba16($image) => ImageRgba16($action),
ImageRgb32F($image) => ImageRgb32F($action),
ImageRgba32F($image) => ImageRgba32F($action),
_ => unreachable!(),
}
});
($dynimage: expr, $image:pat_param, $action: expr) => (
match $dynimage {
DynamicImage::ImageLuma8($image) => $action,
DynamicImage::ImageLumaA8($image) => $action,
DynamicImage::ImageRgb8($image) => $action,
DynamicImage::ImageRgba8($image) => $action,
DynamicImage::ImageLuma16($image) => $action,
DynamicImage::ImageLumaA16($image) => $action,
DynamicImage::ImageRgb16($image) => $action,
DynamicImage::ImageRgba16($image) => $action,
DynamicImage::ImageRgb32F($image) => $action,
DynamicImage::ImageRgba32F($image) => $action,
_ => unreachable!(),
}
);
);
fn main() {
let image1 = DynamicImage::new_luma8(3, 3);
let image2 = DynamicImage::new_rgb8(3, 3);
let image1 = dynamic_map!(image1, i => none(i));
let image2 = dynamic_map!(image2, i => none(i));
}
fn none<P: Pixel, Container>(
_image: ImageBuffer<P, Vec<Container>>,
) -> ImageBuffer<P, Vec<Container>> {
todo!()
} |
Thank you for your comments. I added tasks for your (and my points) so we can track progress more easily. |
7ac2d69
to
2339f81
Compare
I have pushed a rather coarse fix for the private dependency checks. However, I don't know how to deal with these checks. I found no easy way of systematically fix this. The main problem is, that I can't use the public dependency property in the regular
To make this PR mergeable I hacked an "allow" for this llint to |
8dcc049
to
7d1683e
Compare
I did some initial benchmarking: on my intel i9 machine the bench (1024x768 rgb) takes 680ms with the true gaussian blur whereas |
7d1683e
to
da68a1b
Compare
Hi, some ways to improve noted in my comments on zune-imageprocs (author of the library) Never did the loop unrolling to save ilp, but others were implemented, really nice properties like no bounds check in inner loop, may be worth investigating (see https://godbolt.org/z/zexPEd976) See https://fgiesen.wordpress.com/2012/07/30/fast-blurs-1/ and https://fgiesen.wordpress.com/2012/08/01/fast-blurs-2/ for the theories behind it |
I invested some time into performance optimization:
None of these measures produced any results. I am leaving the code as it is for now (if no one comes up with another idea, of course). |
Furthermore, I see no need for kani/quickcheck tests. First the bounds are obviously correct. On the other hand there is no point in using unsafe so securing the code using |
@etemesi254: |
Nice. FYI these were the benchmark results for gaussian blur for big images before this commit . (image-rs, libvips,zune-image) https://etemesi254.github.io/assets/criterion/imageprocs_%20gaussian%20blur/report/index.html |
fc50c64
to
cdd617d
Compare
My work here is done for now and I would be happy about a code review |
@ripytide sorry to bother you (but you already commented on the issue): Do you know what would be the next steps to merge this PR (and get it released)? |
You'll need one of the maintainers signoff, of which I am not one. Also, I don't think this function belongs in this library and should instead be in the |
Thank you for your feedback. I created a PR in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code review time :)
fa64780
to
e7eefdb
Compare
1592ff5
to
d672675
Compare
25902b9
to
4737c29
Compare
I just wanted to give a short status report (I also cross post this comment to image-rs/imageproc#683)
What would be the next steps? |
4737c29
to
b4f75eb
Compare
@HeroicKatora sorry for pinging you directly but you already started a review. Is there anything I can do to support the review of this PR? Thank you a lot! |
b4f75eb
to
e5672ff
Compare
I've written the following example code for blur - feel free to include it in the PR under use std::env;
use std::error::Error;
use std::path::Path;
use std::process;
fn main() -> Result<(), Box<dyn Error>> {
// Collect command-line arguments
let args: Vec<String> = env::args().collect();
// Ensure that we have 2 CLI arguments
if args.len() != 3 {
eprintln!("Usage: {} <path> <radius>", args[0]);
process::exit(1);
}
let radius: f32 = args[2].parse()?;
// Load the input image
let path_str = &args[1];
let path = Path::new(path_str);
let input = image::open(path)?;
// Correct but slow Gaussian blur
let mut out_path_gauss = path.to_owned();
out_path_gauss.set_extension("gaussian.png");
let correct_but_slow = input.blur(radius);
correct_but_slow.save(out_path_gauss)?;
// Approximate but fast blur
let mut out_path_fast = path.to_owned();
out_path_fast.set_extension("fast.png");
let approximate_but_fast = input.fast_blur(radius);
approximate_but_fast.save(out_path_fast)?;
Ok(())
} With this test image both blur implementation show artifacts: It's just a white rectangle in the middle, surrounded by fully transparent pixels. Both Gaussian blur in The fully transparent pixels have R set to 255 while all the other channels are set to 0, and that red color bleeds into the white rectangle. The expected blur as produced by GIMP is like this - blurred white rectangle without any red color: To avoid such artifacts, the pixel's contribution to the color change should be weighted by its alpha channel value. Fully transparent pixels should get multiplied by 0 and not contribute to the changes in non-alpha channels at all. But since both blur implementations are incorrect, I'll leave deciding how to handle this up to the maintainers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine to keep the incorrect for now, given that they both are. I think we should bunch it with the known color-space issues, afterall it's not clear whether to treat channels as pre-multiplied or not. Given that ambiguity, this just ignores the alpha channel (edit: or assumes pre-multiplied in contrast to the other parts of the library). I think it'd be best to introduces separate alpha-aware version of both pre-multiplied and non-multiplied channel interpretations at a later point and to keep it consistent for now.
PR
This is an attempt to solve #986 as the current solution (using
fastblur
) is fragile and requires the consumers to access raw buffersof the image which is undesirable.
The implementation is inspired by
this article
which is in turn based on Kovesi, P.: Fast Almost-Gaussian Filtering The Australian Pattern
Recognition Society Conference: DICTA 2010. December 2010. Sydney.
The short story is that the real gaussian blur takes about 30 second for
real-world photos which is undesirable. The algorithm here is quick and
has pretty good results.
Questions
Is this PR ready?
No, but I want to have some feedback on the implementation so I can move on (in the right direction).
Are you aware of #2238?
Yes, but I don't know how to deal with it.
blur
is not yet deprecated andusers of the library will search for a fast alternative in this crate,
not in
imageproc
. I am open to move my code there now or later.The advantage of having it here would be that I do not suffer from
the
non_exhaustive
ofDynamicImage
and can implement blurfor this type (if desired).
Why didn't you simply c&p the code from the blurslice crate (or the article)?
The
blurslice
crate is distributed under the MIT license andre-licencing the code under MIT/Apache2 would require the approval
of all contributors of the crate.
On the other hand it doesn't integrate well with the principal
structures
DynamicImage
andImageBuffer
. That's why Idecided to re-implement the algorithm. I have to admit here are some
details in both implementations that I don't understand and where
I couldn't find an explanation. Hence I just did my own implementation
that I think I understand.
What about the performance?
I didn't check the performance (and didn't optimize it).
But opposed to the
blur
implementation the blurring finishesin a reasonable amount of time.
As soon as the interface is fixed (i.e. whether to accept
GenericImageView
orDynamicImage
) I can start to performance tweaks.What about tests?
I can't really imagine how to represent functional tests. However, I plan to
sigma=1
is a no op)Is the interface fixed?
Currently, I accept an
ImageBuffer
simply because it lets me operate on slices ratherthan using another interface to access pixels. However, it might make sense
to also accept an
DynamicImage
(can be easily accomplished) or aGenericImageView
(requires a complete rewrite of the algorithm).Will the "old" blur stay?
The fast blur algorithm is inaccurate but for enough for many applications. I would recommend
that the "real" blur stays. It is not much code anyways as it is implemented general convolution
with gaussian kernel.
License
I license past and future contributions under the dual MIT/Apache-2.0 license,
allowing licensees to choose either at their option.
Todos
fast_blur
in docs forblur
fast_blur
forDynamicImage
public_private_dependencies
jobkani
orquickcheck
)