|
1 | 1 | module GaborKernels
|
2 | 2 |
|
3 |
| -export Gabor |
| 3 | +export Gabor, LogGabor, LogGaborComplex |
4 | 4 |
|
5 | 5 | """
|
6 | 6 | Gabor(kernel_size; kwargs...)
|
@@ -145,6 +145,128 @@ end
|
145 | 145 | return exp(-(xr^2 + yr^2)/(2σ^2)) * cis(xr*λ_scaled + ψ)
|
146 | 146 | end
|
147 | 147 |
|
| 148 | + |
| 149 | +""" |
| 150 | + LogGaborComplex(kernel_size; ω, θ, σω=1, σθ=1) |
| 151 | + LogGaborComplex(kernel_axes; ω, θ, σω=1, σθ=1) |
| 152 | +
|
| 153 | +Generates the 2-D Log Gabor kernel in frequency space by `Complex(r, a)`, where `r` and `a` |
| 154 | +are the frequency and angular components, respectively. |
| 155 | +
|
| 156 | +More detailed documentation can be found in the `r * a` version [`LogGabor`](@ref). |
| 157 | +""" |
| 158 | +struct LogGaborComplex{T, TP,R<:AbstractUnitRange} <: AbstractMatrix{T} |
| 159 | + ax::Tuple{R,R} |
| 160 | + ω::TP |
| 161 | + θ::TP |
| 162 | + σω::TP |
| 163 | + σθ::TP |
| 164 | + |
| 165 | + # cache values |
| 166 | + ω_denom::TP # 1/(2(log(σω/ω))^2) |
| 167 | + θ_denom::TP # 1/(2σθ^2) |
| 168 | + function LogGaborComplex{T,TP,R}(ax::Tuple{R,R}, ω::TP, θ::TP, σω::TP, σθ::TP) where {T,TP,R} |
| 169 | + σω > 0 || throw(ArgumentError("`σω` should be positive: $σω")) |
| 170 | + σθ > 0 || throw(ArgumentError("`σθ` should be positive: $σθ")) |
| 171 | + new{T,TP,R}(ax, ω, θ, σω, σθ, 1/(2(log(σω/ω))^2), 1/(2σθ^2)) |
| 172 | + end |
| 173 | +end |
| 174 | +function LogGaborComplex(size_or_axes::Tuple; ω::Real, θ::Real, σω::Real=1, σθ::Real=1) |
| 175 | + params = float.(promote(ω, θ, σω, σθ)) |
| 176 | + T = typeof(params[1]) |
| 177 | + ax = _to_axes(size_or_axes) |
| 178 | + LogGaborComplex{Complex{T}, T, typeof(first(ax))}(ax, params...) |
| 179 | +end |
| 180 | + |
| 181 | +@inline Base.size(kern::LogGaborComplex) = map(length, kern.ax) |
| 182 | +@inline Base.axes(kern::LogGaborComplex) = kern.ax |
| 183 | + |
| 184 | +@inline function Base.getindex(kern::LogGaborComplex, x::Int, y::Int) |
| 185 | + ω_denom, θ_denom = kern.ω_denom, kern.θ_denom |
| 186 | + # normalize: from reference [1] |
| 187 | + x, y = (x, y)./size(kern) |
| 188 | + ω = sqrt(x^2 + y^2) # this is faster than hypot(x, y) |
| 189 | + θ = atan(y, x) |
| 190 | + r = exp((-(log(ω/kern.ω))^2)*ω_denom) # radial component |
| 191 | + a = exp((-(θ-kern.θ).^2)*θ_denom) # angular component |
| 192 | + return r + a*im |
| 193 | +end |
| 194 | + |
| 195 | + |
| 196 | +""" |
| 197 | + LogGabor(kernel_size; ω, θ, σω=1, σθ=1) |
| 198 | + LogGabor(kernel_axes; ω, θ, σω=1, σθ=1) |
| 199 | +
|
| 200 | +Generates the 2-D Log Gabor kernel in frequency space by `r * a`, where `r` and `a` are the |
| 201 | +frequency and angular components, respectively. |
| 202 | +
|
| 203 | +See also [`LogGabor`](@ref) for the `Complex(r, a)` version. |
| 204 | +
|
| 205 | +# Arguments |
| 206 | +
|
| 207 | +- `kernel_size::Dims{2}`: the Log Gabor kernel size. The axes at each dimension will be |
| 208 | + `-r:r` if the size is odd. |
| 209 | +- `kernel_axes::NTuple{2, <:AbstractUnitRange}`: the axes of the Log Gabor kernel. |
| 210 | +
|
| 211 | +# Keywords |
| 212 | +
|
| 213 | +- `ω`: the center frequency. |
| 214 | +- `θ`: the center orientation. |
| 215 | +- `σω=1`: scale component for `ω`. Larger `σω` makes the filter more sensitive to center region. |
| 216 | +- `σθ=1`: scale component for `θ`. Larger `σθ` makes the filter less sensitive to orientation. |
| 217 | +
|
| 218 | +# Examples |
| 219 | +
|
| 220 | +To apply log gabor filter `g` on image `X`, one need to use convolution theorem, i.e., |
| 221 | +`conv(A, K) == ifft(fft(A) .* fft(K))`. Because this `LogGabor` function generates Log Gabor |
| 222 | +kernel directly in frequency space, we don't need to apply `fft(K)` here: |
| 223 | +
|
| 224 | +```jldoctest |
| 225 | +julia> using ImageFiltering, FFTW, TestImages, ImageCore |
| 226 | +
|
| 227 | +julia> img = TestImages.shepp_logan(256); |
| 228 | +
|
| 229 | +julia> kern = Kernel.LogGabor(size(img); ω=1/6, θ=0); |
| 230 | +
|
| 231 | +julia> g_img = ifft(centered(fft(channelview(img))) .* fftshift(kern)); # apply convolution theorem |
| 232 | +
|
| 233 | +julia> @. Gray(abs(g_img)); |
| 234 | +
|
| 235 | +``` |
| 236 | +
|
| 237 | +# Extended help |
| 238 | +
|
| 239 | +Mathematically, log gabor filter is defined in frequency space as the product of |
| 240 | +its frequency component `r` and angular component `a`: |
| 241 | +
|
| 242 | +```math |
| 243 | +r(\\omega, \\theta) = \\exp(-\\frac{(\\log(\\omega/\\omega_0))^2}{2\\sigma_\\omega^2}) \\ |
| 244 | +a(\\omega, \\theta) = \\exp(-\\frac{(\\theta - \\theta_0)^2}{2\\sigma_\\theta^2}) |
| 245 | +``` |
| 246 | +
|
| 247 | +# References |
| 248 | +
|
| 249 | +- [1] [What Are Log-Gabor Filters and Why Are They |
| 250 | + Good?](https://www.peterkovesi.com/matlabfns/PhaseCongruency/Docs/convexpl.html) |
| 251 | +- [2] Kovesi, Peter. "Image features from phase congruency." _Videre: Journal of computer |
| 252 | + vision research_ 1.3 (1999): 1-26. |
| 253 | +- [3] Field, David J. "Relations between the statistics of natural images and the response |
| 254 | + properties of cortical cells." _Josa a_ 4.12 (1987): 2379-2394. |
| 255 | +""" |
| 256 | +struct LogGabor{T, AT<:LogGaborComplex} <: AbstractMatrix{T} |
| 257 | + complex_data::AT |
| 258 | +end |
| 259 | +LogGabor(complex_data::AT) where AT<:LogGaborComplex = LogGabor{eltype(AT), AT}(complex_data) |
| 260 | +LogGabor(size_or_axes::Tuple; kwargs...) = LogGabor(LogGaborComplex(size_or_axes; kwargs...)) |
| 261 | + |
| 262 | +@inline Base.size(kern::LogGabor) = size(kern.complex_data) |
| 263 | +@inline Base.axes(kern::LogGabor) = axes(kern.complex_data) |
| 264 | +Base.@propagate_inbounds function Base.getindex(kern::LogGabor, inds::Int...) |
| 265 | + v = kern.complex_data[inds...] |
| 266 | + return real(v) * imag(v) |
| 267 | +end |
| 268 | + |
| 269 | + |
148 | 270 | # Utils
|
149 | 271 |
|
150 | 272 | function _to_axes(sz::Dims)
|
|
0 commit comments