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