Skip to content
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

also use the streaming max-min filter for maximum/minimum #193

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/demos/filters/median_filter.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# ---
# title: Custom median filters
# id: median_filter_example
# cover: assets/median.gif
# author: Johnny Chen
# date: 2020-09-23
Expand Down
57 changes: 38 additions & 19 deletions docs/demos/filters/min_max_filter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,49 @@
# ---

# In this tutorial we see how can we can effectively use max and min filter to distinguish
# between smooth and texture edges in grayscale images.
# between smooth and texture edges in grayscale images. The example in this demo comes from [1].

# We will use the [`mapwindow`](@ref) function in `ImageFiltering.jl` which provides a general
# functionality to apply any function to the window around each pixel.
# functionality to apply any function to the window around each pixel.
# [Custom median filters](@ref median_filter_example) is another usage example of `mapwindow`.

using ImageCore, ImageShow, ImageFiltering
using MappedArrays
using TestImages

img = Gray.(testimage("house");) # Original Image

# We can use the `minimum` function to compute the minimum of the grayscale values in the given
# matrix or array. For example:
minimum([Gray(0.7),Gray(0.5),Gray(0.0)]) # Should return Gray(0.0) i.e black.
#
filter_size = (15, 15)
## Using the `mapwindow` function, we create an image of the local minimum.
## `mapwindow` maps the given function over a moving window of given size.
img_min = mapwindow(minimum, img, filter_size)
## Similarly for maximum
img_max = mapwindow(maximum, img, filter_size)
## The max(min) filter
img_max_min = mapwindow(maximum, img_min, filter_size)
## The min(max) filter
img_min_max = mapwindow(minimum, img_max, filter_size)
img = Gray.(testimage("house")); # Original Image

# We need four statistics for our demo, they are: the local minimum, maximum, min-max, max-min of
# the input image. We can use `mapwindow` to get them.

window_size = (15, 15)
img_min = mapwindow(minimum, img, window_size)
img_max = mapwindow(maximum, img, window_size)
img_max_min = mapwindow(maximum, img_min, window_size)
img_min_max = mapwindow(minimum, img_max, window_size)
mosaicview(img_min, img_max, img_max_min, img_min_max; nrow=1)

# When `f` is one of `maximum`, `minimum` and `maximum`, `mapwindow(f, img, window_size)` will use
# a streaming version Lemire max-min filter[2] to do the filtering work. This is more efficient
# than a plain maximum implementation.

# ```julia
# using BenchmarkTools
# f(x) = minimum(x) # do note that `f !== minimum`
# @btime mapwindow(f, $img, window_size) # 47.508 ms (202831 allocations: 12.91 MiB)
# @btime mapwindow(minimum, $img, window_size) # 13.216 ms (58 allocations: 1.75 MiB)
# ```

# `mapwindow` with `maximum`/`minimum` actually only uses the partial results from `mapwindow` with
# `extrema`, so if we need both results, we could half the computation by directly calling `extrema`
# and doing a manual splitting. Also, we could use a `MappedArrays.mappedarray` view to reduce the
# allocation.

img_extrema = mapwindow(extrema, img, window_size) # half the computation
img_min = mappedarray(first, img_extrema) # 0 allocation
img_max = mappedarray(last, img_extrema) # 0 allocation
#md nothing #hide

# Now that we are done with the basic filtered images, we proceed to the next part
# which is edge detection using these filters.

Expand Down Expand Up @@ -72,7 +89,9 @@ mosaicview(img, ramp, edge, edge_smoothed; nrow=2)

# # References

# Verbeek, P. W., Vrooman, H. A., & Van Vliet, L. J. (1988). [Low-level image processing by max-min filters](https://core.ac.uk/download/pdf/194053536.pdf). Signal Processing, 15(3), 249-258.
# [1] Verbeek, P. W., Vrooman, H. A., & Van Vliet, L. J. (1988). [Low-level image processing by max-min filters](https://core.ac.uk/download/pdf/194053536.pdf). Signal Processing, 15(3), 249-258.

# [2] Lemire, D. (2006). [Streaming maximum-minimum filter using no more than three comparisons per element](https://lemire.me/en/publication/arxiv0610046/). arXiv preprint cs/0610046.

## save covers #src
using ImageMagick #src
Expand Down
3 changes: 3 additions & 0 deletions src/mapwindow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ end

mapwindow(::typeof(extrema), A::AbstractArray, window::Dims) = extrema_filter(A, window)
mapwindow(::typeof(extrema), A::AbstractVector, window::Integer) = extrema_filter(A, (window,))
# it is still faster than plain loop with maximum/minimum
mapwindow(::typeof(minimum), A::AbstractArray, window::Dims) = map(first, extrema_filter(A, window))
mapwindow(::typeof(maximum), A::AbstractArray, window::Dims) = map(last, extrema_filter(A, window))

# Max-min filter

Expand Down