From 41a1a0bbe58a30d6abd42bada3c60ffc00ac8698 Mon Sep 17 00:00:00 2001 From: "Jake W. Ireland" Date: Fri, 30 Oct 2020 00:58:05 +1300 Subject: [PATCH] Some performance improvements Including concrete types where possible for type stability, and array comprehension instead of `map` where performant. Minor formatting changes. Addresses #26. --- src/AdaBoost.jl | 148 +++++++++++++++++++++-------------------- src/HaarLikeFeature.jl | 104 ++++++++++++++--------------- src/IntegralImage.jl | 25 ++++--- src/Utils.jl | 70 ++++++------------- test/runtests.jl | 17 ++--- 5 files changed, 168 insertions(+), 196 deletions(-) diff --git a/src/AdaBoost.jl b/src/AdaBoost.jl index b438c5533..63c7f1b45 100755 --- a/src/AdaBoost.jl +++ b/src/AdaBoost.jl @@ -18,27 +18,31 @@ using ProgressMeter: @showprogress, Progress, next! function get_feature_votes( positive_path::AbstractString, negative_path::AbstractString, - num_classifiers::Integer=Int32(-1), - min_feature_width::Integer=Int32(1), - max_feature_width::Integer=Int32(-1), - min_feature_height::Integer=Int32(1), - max_feature_height::Integer=Int32(-1); + num_classifiers::Integer=-one(Int32), + min_feature_width::Integer=one(Int32), + max_feature_width::Integer=-one(Int32), + min_feature_height::Integer=one(Int32), + max_feature_height::Integer=-one(Int32); scale::Bool = false, scale_to::Tuple = (Int32(200), Int32(200)) - ) + )::Tuple{Matrix{Int8}, Array{HaarLikeObject, 1}} # return a matrix of votes and a list of features #this transforms everything to maintain type stability - s1 ,s2 = scale_to + s₁, s₂ = scale_to min_feature_width, max_feature_width, min_feature_height, - max_feature_height,s1,s2 = promote(min_feature_width, - max_feature_width, - min_feature_height, - max_feature_height,s1,s2) - scale_to = (s1,s2) + max_feature_height, s₁, s₂ = promote( + min_feature_width, + max_feature_width, + min_feature_height, + max_feature_height, + s₁, + s₂) + scale_to = (s₁, s₂) _Int = typeof(max_feature_width) + # get number of positive and negative images (and create a global variable of the total number of images——global for the @everywhere scope) positive_files = filtered_ls(positive_path) negative_files = filtered_ls(negative_path) @@ -73,8 +77,8 @@ function get_feature_votes( map(partition(image_files, batch_size)) do batch ii_imgs = load_image.(batch; scale=scale, scale_to=scale_to) @threads for t in 1:length(batch) - # votes[:, num_processed+t] .= get_vote.(features, Ref(ii_imgs[t])) - map!(f -> get_vote(f, ii_imgs[t]), view(votes, :, num_processed + t), features) + votes[:, num_processed + t] .= get_vote.(features, Ref(ii_imgs[t])) + # map!(f -> get_vote(f, ii_imgs[t]), view(votes, :, num_processed + t), features) next!(p) # increment progress bar end num_processed += length(batch) @@ -84,40 +88,13 @@ function get_feature_votes( return votes, features end -""" - learn( - positive_iis::AbstractArray, - negative_iis::AbstractArray, - num_classifiers::Int64=-1, - min_feature_width::Int64=1, - max_feature_width::Int64=-1, - min_feature_height::Int64=1, - max_feature_height::Int64=-1 - ) ->::Array{HaarLikeObject,1} - -The boosting algorithm for learning a query online. T hypotheses are constructed, each using a single feature. -The final hypothesis is a weighted linear combination of the T hypotheses, where the weights are inversely proportional to the training errors. -This function selects a set of classifiers. Iteratively takes the best classifiers based on a weighted error. - -# Arguments - -- `positive_iis::AbstractArray`: List of positive integral image examples -- `negative_iis::AbstractArray`: List of negative integral image examples -- `num_classifiers::Integer`: Number of classifiers to select. -1 will use all classifiers -- `min_feature_width::Integer`: the minimum width of the feature -- `max_feature_width::Integer`: the maximum width of the feature -- `min_feature_height::Integer`: the minimum height of the feature -- `max_feature_width::Integer`: the maximum height of the feature - -# Returns `classifiers::Array{HaarLikeObject, 1}`: List of selected features -""" function learn( positive_path::AbstractString, negative_path::AbstractString, - features::AbstractArray, - votes::AbstractArray, - num_classifiers::Integer=-1 - ) + features::Array{HaarLikeObject, 1}, + votes::Matrix{Int8}, + num_classifiers::Integer=-one(Int32) + )::Array{HaarLikeObject, 1} # get number of positive and negative images (and create a global variable of the total number of images——global for the @everywhere scope) num_pos = length(filtered_ls(positive_path)) @@ -139,33 +116,33 @@ function learn( notify_user("Selecting classifiers...") # select classifiers - classifiers = [] + classifiers = HaarLikeObject[] p = Progress(num_classifiers, 1) # minimum update interval: 1 second for t in 1:num_classifiers - # classification_errors = zeros(length(feature_indices)) classification_errors = Matrix{Float64}(undef, length(feature_indices), 1) # normalize the weights $w_{t,i}\gets \frac{w_{t,i}}{\sum_{j=1}^n w_{t,j}}$ weights = float(weights) / sum(weights) # For each feature j, train a classifier $h_j$ which is restricted to using a single feature. The error is evaluated with respect to $w_j,\varepsilon_j = \sum_i w_i\left|h_j\left(x_i\right)-y_i\right|$ - map!(view(classification_errors, :), 1:length(feature_indices)) do j - sum(1:num_imgs) do img_idx - labels[img_idx] ≠ votes[feature_indices[j], img_idx] ? weights[img_idx] : zero(Float64) - end - end + classification_errors[:] .= [sum([labels[img_idx] !== votes[feature_indices[j], img_idx] ? weights[img_idx] : zero(Float64) for img_idx in 1:num_imgs]) for j in 1:length(feature_indices)] + # map!(view(classification_errors, :), 1:length(feature_indices)) do j + # sum(1:num_imgs) do img_idx + # labels[img_idx] !== votes[feature_indices[j], img_idx] ? weights[img_idx] : zero(Float64) + # end + # end # choose the classifier $h_t$ with the lowest error $\varepsilon_t$ best_error, min_error_idx = findmin(classification_errors) best_feature_idx = feature_indices[min_error_idx] + best_feature = features[best_feature_idx] # set feature weight - best_feature = features[best_feature_idx] feature_weight = 0.5 * log((1 - best_error) / best_error) # β best_feature.weight = feature_weight classifiers = push!(classifiers, best_feature) # update image weights $w_{t+1,i}=w_{t,i}\beta_{t}^{1-e_i}$ - weights = map(i -> labels[i] ≠ votes[best_feature_idx, i] ? weights[i] * sqrt((1 - best_error) / best_error) : weights[i] * sqrt(best_error / (1 - best_error)), 1:num_imgs) + weights .= [labels[i] !== votes[best_feature_idx, i] ? weights[i] * sqrt((1 - best_error) / best_error) : weights[i] * sqrt(best_error / (1 - best_error)) for i in 1:num_imgs] # remove feature (a feature can't be selected twice) filter!(e -> e ∉ best_feature_idx, feature_indices) # note: without unicode operators, `e ∉ [a, b]` is `!(e in [a, b])` @@ -179,17 +156,44 @@ function learn( end +""" + learn( + positive_iis::AbstractArray, + negative_iis::AbstractArray, + num_classifiers::Int64=-1, + min_feature_width::Int64=1, + max_feature_width::Int64=-1, + min_feature_height::Int64=1, + max_feature_height::Int64=-1 + ) ->::Array{HaarLikeObject,1} + +The boosting algorithm for learning a query online. T hypotheses are constructed, each using a single feature. +The final hypothesis is a weighted linear combination of the T hypotheses, where the weights are inversely proportional to the training errors. +This function selects a set of classifiers. Iteratively takes the best classifiers based on a weighted error. + +# Arguments + +- `positive_iis::AbstractArray`: List of positive integral image examples +- `negative_iis::AbstractArray`: List of negative integral image examples +- `num_classifiers::Integer`: Number of classifiers to select. -1 will use all classifiers +- `min_feature_width::Integer`: the minimum width of the feature +- `max_feature_width::Integer`: the maximum width of the feature +- `min_feature_height::Integer`: the minimum height of the feature +- `max_feature_width::Integer`: the maximum height of the feature + +# Returns `classifiers::Array{HaarLikeObject, 1}`: List of selected features +""" function learn( positive_path::AbstractString, negative_path::AbstractString, - num_classifiers::Integer=-1, - min_feature_width::Integer=1, - max_feature_width::Integer=-1, - min_feature_height::Integer=1, - max_feature_height::Integer=-1; + num_classifiers::Int64=-1, + min_feature_width::Int64=1, + max_feature_width::Int64=-1, + min_feature_height::Int64=1, + max_feature_height::Int64=-1; scale::Bool = false, scale_to::Tuple = (200, 200) -)::Array{HaarLikeObject,1} +)::Array{HaarLikeObject, 1} votes, features = get_feature_votes( positive_path, @@ -232,15 +236,15 @@ Iteratively creates the Haar-like feautures - `features::AbstractArray`: an array of Haar-like features found for an image """ function create_features( - img_height::Integer, - img_width::Integer, - min_feature_width::Integer, - max_feature_width::Integer, - min_feature_height::Integer, - max_feature_height::Integer -) + img_height::Int64, + img_width::Int64, + min_feature_width::Int64, + max_feature_width::Int64, + min_feature_height::Int64, + max_feature_height::Int64 +)::Array{HaarLikeObject, 1} notify_user("Creating Haar-like features...") - features = [] + features = HaarLikeObject[] if img_width < max_feature_width || img_height < max_feature_height error(""" @@ -249,10 +253,10 @@ function create_features( end for feature in values(feature_types) # (feature_types are just tuples) - feature_start_width = max(min_feature_width, feature[1]) - for feature_width in range(feature_start_width, stop=max_feature_width, step=feature[1]) - feature_start_height = max(min_feature_height, feature[2]) - for feature_height in range(feature_start_height, stop=max_feature_height, step=feature[2]) + feature_start_width = max(min_feature_width, first(feature)) + for feature_width in range(feature_start_width, stop=max_feature_width, step=first(feature)) + feature_start_height = max(min_feature_height, last(feature)) + for feature_height in range(feature_start_height, stop=max_feature_height, step=last(feature)) for x in 1:(img_width - feature_width) for y in 1:(img_height - feature_height) push!(features, HaarLikeObject(feature, (x, y), feature_width, feature_height, 0, 1)) diff --git a/src/HaarLikeFeature.jl b/src/HaarLikeFeature.jl index 9135d316e..34f55bf8e 100755 --- a/src/HaarLikeFeature.jl +++ b/src/HaarLikeFeature.jl @@ -3,11 +3,9 @@ exec julia --project="$(realpath $(dirname $0))/../" "${BASH_SOURCE[0]}" "$@" -e 'include(popfirst!(ARGS))' \ "${BASH_SOURCE[0]}" "$@" =# - - const feature_types = ( - two_vertical = (1, 2), + two_vertical = (1, 2), two_horizontal = (2, 1), three_horizontal = (3, 1), three_vertical = (1, 3), @@ -15,14 +13,14 @@ const feature_types = ( ) abstract type HaarFeatureAbstractType end -# abstract type AbstractHaarLikeObject <: HaarFeatureAbstractType end """ - mutable struct HaarLikeObject{I<:Integer,F<:AbstractFloat} + mutable struct HaarLikeObject{I <: Integer, F <: AbstractFloat} Struct representing a Haar-like feature. """ -mutable struct HaarLikeObject{I<:Integer,F<:AbstractFloat} <: HaarFeatureAbstractType +mutable struct HaarLikeObject{I <: Integer, F <: AbstractFloat} <: HaarFeatureAbstractType + #parametric struct to store the ints and floats efficiently feature_type::Tuple{I, I} position::Tuple{I, I} top_left::Tuple{I, I} @@ -32,34 +30,32 @@ mutable struct HaarLikeObject{I<:Integer,F<:AbstractFloat} <: HaarFeatureAbstrac threshold::I polarity::I weight::F - #parametric struct to store the ints and floats efficiently - - end # end structure - # constructor; equivalent of __init__ method within class - function HaarLikeObject( - feature_type::Tuple{Integer, Integer}, - position::Tuple{Integer, Integer}, - width::Integer, - height::Integer, - threshold::Integer, - polarity::Integer - ) +function HaarLikeObject( + feature_type::Tuple{Integer, Integer}, + position::Tuple{Integer, Integer}, + width::Integer, + height::Integer, + threshold::Integer, + polarity::Integer +) + + # make sure that everything is of the same size + p₁, p₂ = position + f₁, f₂ = feature_type + p₁, p₂, f₁, f₂, width, height, threshold, polarity = + promote(p₁, p₂, f₁, f₂, width, height, threshold, polarity) + position = (p₁, p₂) + feature_type = (f₁, f₂) + top_left = position + bottom_right = (first(position) + width, last(position) + height) + weight = float(one(p₁)) #to make a float of the same size + + + HaarLikeObject(feature_type, position, top_left, bottom_right, width, height, threshold, polarity, weight) +end - #all this to make sure that everything is of se same size - p1,p2 = position - f1,f2 = feature_type - p1,p2,f1,f2,width,height,threshold,polarity = promote(p1,p2,f1,f2,width,height,threshold,polarity) - position = (p1,p2) - feature_type = (f1,f2) - top_left = position - bottom_right = (position[1] + width, position[2] + height) - weight = float(one(p1)) #to make a float of the same size - - - HaarLikeObject(feature_type, position, top_left, bottom_right, width, height, threshold, polarity, weight) - end # end constructor """ get_score(feature::HaarLikeObject, int_img::Array) -> Tuple{Number, Number} @@ -74,49 +70,49 @@ Get score for given integral image array. - `score::Number`: Score for given feature """ -function get_score(feature::HaarLikeObject{I,F}, int_img::Array) where {I,F} +function get_score(feature::HaarLikeObject{I,F}, int_img::Array) where {I, F} score = zero(I) faceness = zero(I) if feature.feature_type == feature_types.two_vertical - first = sum_region(int_img, feature.top_left, (feature.top_left[1] + feature.width, I(round(feature.top_left[2] + feature.height / 2)))) - second = sum_region(int_img, (feature.top_left[1], I(round(feature.top_left[2] + feature.height / 2))), feature.bottom_right) - score = first - second + _first = sum_region(int_img, feature.top_left, (first(feature.top_left) + feature.width, I(round(last(feature.top_left) + feature.height / 2)))) + _second = sum_region(int_img, (first(feature.top_left), I(round(last(feature.top_left) + feature.height / 2))), feature.bottom_right) + score = _first - _second faceness = I(1) elseif feature.feature_type == feature_types.two_horizontal - first = sum_region(int_img, feature.top_left, (I(round(feature.top_left[1] + feature.width / 2)), feature.top_left[2] + feature.height)) - second = sum_region(int_img, (I(round(feature.top_left[1] + feature.width / 2)), feature.top_left[2]), feature.bottom_right) - score = first - second + _first = sum_region(int_img, feature.top_left, (I(round(first(feature.top_left) + feature.width / 2)), last(feature.top_left) + feature.height)) + _second = sum_region(int_img, (I(round(first(feature.top_left) + feature.width / 2)), last(feature.top_left)), feature.bottom_right) + score = _first - _second faceness = I(2) elseif feature.feature_type == feature_types.three_horizontal - first = sum_region(int_img, feature.top_left, (I(round(feature.top_left[1] + feature.width / 3)), feature.top_left[2] + feature.height)) - second = sum_region(int_img, (I(round(feature.top_left[1] + feature.width / 3)), feature.top_left[2]), (I(round(feature.top_left[1] + 2 * feature.width / 3)), feature.top_left[2] + feature.height)) - third = sum_region(int_img, (I(round(feature.top_left[1] + 2 * feature.width / 3)), feature.top_left[2]), feature.bottom_right) - score = first - second + third + _first = sum_region(int_img, feature.top_left, (I(round(first(feature.top_left) + feature.width / 3)), last(feature.top_left) + feature.height)) + _second = sum_region(int_img, (I(round(first(feature.top_left) + feature.width / 3)), last(feature.top_left)), (I(round(first(feature.top_left) + 2 * feature.width / 3)), last(feature.top_left) + feature.height)) + third = sum_region(int_img, (I(round(first(feature.top_left) + 2 * feature.width / 3)), last(feature.top_left)), feature.bottom_right) + score = _first - _second + third faceness = I(3) elseif feature.feature_type == feature_types.three_vertical - first = sum_region(int_img, feature.top_left, (feature.bottom_right[1], I(round(feature.top_left[2] + feature.height / 3)))) - second = sum_region(int_img, (feature.top_left[1], I(round(feature.top_left[2] + feature.height / 3))), (feature.bottom_right[1], I(round(feature.top_left[2] + 2 * feature.height / 3)))) - third = sum_region(int_img, (feature.top_left[1], I(round(feature.top_left[2] + 2 * feature.height / 3))), feature.bottom_right) - score = first - second + third + _first = sum_region(int_img, feature.top_left, (first(feature.bottom_right), I(round(last(feature.top_left) + feature.height / 3)))) + _second = sum_region(int_img, (first(feature.top_left), I(round(last(feature.top_left) + feature.height / 3))), (first(feature.bottom_right), I(round(last(feature.top_left) + 2 * feature.height / 3)))) + third = sum_region(int_img, (first(feature.top_left), I(round(last(feature.top_left) + 2 * feature.height / 3))), feature.bottom_right) + score = _first - _second + third faceness = I(4) elseif feature.feature_type == feature_types.four # top left area - first = sum_region(int_img, feature.top_left, (I(round(feature.top_left[1] + feature.width / 2)), I(round(feature.top_left[2] + feature.height / 2)))) + _first = sum_region(int_img, feature.top_left, (I(round(first(feature.top_left) + feature.width / 2)), I(round(last(feature.top_left) + feature.height / 2)))) # top right area - second = sum_region(int_img, (I(round(feature.top_left[1] + feature.width / 2)), feature.top_left[2]), (feature.bottom_right[1], I(round(feature.top_left[2] + feature.height / 2)))) + _second = sum_region(int_img, (I(round(first(feature.top_left) + feature.width / 2)), last(feature.top_left)), (first(feature.bottom_right), I(round(last(feature.top_left) + feature.height / 2)))) # bottom left area - third = sum_region(int_img, (feature.top_left[1], I(round(feature.top_left[2] + feature.height / 2))), (I(round(feature.top_left[1] + feature.width / 2)), feature.bottom_right[2])) + third = sum_region(int_img, (first(feature.top_left), I(round(last(feature.top_left) + feature.height / 2))), (I(round(first(feature.top_left) + feature.width / 2)), last(feature.bottom_right))) # bottom right area - fourth = sum_region(int_img, (I(round(feature.top_left[1] + feature.width / 2)), I(round(feature.top_left[2] + feature.height / 2))), feature.bottom_right) - score = first - second - third + fourth + fourth = sum_region(int_img, (I(round(first(feature.top_left) + feature.width / 2)), I(round(last(feature.top_left) + feature.height / 2))), feature.bottom_right) + score = _first - _second - third + fourth faceness = I(5) end return score, faceness end -#= +""" get_vote(feature::HaarLikeObject, int_img::AbstractArray) -> Integer Get vote of this feature for given integral image. @@ -131,8 +127,8 @@ Get vote of this feature for given integral image. - `vote::Integer`: 1 ⟺ this feature votes positively -1 otherwise -=# -function get_vote(feature::HaarLikeObject, int_img::AbstractArray) +""" +function get_vote(feature::HaarLikeObject, int_img::AbstractArray)::Int8 score = first(get_score(feature, int_img)) # we only care about score here return (feature.weight * score) < (feature.polarity * feature.threshold) ? one(Int8) : -one(Int8) end diff --git a/src/IntegralImage.jl b/src/IntegralImage.jl index 9cd795660..ac2a14ad0 100755 --- a/src/IntegralImage.jl +++ b/src/IntegralImage.jl @@ -5,6 +5,8 @@ =# #= + IntegralArray{T, N, A} <: AbstractArray{T, N} + Rectangle features can be computed very rapidly using an intermediate representation for the image, which we call the integral image. The integral image at location $x,y$ contains the sum of the pixels above and to the left of $x,y$ inclusive. Original Integral @@ -13,9 +15,6 @@ Original Integral | 4 5 6 . | 5 12 21 . | . . . . | . . . . . =# - - - struct IntegralArray{T, N, A} <: AbstractArray{T, N} data::A end @@ -33,11 +32,11 @@ Calculates the integral image based on this instance's original image data. - `integral_image_arr::AbstractArray`: Integral image for given image """ -function to_integral_image(img_arr::AbstractArray{T,N}) where {T,N} +function to_integral_image(img_arr::AbstractArray{T, N}) where {T, N} array_size = size(img_arr) integral_image_arr = Array{Images.accum(eltype(img_arr))}(undef, array_size) sd = coords_spatial(img_arr) - cumsum!(integral_image_arr, img_arr; dims=sd[1])#length(array_size) + cumsum!(integral_image_arr, img_arr; dims=first(sd)) for i = 2:length(sd) cumsum!(integral_image_arr, integral_image_arr; dims=sd[i]) end @@ -48,7 +47,7 @@ end LinearIndices(A::IntegralArray) = Base.LinearFast() @inline size(A::IntegralArray) = size(A.data) @inline getindex(A::IntegralArray, i::Int...) = A.data[i...] -@inline getindex(A::IntegralArray, ids::Tuple...) = getindex(A, ids[1]...) +@inline getindex(A::IntegralArray, ids::Tuple...) = getindex(A, first(ids)...) """ sum_region( @@ -69,14 +68,14 @@ LinearIndices(A::IntegralArray) = Base.LinearFast() """ function sum_region( integral_image_arr::AbstractArray, - top_left::Tuple{T,T}, - bottom_right::Tuple{T,T} + top_left::Tuple{T, T}, + bottom_right::Tuple{T, T} ) where T <: Integer _1 = one(T) - sum = integral_image_arr[bottom_right[2], bottom_right[1]] - sum -= top_left[1] > _1 ? integral_image_arr[bottom_right[2], top_left[1] - 1] : zero(T) - sum -= top_left[2] > _1 ? integral_image_arr[top_left[2] - _1, bottom_right[1]] : zero(T) - sum += top_left[2] > _1 && top_left[1] > _1 ? integral_image_arr[top_left[2] - 1, top_left[1] - 1] : zero(T) + _0 = zero(0) + sum = integral_image_arr[last(bottom_right), first(bottom_right)] + sum -= first(top_left) > _1 ? integral_image_arr[last(bottom_right), first(top_left) - _1] : _0 + sum -= last(top_left) > _1 ? integral_image_arr[last(top_left) - _1, first(bottom_right)] : _0 + sum += last(top_left) > _1 && first(top_left) > _1 ? integral_image_arr[last(top_left) - _1, first(top_left) - _1] : _0 return sum end - diff --git a/src/Utils.jl b/src/Utils.jl index 3407dbd02..815672e22 100755 --- a/src/Utils.jl +++ b/src/Utils.jl @@ -9,23 +9,7 @@ using Images: save, load, Colors, clamp01nan, Gray, imresize using ImageDraw: draw, Polygon, Point -#= - displaymatrix(M::AbstractArray) -> AbstractString - -A function to show a big matrix on one console screen (similar to default `print` of numpy arrays in Python). - -# Arguments - -- `M::AbstractArray`: Some array - -# Returns -- `A::AbstractString`: A nice array to print -=# -function displaymatrix(M::AbstractArray) - return show(IOContext(stdout, :limit => true, :compact => true, :short => true), "text/plain", M); print("\n") -end - -#= +""" notify_user(message::AbstractString) -> AbstractString A function to pretty print a message to the user @@ -36,12 +20,12 @@ A function to pretty print a message to the user # Returns - `A::AbstractString`: A message to print to the user -=# +""" function notify_user(message::AbstractString) return println("\033[1;34m===>\033[0;38m\033[1;38m\t$message\033[0;38m") end -#= +""" filtered_ls(path::AbstractString) -> Array{String, 1} A function to filter the output of readdir @@ -53,12 +37,12 @@ A function to filter the output of readdir # Returns - `Array{String, 1}`: An array of filtered files in the path -=# +""" function filtered_ls(path::AbstractString)::Array{String, 1} return filter!(f -> ! occursin(r".*\.DS_Store", f), readdir(path, join=true, sort=false)) end -#= +""" load_image(image_path::AbstractString) -> AbstractArray Loads an image as gray_scale @@ -68,7 +52,7 @@ Loads an image as gray_scale # Returns `AbstractArray`: An array of floating point values representing the image -=# +""" function load_image( image_path::AbstractString; scale::Bool=false, @@ -77,15 +61,12 @@ function load_image( img = load(image_path) img = convert(Array{Float64}, Gray.(img)) - - if scale - img = imresize(img, scale_to) - end + img = scale ? imresize(img, scale_to) : img return to_integral_image(img) end -#= +""" determine_feature_size( pos_training_path::AbstractString, neg_training_path::AbstractString @@ -105,12 +86,12 @@ Takes images and finds the best feature size for the image size. - `min_feature_height::Integer`: the minimum height of the feature - `min_feature_width::Integer`: the minimum width of the feature - `min_size_img::Tuple{Integer, Integer}`: the minimum-sized image in the image directories -=# +""" function determine_feature_size( pos_training_path::AbstractString, neg_training_path::AbstractString; scale::Bool=false, - scale_to::Tuple=(200,200) + scale_to::Tuple=(200, 200) ) min_feature_height = 0 @@ -158,15 +139,11 @@ That is, the final strong classifier is $h(x)=\begin{cases}1&\text{if }\sum_{t=1 1 ⟺ sum of classifier votes > 0 0 otherwise =# -function ensemble_vote(int_img::AbstractArray, classifiers::AbstractArray) - # evidence = sum([max(get_vote(c[1], image), 0.) * c[2] for c in classifiers]) - # weightedSum = sum([c[2] for c in classifiers]) - # return evidence >= (weightedSum / 2) ? 1 : -1 - - return sum(c -> get_vote(c, int_img), classifiers) >= 0 ? one(Int8) : zero(Int8) +function ensemble_vote(int_img::Matrix, classifiers::Array{HaarLikeObject, 1})::Int8 + return sum(c -> get_vote(c, int_img), classifiers) ≥ zero(Int8) ? one(Int8) : zero(Int8) end -#= +""" ensemble_vote_all(int_imgs::AbstractArray, classifiers::AbstractArray) -> AbstractArray Classifies given integral image (Abstract Array) using given classifiers. I.e., if the sum of all classifier votes is greater 0, the image is classified positively (1); else it is classified negatively (0). The threshold is 0, because votes can be +1 or -1. @@ -177,22 +154,17 @@ Classifies given integral image (Abstract Array) using given classifiers. I.e., # Returns `votes::AbstractArray`: A list of assigned votes (see ensemble_vote). -=# +""" function ensemble_vote_all( image_path::AbstractString, - classifiers::AbstractArray; + classifiers::Array{HaarLikeObject, 1}; scale::Bool=false, - scale_to::Tuple=(200,200) + scale_to::Tuple=(200, 200) )::Array{Int8, 1} - return votes = map(i -> ensemble_vote(load_image(i, scale=scale, scale_to=scale_to), classifiers), filtered_ls(image_path)) + return [ensemble_vote(load_image(i, scale=scale, scale_to=scale_to), classifiers) for i in filtered_ls(image_path)] end - -# function ensemble_vote_all(int_imgs::AbstractArray, classifiers::AbstractArray) -# return Array(map(i -> ensemble_vote(i, classifiers), int_imgs)) -# end - #= get_faceness(feature, int_img::AbstractArray) -> Number @@ -207,10 +179,10 @@ Get facelikeness for a given feature. - `score::Number`: Score for given feature =# -function get_faceness(feature, int_img::AbstractArray) - score, faceness = get_score(feature, int_img) - - return (feature.weight * score) < (feature.polarity * feature.threshold) ? faceness : 0 +function get_faceness(feature::HaarLikeObject, int_img::Matrix{T}) where T + score, faceness = get_score(feature, int_img) + + return (feature.weight * score) < (feature.polarity * feature.threshold) ? faceness : zero(T) end #= diff --git a/test/runtests.jl b/test/runtests.jl index 1b0a23754..becf4fd47 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,6 +9,7 @@ include(joinpath(dirname(dirname(@__FILE__)), "src", "FaceDetection.jl")) # ../s using .FaceDetection using Test: @testset, @test using Suppressor: @suppress +# using BenchmarkTools: @btime const main_data_path = joinpath(@__DIR__, "images") @@ -28,12 +29,12 @@ const main_data_path = joinpath(@__DIR__, "images") f = rand((0, 1)) arr = rand(Int, 100, 100) @test FaceDetection.HaarLikeObject(a, b, c, d, e, f) isa HaarLikeObject - @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).feature_type isa Tuple{Int, Int} - @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).position isa Tuple{Int, Int} - @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).top_left isa Tuple{Int, Int} - @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).bottom_right isa Tuple{Int, Int} - @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).width isa Int - @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).height isa Int + @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).feature_type isa Tuple{Integer, Integer} + @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).position isa Tuple{Integer, Integer} + @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).top_left isa Tuple{Integer, Integer} + @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).bottom_right isa Tuple{Integer, Integer} + @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).width isa Integer + @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).height isa Integer @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).threshold ∈ [0, 1] @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).polarity ∈ [0, 1] @test FaceDetection.HaarLikeObject((1,3), (1,3), 10, 8, 0, 1).weight ∈ [0, 1] @@ -63,7 +64,7 @@ const main_data_path = joinpath(@__DIR__, "images") end @test isapprox(p, 0.63, atol=1e-1) @test isapprox(n, 0.372, atol=1e-1) - random_img = rand(vcat(filtered_ls.([pos_training_path, neg_training_path, pos_testing_path, neg_testing_path])...)) - @test get_faceness(classifiers[rand(1:length(classifiers))], load_image(random_img)) isa Integer + random_img = load_image(rand(vcat(filtered_ls.([pos_training_path, neg_training_path, pos_testing_path, neg_testing_path])...))) + @test get_faceness(classifiers[rand(1:length(classifiers))], random_img) isa Real @test determine_feature_size(pos_training_path, neg_training_path) == (10, 10, 8, 8, (19, 19)) end # end tests