Skip to content

Commit e8bcf44

Browse files
committed
implement uniform encoding pass
1 parent b3544e6 commit e8bcf44

File tree

5 files changed

+96
-19
lines changed

5 files changed

+96
-19
lines changed

benchmark/benchmarks.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ tst_types = (N0f8, Float32, Gray{N0f8}, Gray{Float32})
1717
const SUITE = BenchmarkGroup()
1818

1919
alg_list = (( "Original", lbp_original),
20-
( "Rotation-Invariant", lbp_rotation_invariant),
2120
)
2221

2322
function add_algorithm_benchmark!(suite, img, alg_name, alg;

src/compat.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ if VERSION < v"1.5"
44
(x << ((sizeof(T) << 3 - 1) & k)) | (x >>> ((sizeof(T) << 3 - 1) & -k))
55
end
66
end
7+
8+
if VERSION < v"1.1"
9+
isnothing(x) = x === nothing
10+
end

src/lbp_original.jl

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@ neighborhood matrix.
77
88
# Parameters
99
10-
- `rotation=false`: set `true` to generate patterns that are invariant to rotation [3].
10+
The parameters control whether and the degree additional encoding passses are used to get
11+
patterns that are more robust to certain changes, e.g., rotation. The following lists are
12+
ordered as encoding order. For example, if `rotation=true` and `uniform_degree=2`, then
13+
rotation encoding will be applied first.
14+
15+
- `rotation=false`: set `true` to generate patterns that are invariant to rotation [3]. For
16+
example, pattern `0b00001101` is equivalent to `0b01000011` when `rotation=true`.
17+
- `uniform_degree`: the threshold number of pattern uniform degree. From [2] a typical
18+
choice is `2`.If it is `nothing`(default value) then no uniform encoding is applied.
1119
1220
# Examples
1321
@@ -29,6 +37,12 @@ julia> lbp_original(X; rotation=true)
2937
0x03 0x01 0x00
3038
0x0d 0x35 0x1b
3139
0x05 0x5b 0x00
40+
41+
julia> lbp_original(X; uniform_degree=2)
42+
3×3 $(Matrix{UInt8}):
43+
0xc0 0x40 0x00
44+
0x09 0x09 0x09
45+
0x09 0x09 0x00
3246
```
3347
3448
# Extended help
@@ -55,7 +69,19 @@ the same class because `bitrotate(0b01000011, -2) == 0b11010000`, thus both valu
5569
mapped to `0b00001101`. See also Eq.(8) in [2].
5670
5771
For 3x3 neighborhood matrix, applying rotation-invariant encoding decreases the possible
58-
number of binary patterns from 256 to 36.
72+
number of binary patterns from ``256`` to ``36``.
73+
74+
## Uniform encoding
75+
76+
Authors of [2] states that certain local binary patterns are fundamental properties of
77+
texture, providing the vast majority, sometimes over 90 percent, of all 3x3 patterns. Those
78+
patterns are called "uniform" as they contain very few spatial transitions. Uniform degree
79+
is an additional encoding pass that controls at what circumstances can we set the block to
80+
miscellaneous class.
81+
82+
For example, if `uniform_degree=2`, then `0b00001101` will be encoded as `9` (type
83+
miscellaneous) because it has ``3`` bit transitions, and `0b00001100` will be unchanged
84+
because it only has ``2`` bit transitions.
5985
6086
# References
6187
@@ -67,7 +93,8 @@ lbp_original(X::AbstractArray; kwargs...) = lbp_original!(similar(X, UInt8), X;
6793
function lbp_original!(
6894
out,
6995
X::AbstractMatrix{T};
70-
rotation=false
96+
rotation::Bool=false,
97+
uniform_degree::Union{Nothing,Int}=nothing,
7198
) where T<:Union{Real,Gray}
7299
# nearest interpolation, 3x3 neighborhood
73100

@@ -104,8 +131,10 @@ function lbp_original!(
104131
out[I] = rst
105132
end
106133

107-
if rotation
108-
encoding_table = build_rotation_invariant_encoding_table(UInt8)
134+
# The building is cached and chained(if there are multiple encoding passes) thus the
135+
# cost is decreased to one brocasting to `getindex` and `setindex!`.
136+
encoding_table = build_LBP_encoding_table(UInt8; rotation=rotation, uniform_degree=uniform_degree)
137+
if !isnothing(encoding_table)
109138
@. out = encoding_table[out + 1]
110139
end
111140

src/utils.jl

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
1-
# This implements the solver for Eq. (8) in the Ojala 2002 as lookup table.
2-
# Computing the encoding table using naive implementation is time-consuming,
3-
# since it's read-only, we simply cache the encoding table.
4-
const _rotation_invariant_encoding_tables = Dict()
51
const SupportedEncodingTypes = Union{UInt8, UInt16}
6-
function build_rotation_invariant_encoding_table(::Type{T}) where T<:SupportedEncodingTypes
7-
d = _rotation_invariant_encoding_tables
8-
haskey(d, T) && return d[T]
9-
d[T] = _freeze(T, _build_inverse_table(_bitrotate_quotation_space(T)))
10-
return d[T]
2+
const _LBP_encoding_table = Dict()
3+
function build_LBP_encoding_table(::Type{T};
4+
rotation::Bool,
5+
uniform_degree::Union{Nothing,Int}=nothing
6+
) where T<:SupportedEncodingTypes
7+
8+
d = _LBP_encoding_table
9+
p = (T, rotation, uniform_degree)
10+
haskey(d, p) && return d[p]
11+
12+
identity_lookup = identity.(typemin(T):typemax(T))
13+
rot_lookup = rotation ? _build_inverse_table(_bitrotate_quotation_space(T)) : identity_lookup
14+
uniform_lookup = !isnothing(uniform_degree) ? _uniform_encoding_table(T, uniform_degree) : identity_lookup
15+
16+
# Chaining multiple encoding passes into one lookup table so that we can move as
17+
# much computation to warm-up phase as we can.
18+
lookup = d[p] = _freeze(T, uniform_lookup[rot_lookup.+1])
19+
return lookup == identity_lookup ? nothing : lookup
1120
end
1221

13-
# Mathematically, this is the quotation space under circular bitshift of the N-bits binary
14-
# pattern space. For instance, 0b00001101 and 0b01000011 belong to the same equivalance
15-
# class.
16-
# TODO(johnnychen94): maybe support UInt32 and beyond by providing a more efficient implementation
1722
function _bitrotate_quotation_space(::Type{T}) where T<:SupportedEncodingTypes
23+
# Mathematically, this is the quotation space under circular bitshift of the N-bits binary
24+
# pattern space. For instance, 0b00001101 and 0b01000011 belong to the same equivalance
25+
# class.
26+
# TODO(johnnychen94): maybe support UInt32 and beyond by providing a more efficient implementation
27+
28+
# This implements the solver for Eq. (8) in the Ojala 2002 as lookup table.
29+
# Computing the encoding table using naive implementation is time-consuming,
30+
# since it's read-only, we simply cache the encoding table.
1831
s = Vector{T}[]
1932
for x in typemin(T):typemax(T)
2033
# without the following skipping mechanism actually runs faster
@@ -23,6 +36,18 @@ function _bitrotate_quotation_space(::Type{T}) where T<:SupportedEncodingTypes
2336
end
2437
Dict(minimum(c) => c for c in unique!(s))
2538
end
39+
40+
function _uniform_encoding_table(::Type{T}, degree::Int) where T<:SupportedEncodingTypes
41+
function _count_bit_transitions(x)
42+
count_ones(x << 7 & typemax(typeof(x))) + count_ones(x (x >> 1))
43+
end
44+
# Eq. (9) for Ojala 2002
45+
map(typemin(T):typemax(T)) do x
46+
n = _count_bit_transitions(x)
47+
ifelse(n > degree, 8sizeof(T)+1, x)
48+
end
49+
end
50+
2651
function _build_inverse_table(d::Dict{T,<:AbstractVector{T}}) where {T}
2752
id = Vector{T}(undef, typemax(T)-typemin(T)+1)
2853
for k in keys(d)

test/lbp_original.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,24 @@
4343
@test size(out) == (3, 3)
4444
@test out == ref_out
4545
end
46+
47+
@testset "Uniform encoding" begin
48+
X = [6 7 9; 5 6 3; 2 1 7]
49+
ref_out = [192 64 0; 9 9 9; 9 9 0]
50+
51+
out = lbp_original(X; rotation=false, uniform_degree=2)
52+
@test eltype(out) == UInt8
53+
@test size(out) == (3, 3)
54+
@test out == ref_out
55+
end
56+
57+
@testset "Rotation Invariant, Uniform encoding" begin
58+
X = [6 7 9; 5 6 3; 2 1 7]
59+
ref_out = [3 1 0; 9 9 9; 9 9 0]
60+
61+
out = lbp_original(X; rotation=true, uniform_degree=2)
62+
@test eltype(out) == UInt8
63+
@test size(out) == (3, 3)
64+
@test out == ref_out
65+
end
4666
end

0 commit comments

Comments
 (0)