Skip to content

Commit a133077

Browse files
authored
add fuzzy edge detector documentation application (#6)
1 parent 71ca709 commit a133077

File tree

7 files changed

+141
-2
lines changed

7 files changed

+141
-2
lines changed

docs/Project.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
DocThemeIndigo = "8bac0ac5-51bf-41f9-885e-2bf1ac2bec5f"
33
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
44
FuzzyLogic = "271df9f8-4390-4196-9d4f-bdd0b67035b3"
5+
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
6+
ImageFiltering = "6a3955dd-da59-5b1f-98d4-e7296123deb5"
7+
ImageShow = "4e3cecfd-b093-5904-9786-8bbb286a6a31"
58
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
69
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
10+
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"
711

812
[compat]
913
DocThemeIndigo = "0.1.3"
14+
ImageFiltering = "0.7.3"
1015
Literate = "2.14"
1116
Plots = "1.38"
17+
TestImages = "1.7"

docs/make.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ makedocs(;
112112
"Build a Mamdani inference system" => "tutorials/mamdani.md",
113113
"Build a Sugeno inference system" => "tutorials/sugeno.md",
114114
],
115+
"Applications" => [
116+
"Edge detection" => "applications/edge_detector.md",
117+
],
115118
"API" => [
116119
"Inference system API" => [
117120
"Types" => "api/fis.md",
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#=
2+
# Fuzzy edge detector
3+
4+
This tutorial shows how fuzzy logic can be applied to image processing. It showcases how
5+
`FuzzyLogic.jl` seamlessly composes with common Julia image processing libraries and works
6+
out-of-the box.
7+
8+
This tutorial builds a fuzzy edge detector and it is inspired from the matlab tutorial
9+
available [here](https://www.mathworks.com/help/fuzzy/fuzzy-logic-image-processing.html).
10+
11+
DOWNLOAD_NOTE
12+
13+
## Introduction
14+
15+
We want to design an edge detector. That is, a function that takes an image as input and finds
16+
the edges in the image.
17+
Our function should produce a new image, with edges highlighted in black and flat areas in white.
18+
19+
First, let's load the image processing tools we need in this tutorial.
20+
=#
21+
22+
using TestImages, ImageFiltering, ImageShow, ImageCore
23+
24+
img = Gray.(testimage("house"))
25+
26+
#=
27+
Next, we need to model our problem. When we cross an edge, we have a transition from a clearly delimited area to another.
28+
Hence, a pixel is on an edge if moving in its neighborhood we see some change in intensity.
29+
If, on the other hand, in the pixel neighborhood there is no change, then it belongs to a flat area.
30+
31+
Hence, to detect edges we need to compute the gradient of the image at each pixel.
32+
This can be done using `imgradients` from [ImageFiltering](https://github.com/JuliaImages/ImageFiltering.jl).
33+
This function will return two images, one containg the gradient x-component at each pixel, and one containing the y-component at each pixel.
34+
For better visualization, these gradient images are renormalized so that their maximum in absolute value is ``1``.
35+
=#
36+
37+
img_y, img_x = imgradients(img, KernelFactors.sobel)
38+
39+
img_y /= Float64(maximum(abs.(img_y)))
40+
img_x /= Float64(maximum(abs.(img_x)))
41+
42+
img_y
43+
#-
44+
img_x
45+
46+
#=
47+
## Fuzzy system design
48+
49+
Now we want to design a fuzzy system that takes as input the gradient x- and y- components and produces as output the intensity of the new image.
50+
Particularly, in the output image we want to plot flat areas in white (intensity close to ``1``) and edges in black (intensity close to ``0``).
51+
52+
Based on our previous discussion, a pixel belongs to a flat area if it has zero gradient (i.e. both x- and y-components are zero).
53+
If the gradient is non-zero (either x- or y-component is non-zero) then it belongs to an edge.
54+
Hence for our fuzzy edge detector we can use the following rules
55+
56+
- If the gradient x-component is zero and the gradient y-component is zero, then the output intensity is white.
57+
- If the gradient x-component is nonzero or the gradient y-compnent is non-zero, then the output intensity is black.
58+
59+
Hence, for the input, we will use a single membership function `zero`, which is a sharp Gaussian centered at zero.
60+
For the outupt, we have two membership functions, `black` and `white`.
61+
Recalling that a black pixel means intensity zero and a white pixel intensity one, we will use for `black` a linear membership function, that decreases from ``1``
62+
to ``0`` as the intensity increases. Similarly, for `white` we can use a linear membership function that increases as the intensity increases.
63+
64+
We can now implement and visualize our inference system.
65+
=#
66+
67+
using FuzzyLogic, Plots
68+
69+
fis = @mamfis function edge_detector(dx, dy)::Iout
70+
dx := begin
71+
domain = -1:1
72+
zero = GaussianMF(0.0, 0.1)
73+
end
74+
75+
dy := begin
76+
domain = -1:1
77+
zero = GaussianMF(0.0, 0.1)
78+
end
79+
80+
Iout := begin
81+
domain = 0:1
82+
black = LinearMF(0.7, 0.0)
83+
white = LinearMF(0.1, 1.0)
84+
end
85+
86+
dx == zero && dy == zero --> Iout == white
87+
dx != zero || dy != zero --> Iout == black
88+
end
89+
plot(fis)
90+
91+
#-
92+
93+
plot(plot(fis, :dx), plot(fis, :Iout), layout = (1, 2))
94+
95+
#=
96+
We are now ready to apply our fuzzy edge detector to the input image.
97+
We will create a new image `Iout` and assign to each pixel the intensity value computed with our fuzzy system.
98+
=#
99+
Iout = copy(img)
100+
101+
for idx in eachindex(Iout)
102+
Iout[idx] = fis(dx = img_x[idx], dy = img_y[idx])[:Iout]
103+
end
104+
Iout

src/evaluation.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ function (fr::FuzzyRelation)(fis::AbstractFuzzySystem,
55
memberships(fis.inputs[fr.subj])[fr.prop](inputs[fr.subj])
66
end
77

8+
function (fr::FuzzyNegation)(fis::AbstractFuzzySystem,
9+
inputs::T)::float(eltype(T)) where {T <: NamedTuple}
10+
1 - memberships(fis.inputs[fr.subj])[fr.prop](inputs[fr.subj])
11+
end
12+
813
function (fa::FuzzyAnd)(fis::AbstractFuzzySystem, inputs)
914
fis.and(fa.left(fis, inputs), fa.right(fis, inputs))
1015
end

src/parser.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ function parse_antecedent(ant)
236236
return Expr(:call, :FuzzyOr, parse_antecedent(left), parse_antecedent(right))
237237
elseif @capture(ant, subj_==prop_)
238238
return Expr(:call, :FuzzyRelation, QuoteNode(subj), QuoteNode(prop))
239+
elseif @capture(ant, subj_!=prop_)
240+
return Expr(:call, :FuzzyNegation, QuoteNode(subj), QuoteNode(prop))
239241
else
240242
throw(ArgumentError("Invalid premise $ant"))
241243
end

src/plotting.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,13 @@ end
8080
@series if length(idx) == 1
8181
rel = ants[first(idx)]
8282
title := string(rel)
83-
var.mfs[predicate(rel)], var.domain
83+
# TODO: use own recipes for negation and relation
84+
if rel isa FuzzyNegation
85+
legend --> false
86+
x -> 1 - var.mfs[predicate(rel)](x), low(var.domain), high(var.domain)
87+
else
88+
var.mfs[predicate(rel)], var.domain
89+
end
8490
else
8591
grid --> false
8692
axis --> false

src/rules.jl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ Base.show(io::IO, fr::FuzzyRelation) = print(io, fr.subj, " is ", fr.prop)
1515
subject(fr::FuzzyRelation) = fr.subj
1616
predicate(fr::FuzzyRelation) = fr.prop
1717

18+
"""
19+
Describes a fuzzy relation like "food is good".
20+
"""
21+
struct FuzzyNegation <: AbstractFuzzyProposition
22+
"subject of the relation."
23+
subj::Symbol
24+
"property of the relation."
25+
prop::Symbol
26+
end
27+
Base.show(io::IO, fr::FuzzyNegation) = print(io, fr.subj, " is not ", fr.prop)
28+
subject(fr::FuzzyNegation) = fr.subj
29+
predicate(fr::FuzzyNegation) = fr.prop
30+
1831
"""
1932
Describes the conjuction of two propositions.
2033
"""
@@ -60,5 +73,5 @@ function Base.:(==)(r1::FuzzyRule, r2::FuzzyRule)
6073
end
6174

6275
# utilities
63-
leaves(fr::FuzzyRelation) = (fr,)
76+
leaves(fr::Union{FuzzyNegation, FuzzyRelation}) = (fr,)
6477
leaves(fp::AbstractFuzzyProposition) = [leaves(fp.left)..., leaves(fp.right)...]

0 commit comments

Comments
 (0)