|
1 | 1 | """
|
2 |
| - lbp_original(X; kwargs...) |
3 |
| - lbp_original(X, npoints, radius, interpolation=Linear(); kwargs...) |
4 |
| - lbp_original!(out, X, offsets; kwargs...) |
| 2 | + lbp_original(X; [rotation], [uniform_degree]) |
5 | 3 |
|
6 | 4 | Compute the local binary pattern of gray image `X` using the original method.
|
7 | 5 |
|
8 | 6 | # Arguments
|
9 | 7 |
|
10 | 8 | - `X::AbstractMatrix`: the input image matrix. For colorful images, one can manually convert
|
11 |
| - it to some monochrome space, e.g., `Gray` or the L-channel of `Lab`. |
12 |
| -
|
13 |
| -[2,4] proposes a generalized interpolation-based version using circular neighborhood matrix, |
14 |
| -it produces better result for `rotation=true` case but is usually much slower than the plain |
15 |
| -3x3 matrix version. The arguments for this version are: |
16 |
| -
|
17 |
| -- `npoints::Int`(4 ≤ npoints ≤ 8): the number of (uniform-spaced) neighborhood points. |
18 |
| -- `radius::Real`(radius ≥ 1.0): the radius of the circular. |
19 |
| -- `interpolation::Union{Degree, InterpolationType}=Linear()`: the interpolation method used |
20 |
| - to generate non-grid pixel value. In most cases, `Linear()` are good enough. One can also |
21 |
| - try other costly interpolation methods, e.g., `Cubic(Line(OnGrid()))`(also known as |
22 |
| - "bicubic"), `Lanczos()`. See also Interpolations.jl for more choices. |
23 |
| -
|
24 |
| -!!! note "neighborhood order differences" |
25 |
| - Different implementation might use different neighborhood orders; this will change the |
26 |
| - encoding result but will not change the overall distribution. For instance, |
27 |
| - `lbp_original(X)` differs from `lbp_original(X, 8, 1, Constant())` only by how `offsets` |
28 |
| - (see below) are ordered; the former uses column-major top-left to bottom-right 3x3 matrix |
29 |
| - order and the latter uses circular order. |
30 |
| -
|
31 |
| -Arguments for in-place version: |
32 |
| -
|
33 |
| -- `offsets::Tuple`: tuple of neighborhood matrix, e.g., `((0, 1), (0, -1), (1, 0), (-1, 0))` |
34 |
| - specifies the 4-neighborhood matrix. If `X isa Interpolations.AbstractInterpolation` |
35 |
| - holds, then the position values can be float numbers, e.g, `(0.7, 0.7)`. |
| 9 | + it to some monochrome space, e.g., `Gray`, the L-channel of `Lab`. One could also do |
| 10 | + channelwise LBP and then concatenate together. |
36 | 11 |
|
37 | 12 | # Parameters
|
38 | 13 |
|
@@ -61,12 +36,6 @@ julia> lbp_original(X)
|
61 | 36 | 0x68 0xa9 0x1b
|
62 | 37 | 0x28 0x6b 0x00
|
63 | 38 |
|
64 |
| -julia> lbp_original(X, 4, 1) # 4-neighbor with circular radius 1 |
65 |
| -3×3 $(Matrix{UInt8}): |
66 |
| - 0x01 0x01 0x00 |
67 |
| - 0x03 0x02 0x0e |
68 |
| - 0x02 0x07 0x00 |
69 |
| -
|
70 | 39 | julia> lbp_original(X; rotation=true)
|
71 | 40 | 3×3 $(Matrix{UInt8}):
|
72 | 41 | 0x03 0x01 0x00
|
@@ -128,21 +97,90 @@ unchanged because it only has ``2`` bit transitions.
|
128 | 97 | - [3] Pietikäinen, Matti, Timo Ojala, and Zelin Xu. "Rotation-invariant texture classification using feature distributions." _Pattern recognition_ 33.1 (2000): 43-52.
|
129 | 98 | - [4] T. Ojala, M. Pietikainen, and T. Maenpaa, “Multiresolution gray-scale and rotation invariant texture classification with local binary patterns,” _IEEE Trans. Pattern Anal. Machine Intell._, vol. 24, no. 7, pp. 971–987, Jul. 2002, doi: 10.1109/TPAMI.2002.1017623.
|
130 | 99 | """
|
131 |
| -function lbp_original(X::AbstractArray; kwargs...) |
| 100 | +function lbp_original(X::AbstractArray; rotation::Bool=false, uniform_degree::Union{Nothing,Int}=nothing) |
132 | 101 | # The original version [1] uses clockwise order; here we use anti-clockwise order
|
133 | 102 | # because Julia is column-major order. If we consider memory order differences then they
|
134 | 103 | # are equivalent.
|
135 | 104 | offsets = ((-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1))
|
136 |
| - lbp_original!(similar(X, UInt8), X, offsets; kwargs...) |
| 105 | + lookups = _build_lbp_original_lookup(UInt8, 8; rotation=rotation, uniform_degree=uniform_degree) |
| 106 | + lbp_original!(similar(X, UInt8), X, offsets, lookups) |
| 107 | +end |
| 108 | + |
| 109 | +""" |
| 110 | + lbp_original(X, npoints, radius, interpolation=Linear(); [rotation], [uniform_degree]) |
| 111 | +
|
| 112 | +Compute the local binary pattern of gray image `X` using the interpolation-based original |
| 113 | +method with circular neighborhood matrix. |
| 114 | +
|
| 115 | +This produces better result for `rotation=true` case but is usually slower than the plain |
| 116 | +3x3 matrix version `lbp_original(X)`. |
| 117 | +
|
| 118 | +# Arguments |
| 119 | +
|
| 120 | +- `npoints::Int`(4 ≤ npoints ≤ 32): the number of (uniform-spaced) neighborhood points. It |
| 121 | + is recommended to use one of {4, 8, 12, 16, 24}. |
| 122 | +- `radius::Real`(radius ≥ 1.0): the radius of the circular. Larger radius computes the |
| 123 | + pattern of a larger local window/block. |
| 124 | +- `interpolation::Union{Degree, InterpolationType}=Linear()`: the interpolation method used |
| 125 | + to generate non-grid pixel value. In most cases, `Linear()` are good enough. One can |
| 126 | + also try other costly interpolation methods, e.g., `Cubic(Line(OnGrid()))`(also known as |
| 127 | + "bicubic"), `Lanczos()`. See also Interpolations.jl for more choices. |
| 128 | +
|
| 129 | +!!! note "neighborhood order differences" |
| 130 | + Different implementation might use different neighborhood orders; this will change the |
| 131 | + encoding result but will not change the overall distribution. For instance, |
| 132 | + `lbp_original(X)` differs from `lbp_original(X, 8, 1, Constant())` only by how |
| 133 | + `offsets` (see below) are ordered; the former uses column-major top-left to |
| 134 | + bottom-right 3x3 matrix order and the latter uses circular order. |
| 135 | +
|
| 136 | +# Examples |
| 137 | +
|
| 138 | +```jldoctest; setup=:(using LocalBinaryPatterns) |
| 139 | +julia> X = [6 7 9; 5 6 3; 2 1 7] |
| 140 | +3×3 $(Matrix{Int}): |
| 141 | + 6 7 9 |
| 142 | + 5 6 3 |
| 143 | + 2 1 7 |
| 144 | +
|
| 145 | +julia> lbp_original(X, 4, 1) # 4-neighbor with circular radius 1 |
| 146 | +3×3 $(Matrix{UInt32}): |
| 147 | + 0x00000001 0x00000001 0x00000000 |
| 148 | + 0x00000003 0x00000002 0x0000000e |
| 149 | + 0x00000002 0x00000007 0x00000000 |
| 150 | +
|
| 151 | +julia> lbp_original(X, 4, 1; rotation=true) |
| 152 | +3×3 $(Matrix{UInt32}): |
| 153 | + 0x00000001 0x00000001 0x00000000 |
| 154 | + 0x00000003 0x00000001 0x00000007 |
| 155 | + 0x00000001 0x00000007 0x00000000 |
| 156 | +``` |
| 157 | +
|
| 158 | +""" |
| 159 | +function lbp_original( |
| 160 | + X::AbstractArray, npoints, radius, interpolation=Linear(); |
| 161 | + rotation::Bool=false, uniform_degree::Union{Nothing,Int}=nothing) |
| 162 | + interpolation = wrap_BSpline(interpolation) |
| 163 | + offsets = _circular_neighbor_offsets(npoints, radius) |
| 164 | + if interpolation == BSpline(Constant()) |
| 165 | + offsets = map(offsets) do o |
| 166 | + round.(Int, o, RoundToZero) |
| 167 | + end |
| 168 | + itp = X |
| 169 | + else |
| 170 | + itp = interpolate(X, interpolation) |
| 171 | + end |
| 172 | + # For the sake of type-stability, hardcode to UInt32 here. |
| 173 | + lookups = _build_lbp_original_lookup(UInt32, npoints, rotation=rotation, uniform_degree=uniform_degree) |
| 174 | + lbp_original!(similar(X, UInt32), itp, offsets, lookups) |
137 | 175 | end
|
| 176 | + |
138 | 177 | function lbp_original!(
|
139 | 178 | out,
|
140 | 179 | X::AbstractMatrix{T},
|
141 |
| - offsets::Tuple; |
142 |
| - rotation::Bool=false, |
143 |
| - uniform_degree::Union{Nothing,Int}=nothing, |
| 180 | + offsets::Tuple, |
| 181 | + lookups::Vector |
144 | 182 | ) where T<:Union{Real,Gray}
|
145 |
| - length(offsets) > 8 && throw(ArgumentError("length(offsets) >= 8 is not supported.")) |
| 183 | + length(offsets) > 32 && throw(ArgumentError("length(offsets)=$(length(offsets)) is expected to be no larger than 32.")) |
146 | 184 | outerR = CartesianIndices(X)
|
147 | 185 | r = CartesianIndex(
|
148 | 186 | ceil(Int, maximum(abs.(extrema(first.(offsets))))),
|
@@ -175,28 +213,26 @@ function lbp_original!(
|
175 | 213 | out[I] = rst
|
176 | 214 | end
|
177 | 215 |
|
178 |
| - # The building is cached and chained(if there are multiple encoding passes) thus the |
179 |
| - # cost is decreased to one brocasting to `getindex` and `setindex!`. |
180 |
| - encoding_table = build_LBP_encoding_table(UInt8; rotation=rotation, uniform_degree=uniform_degree) |
181 |
| - if !isnothing(encoding_table) |
182 |
| - @. out = encoding_table[out + 1] |
| 216 | + for F in lookups |
| 217 | + @. out = F[out] |
183 | 218 | end
|
184 | 219 |
|
185 | 220 | return out
|
186 | 221 | end
|
187 | 222 |
|
188 |
| -function lbp_original(X::AbstractArray, npoints, radius, interpolation=Linear(); kwargs...) |
189 |
| - # TODO(johnnychen94): support npoints=24 as [2, 4] indicate |
190 |
| - 4<= npoints <= 8 || throw(ArgumentError("currently only support 4<= npoints <=8, instead it is $(npoints).")) |
191 |
| - interpolation = wrap_BSpline(interpolation) |
192 |
| - offsets = _circular_neighbor_offsets(npoints, radius) |
193 |
| - if interpolation == BSpline(Constant()) |
194 |
| - offsets = map(offsets) do o |
195 |
| - round.(Int, o, RoundToZero) |
196 |
| - end |
197 |
| - itp = X |
198 |
| - else |
199 |
| - itp = interpolate(X, interpolation) |
| 223 | +function _build_lbp_original_lookup( |
| 224 | + ::Type{T}, nbits; |
| 225 | + rotation::Bool, |
| 226 | + uniform_degree::Union{Nothing,Int} |
| 227 | + ) where T <: Unsigned |
| 228 | + # TODO(johnnychen94): fuse these operation into one broadcasting could potentially give |
| 229 | + # better performance. |
| 230 | + lookups = [] |
| 231 | + if rotation |
| 232 | + push!(lookups, rotation_encoding_table(T, nbits)) |
| 233 | + end |
| 234 | + if !isnothing(uniform_degree) |
| 235 | + push!(lookups, uniform_encoding_table(T, nbits, uniform_degree)) |
200 | 236 | end
|
201 |
| - lbp_original!(similar(X, UInt8), itp, offsets; kwargs...) |
| 237 | + return lookups |
202 | 238 | end
|
0 commit comments