diff --git a/docs/src/benchmarking.md b/docs/src/benchmarking.md index 5376efbad..db2ceaada 100644 --- a/docs/src/benchmarking.md +++ b/docs/src/benchmarking.md @@ -21,3 +21,10 @@ Platform Info: LIBM: libopenlibm LLVM: libLLVM-9.0.1 (ORCJIT, skylake) ``` + +## 1.6 Update + +A few months after the release of Julia 1.6, I did some performance considerations (there are already quite a few nice features that come with 1.6). Now these are the benchmarking results (see [`benchmark/basic.jl`](https://github.com/jakewilliami/FaceDetection.jl/blob/master/benchmark/basic.jl)) +Language of Implementation | Commit | Run Time in Seconds | Number of Allocations | Memory Usage +--- | --- | --- | --- | --- +[Julia](https://github.com/jakewilliami/FaceDetection.jl/) | [???]() | 8.165 | 249021919 | 5.01 GiB diff --git a/examples/basic_ffhq_things.jl b/examples/basic_ffhq_things.jl new file mode 100755 index 000000000..64c254a7f --- /dev/null +++ b/examples/basic_ffhq_things.jl @@ -0,0 +1,124 @@ +# Adapted from https://github.com/Simon-Hohberg/Viola-Jones/ + +# Faces dataset: [FFHQ](https://github.com/NVlabs/ffhq-dataset/) 70_001 images of faces +# Non-faces dataset: [THINGS](https://osf.io/3fu6z/); 26_107 object images + +@info "Loading required libraries (it will take a moment to precompile if it is your first time doing this)..." + +using FaceDetection +using Printf: @printf +using Images: imresize + +@info("...done") + +"Return a random subset of the contents of directory `path` of size `n`." +function rand_subset_ls(path::String, n::Int) + dir_contents = readdir(path, join=true, sort=false) + filter!(f -> !occursin(r".*\.DS_Store", f), dir_contents) + @assert(length(dir_contents) >= n, "Not enough files in given directory to select `n` random.") + + subset_ls = Vector{String}(undef, n) + for i in 1:n + j = rand(1:length(dir_contents)) + subset_ls[i] = dir_contents[j] + deleteat!(dir_contents, j) + end + + return subset_ls +end + +function main( + num_pos::Int, + num_neg::Int; + smart_choose_feats::Bool=false, + scale::Bool=true, + scale_to::Tuple=(128, 128) +) + data_path = joinpath(dirname(@__DIR__), "data") + + pos_training_path = joinpath(data_path, "ffhq", "thumbnails128x128") + neg_training_path = joinpath(data_path, "things", "object_images") + + all_pos_images = rand_subset_ls(pos_training_path, 2num_pos) + all_neg_images = rand_subset_ls(neg_training_path, 2num_neg) + + pos_training_images = all_pos_images[1:num_pos] + neg_training_images = all_neg_images[1:num_neg] + + num_classifiers = 10 + local min_size_img::Tuple{Int, Int} + + if smart_choose_feats + # For performance reasons restricting feature size + @info("Selecting best feature width and height...") + + max_feature_width, max_feature_height, min_feature_height, min_feature_width, min_size_img = + determine_feature_size(vcat(pos_training_images, neg_training_images); scale = scale, scale_to = scale_to, show_progress = true) + + @info("...done. Maximum feature width selected is $max_feature_width pixels; minimum feature width is $min_feature_width; maximum feature height is $max_feature_height pixels; minimum feature height is $min_feature_height.\n") + else + max_feature_width, max_feature_height, min_feature_height, min_feature_width = (67, 67, 65, 65) + min_size_img = (128, 128) + end + + # classifiers are haar like features + classifiers = learn(pos_training_images, neg_training_images, num_classifiers, min_feature_height, max_feature_height, min_feature_width, max_feature_width; scale = scale, scale_to = scale_to) + + @info("Testing selected classifiers...") + sleep(3) # sleep here because sometimes the threads from `learn` are still catching up and then `ensemble_vote_all` errors + + pos_testing_images = all_pos_images[(num_pos + 1):2num_pos] + neg_testing_images = all_neg_images[(num_neg + 1):2num_neg] + num_faces = length(pos_testing_images) + num_non_faces = length(neg_testing_images) + + correct_faces = sum(ensemble_vote_all(pos_testing_images, classifiers, scale=scale, scale_to=scale_to)) + correct_non_faces = num_non_faces - sum(ensemble_vote_all(neg_testing_images, classifiers, scale=scale, scale_to=scale_to)) + correct_faces_percent = (correct_faces / num_faces) * 100 + correct_non_faces_percent = (correct_non_faces / num_non_faces) * 100 + + faces_frac = string(correct_faces, "/", num_faces) + faces_percent = string("(", correct_faces_percent, "% of faces were recognised as faces)") + non_faces_frac = string(correct_non_faces, "/", num_non_faces) + non_faces_percent = string("(", correct_non_faces_percent, "% of non-faces were identified as non-faces)") + + @info("...done.\n") + @info("Result:\n") + + @printf("%10.9s %10.15s %15s\n", "Faces:", faces_frac, faces_percent) + @printf("%10.9s %10.15s %15s\n\n", "Non-faces:", non_faces_frac, non_faces_percent) +end + +@time main(2000, 2000; smart_choose_feats = false, scale = true, scale_to = (128, 128)) + +#= +[ Info: Loading required libraries (it will take a moment to precompile if it is your first time doing this)... +[ Info: ...done +[ Info: Creating Haar-like features... +[ Info: ...finished processing; 169880 features created. +[ Info: Loading images (1000 positive and 1000 negative images) and calculating their scores... +Progress: 100%|███████████████████████████████████████████████████████████████████████████████████████████| Time: 0:02:13 +[ Info: Selecting classifiers... +Progress: 100%|███████████████████████████████████████████████████████████████████████████████████████████| Time: 0:00:17 +[ Info: Testing selected classifiers... +[ Info: ...done. +[ Info: Result: + Faces: 757/1000 (75.7% of faces were recognised as faces) + Non-faces 749/1000 (74.9% of non-faces were identified as non-faces) +=# + +#= +[ Info: Loading required libraries (it will take a moment to precompile if it is your first time doing this)... +[ Info: ...done +[ Info: Creating Haar-like features... +[ Info: ...finished processing; 169880 features created. +[ Info: Loading images (2000 positive and 2000 negative images) and calculating their scores... +Progress: 100%|███████████████████████████████████████████████████████████████████████████████████████████| Time: 0:07:06 +[ Info: Selecting classifiers... +Progress: 100%|███████████████████████████████████████████████████████████████████████████████████████████| Time: 0:00:52 +[ Info: Testing selected classifiers... +[ Info: ...done. +[ Info: Result: + Faces: 1547/2000 (77.35% of faces were recognised as faces) + Non-faces 1400/2000 (70.0% of non-faces were identified as non-faces) +=# diff --git a/src/AdaBoost.jl b/src/AdaBoost.jl index 6d015a20f..a3adc8578 100755 --- a/src/AdaBoost.jl +++ b/src/AdaBoost.jl @@ -1,17 +1,13 @@ # TODO: select optimal threshold for each feature # TODO: attentional cascading -using Base.Threads: @threads -using Base.Iterators: partition -using ProgressMeter: @showprogress, Progress, next! - function β(i::T)::T where T return @fastmath(T(0.5) * log((one(i) - i) / i)) end function get_feature_votes( - positive_path::AbstractString, - negative_path::AbstractString, + positive_files::Vector{String}, + negative_files::Vector{String}, num_classifiers::Integer=-one(Int32), min_feature_width::Integer=one(Int32), max_feature_width::Integer=-one(Int32), @@ -39,8 +35,6 @@ function get_feature_votes( _1 = _Int(1) # get number of positive and negative image - positive_files = filtered_ls(positive_path) - negative_files = filtered_ls(negative_path) num_pos = length(positive_files) num_neg = length(negative_files) num_imgs = num_pos + num_neg @@ -80,19 +74,40 @@ function get_feature_votes( return votes, features end +function get_feature_votes( + positive_path::String, + negative_path::String, + 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)), + show_progress::Bool = true +) + positive_files = filtered_ls(positive_path) + negative_files = filtered_ls(negative_path) + + return get_feature_votes( + positive_files, negative_files, + num_classifiers, + min_feature_width, max_feature_width, + min_feature_height, max_feature_height; + scale = scale, scale_to = scale_to, + show_progress = show_progress + ) +end function learn( - positive_path::AbstractString, - negative_path::AbstractString, + num_pos::Int, num_neg::Int, features::Array{HaarLikeObject, 1}, votes::Matrix{Int8}, num_classifiers::Integer=-one(Int32); show_progress::Bool = true - ) +) # 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)) - num_neg = length(filtered_ls(negative_path)) num_imgs = num_pos + num_neg # Initialise weights $w_{1,i} = \frac{1}{2m}, \frac{1}{2l}$, for $y_i=0,1$ for negative and positive examples respectively @@ -107,10 +122,10 @@ function learn( _neg1 = -_1 labels = Vector{Int8}(undef, num_imgs) for i in 1:num_pos - labels[i] = _1 + @inbounds labels[i] = _1 end for j in (num_pos + 1):num_imgs - labels[j] = _neg1 + @inbounds labels[j] = _neg1 end # get number of features @@ -120,7 +135,6 @@ function learn( # select classifiers @info("Selecting classifiers...") - # classifiers = HaarLikeObject[] classifiers = Vector{HaarLikeObject}(undef, num_classifiers) classification_errors = Vector{Float64}(undef, num_features) @@ -132,7 +146,7 @@ function learn( weights .*= inv(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|$ - @threads for j in 1:length(feature_indices) + @inbounds @threads for j in 1:length(feature_indices) feature_idx = feature_indices[j] classification_errors[j] = sum(weights[img_idx] for img_idx in 1:num_imgs if labels[img_idx] !== votes[img_idx, feature_idx]) end @@ -140,12 +154,10 @@ function learn( # 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 = β(best_error) - best_feature.weight = feature_weight + best_feature.weight = β(best_error) # append selected features classifiers[t] = best_feature @@ -162,7 +174,7 @@ function learn( end # 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])` + deleteat!(feature_indices, best_feature_idx) resize!(classification_errors, length(feature_indices)) next!(p) # increment progress bar end @@ -171,8 +183,8 @@ function learn( end function learn( - positive_path::AbstractString, - negative_path::AbstractString, + positive_files::Vector{String}, + negative_files::Vector{String}, num_classifiers::Int=-1, min_feature_width::Int=1, max_feature_width::Int=-1, @@ -184,29 +196,50 @@ function learn( ) votes, features = get_feature_votes( - positive_path, - negative_path, + positive_files, negative_files, num_classifiers, - min_feature_width, - max_feature_width, - min_feature_height, - max_feature_height, - scale = scale, - scale_to = scale_to, + min_feature_width, max_feature_width, + min_feature_height, max_feature_height, + scale = scale, scale_to = scale_to, show_progress = show_progress ) - return learn(positive_path, negative_path, features, votes, num_classifiers; show_progress = show_progress) + num_pos, num_neg = length(positive_files), length(negative_files) + + return learn(num_pos, num_neg, features, votes, num_classifiers; show_progress = show_progress) +end + +function learn( + positive_path::String, + negative_path::String, + num_classifiers::Int=-1, + min_feature_width::Int=1, + max_feature_width::Int=-1, + min_feature_height::Int=1, + max_feature_height::Int=-1; + scale::Bool = false, + scale_to::Tuple = (200, 200), + show_progress::Bool = true +) + + return learn( + filtered_ls(positive_path), + filtered_ls(negative_path), + num_classifiers, + min_feature_width, max_feature_width, + min_feature_height, max_feature_height; + scale = scale, scale_to = scale_to, + show_progress = show_progress + ) end """ 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::Int, img_width::Int, + min_feature_width::Int, + max_feature_width::Int, + min_feature_height::Int, + max_feature_height::Int ) -> Array{HaarLikeObject, 1} Iteratively creates the Haar-like feautures @@ -225,27 +258,26 @@ Iteratively creates the Haar-like feautures - `features::AbstractArray`: an array of Haar-like features found for an image """ function create_features( - img_height::Int, - img_width::Int, + img_height::Int, img_width::Int, min_feature_width::Int, max_feature_width::Int, min_feature_height::Int, max_feature_height::Int ) - @info("Creating Haar-like features...") - features = HaarLikeObject[] - if img_width < max_feature_width || img_height < max_feature_height error(""" Cannot possibly find classifiers whose size is greater than the image itself [(width,height) = ($img_width,$img_height)]. """) end + @info("Creating Haar-like features...") + features = HaarLikeObject[] + for (feature_first, feature_last) in values(FEATURE_TYPES) # (feature_types are just tuples) feature_start_width = max(min_feature_width, feature_first) - for feature_width in feature_start_width:feature_first:(max_feature_width) + for feature_width in feature_start_width:feature_first:max_feature_width feature_start_height = max(min_feature_height, feature_last) - for feature_height in feature_start_height:feature_last:(max_feature_height) + for feature_height in feature_start_height:feature_last:max_feature_height for x in 1:(img_width - feature_width) for y in 1:(img_height - feature_height) push!(features, HaarLikeObject((feature_first, feature_last), (x, y), feature_width, feature_height, 0, 1)) diff --git a/src/FaceDetection.jl b/src/FaceDetection.jl index bab765efd..1c0a79f73 100755 --- a/src/FaceDetection.jl +++ b/src/FaceDetection.jl @@ -2,6 +2,9 @@ module FaceDetection import Base: size, getindex, LinearIndices using Images: Images, coords_spatial +using ProgressMeter: Progress, next! +using Base.Threads: @threads +using Base.Iterators: partition export to_integral_image, sum_region export learn, get_feature_votes @@ -10,10 +13,9 @@ export displaymatrix, filtered_ls, load_image, ensemble_vote_all, reconstruct, get_random_image, generate_validation_image, get_faceness, determine_feature_size - +include("IntegralImage.jl") include("HaarLikeFeature.jl") include("Utils.jl") # Utils.jl exports HaarLikeFeature.jl functions -include("IntegralImage.jl") include("AdaBoost.jl") end # end module diff --git a/src/HaarLikeFeature.jl b/src/HaarLikeFeature.jl index 8d19d966d..c5624b12a 100755 --- a/src/HaarLikeFeature.jl +++ b/src/HaarLikeFeature.jl @@ -84,7 +84,7 @@ Get score for given integral image array. This is the feature cascade. - `score::Number`: Score for given feature """ -function get_score(feature::HaarLikeObject{I, F}, int_img::AbstractArray{T. N}) where {I, F, T, N} +function get_score(feature::HaarLikeObject{I, F}, int_img::IntegralArray{T, N}) where {I, F, T, N} score = zero(I) faceness = zero(I) _2f = F(2) @@ -131,14 +131,14 @@ function get_score(feature::HaarLikeObject{I, F}, int_img::AbstractArray{T. N}) end """ - get_vote(feature::HaarLikeObject, int_img::AbstractArray) -> Integer + get_vote(feature::HaarLikeObject, int_img::IntegralArray) -> Integer Get vote of this feature for given integral image. # Arguments - `feature::HaarLikeObject`: given Haar-like feature (parameterised replacement of Python's `self`) -- `int_img::AbstractArray`: Integral image array [type: Abstract Array] +- `int_img::IntegralArray`: Integral image array # Returns @@ -146,7 +146,7 @@ Get vote of this feature for given integral image. 1 ⟺ this feature votes positively -1 otherwise """ -function get_vote(feature::HaarLikeObject{I, F}, int_img::AbstractArray{T, N}) where {I, F, T, N} +function get_vote(feature::HaarLikeObject{I, F}, int_img::IntegralArray{T, N}) where {I, F, T, N} score, _ = get_score(feature, int_img) # we only care about score here, not faceness # return (feature.weight * score) < (feature.polarity * feature.threshold) ? one(Int8) : -one(Int8) # return feature.weight * (score < feature.polarity * feature.threshold ? one(Int8) : -one(Int8)) diff --git a/src/IntegralImage.jl b/src/IntegralImage.jl index d49a28cdb..229a504c0 100755 --- a/src/IntegralImage.jl +++ b/src/IntegralImage.jl @@ -12,6 +12,7 @@ Original Integral struct IntegralArray{T, N, A} <: AbstractArray{T, N} data::A end +IntegralArray{T, N}(A::U) where {T, N, U <: AbstractArray{T, N}} = IntegralArray{T, N, U}(A) """ to_integral_image(img_arr::AbstractArray) -> AbstractArray @@ -35,7 +36,7 @@ function to_integral_image(img_arr::AbstractArray{T, N}) where {T, N} cumsum!(integral_image_arr, integral_image_arr; dims=sd[i]) end - return Array{T, N}(integral_image_arr) + return IntegralArray{T, N}(integral_image_arr) end LinearIndices(A::IntegralArray) = Base.LinearFast() diff --git a/src/Utils.jl b/src/Utils.jl index 81782393d..892514bc1 100755 --- a/src/Utils.jl +++ b/src/Utils.jl @@ -1,23 +1,6 @@ using Images: save, load, Colors, clamp01nan, Gray, imresize using ImageDraw: draw, Polygon, Point -#= - notify_user(message::String) -> String - -A function to pretty print a message to the user - -# Arguments - -- `message::String`: Some message to print - -# Returns -- `A::String`: A message to print to the user -=# -# function notify_user(io::IO, message::String) -# return println(io, "\033[1;34m===>\033[0;38m\033[1;38m\t", message, "\033[0;38m") -# end -# notify_user(msg::String) = notify_user(stdout, msg) - #= filtered_ls(path::String) -> Vector{String} @@ -53,13 +36,18 @@ function load_image( ) img = load(image_path) + if scale + img = imresize(img, scale_to) + end img = convert(Array{Float64}, Gray.(img)) - img = scale ? imresize(img, scale_to) : img return to_integral_image(img) end """ + determine_feature_size( + pictures::Vector{String} + ) -> Tuple{Integer, Integer, Integer, Integer, Tuple{Integer, Integer}} determine_feature_size( pos_training_path::String, neg_training_path::String @@ -69,6 +57,8 @@ Takes images and finds the best feature size for the image size. # Arguments +- `pictures::Vector{String}`: a list of paths to the images +OR - `pos_training_path::String`: the path to the positive training images - `neg_training_path::String`: the path to the negative training images @@ -81,43 +71,62 @@ Takes images and finds the best feature size for the image size. - `min_size_img::Tuple{Integer, Integer}`: the minimum-sized image in the image directories """ function determine_feature_size( - pos_training_path::String, - neg_training_path::String; + pictures::Vector{String}; scale::Bool=false, - scale_to::Tuple=(200, 200) + scale_to::Tuple=(200, 200), + show_progress::Bool = true ) - + + if scale + # if we are scaling to something, then we already know the + # minimum image size (the only image size) + @goto determine_feature_parameters + end + min_feature_height = 0 min_feature_width = 0 max_feature_height = 0 max_feature_width = 0 - + min_size_img = (0, 0) - sizes = Tuple{Int, Int}[] - - for picture_dir in[pos_training_path, neg_training_path] - for picture in filtered_ls(picture_dir) - img = load_image(picture, scale=scale, scale_to=scale_to) - new_size = size(img) - sizes = push!(sizes, new_size) + + p = Progress(length(pictures), enabled = show_progress) + p.dt = 1 # minimum update interval: 1 second + @threads for picture in pictures + img = load(picture) + new_size = size(img) + if all(iszero, min_size_img) || new_size < min_size_img + min_size_img = new_size end + next!(p) end - min_size_img = minimum(sizes) + @label determine_feature_parameters - max_feature_height = Int(round(min_size_img[2]*(10/19))) - max_feature_width = Int(round(min_size_img[1]*(10/19))) - min_feature_height = Int(round(max_feature_height - max_feature_height*(2/max_feature_height))) - min_feature_width = Int(round(max_feature_width - max_feature_width*(2/max_feature_width))) + max_feature_height = round(Int, min_size_img[2]*(10/19)) + max_feature_width = round(Int, min_size_img[1]*(10/19)) + min_feature_height = round(Int, max_feature_height - max_feature_height*(2/max_feature_height)) + min_feature_width = round(Int, max_feature_width - max_feature_width*(2/max_feature_width)) return max_feature_width, max_feature_height, min_feature_height, min_feature_width, min_size_img +end +function determine_feature_size( + pos_training_path::String, + neg_training_path::String; + scale::Bool=false, + scale_to::Tuple=(200, 200), + show_progress::Bool = true +) + pictures = vcat(filtered_ls(pos_training_path), filtered_ls(neg_training_path)) + return determine_feature_size(pictures; scale = scale, scale_to = scale_to, show_progress = show_progress) + end @doc raw""" - ensemble_vote(int_img::AbstractArray, classifiers::AbstractArray) -> Integer + ensemble_vote(int_img::IntegralArray, classifiers::AbstractArray) -> Integer -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. +Classifies given integral image (`IntegralArray`) 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. That is, the final strong classifier is @@ -131,7 +140,7 @@ h(x) = \begin{cases} # Arguments -- `int_img::Array{T, N}`: Integral image to be classified +- `int_img::IntegralArray{T, N}`: Integral image to be classified - `classifiers::Vector{HaarLikeObject}`: List of classifiers # Returns @@ -140,23 +149,32 @@ h(x) = \begin{cases} 1 ⟺ sum of classifier votes > 0 0 otherwise """ -function ensemble_vote(int_img::Array{T, N}, classifiers::Vector{HaarLikeObject}) where {T, N} - return sum(get_vote(c, int_img) for c in classifiers) ≥ zero(Int8) ? one(Int8) : zero(Int8) - # return sum(c -> get_vote(c, int_img), classifiers) ≥ zero(Int8) ? one(Int8) : zero(Int8) -end +ensemble_vote(int_img::IntegralArray{T, N}, classifiers::Vector{HaarLikeObject}) where {T, N} = + sum(get_vote(c, int_img) for c in classifiers) ≥ zero(Int8) ? one(Int8) : zero(Int8) """ - ensemble_vote_all(int_imgs::AbstractArray, classifiers::AbstractArray) -> Vector{Int8} -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. + ensemble_vote_all(images::Vector{String}, classifiers::Vector{HaarLikeObject}) -> Vector{Int8} + ensemble_vote_all(image_path::String, classifiers::Vector{HaarLikeObject}) -> Vector{Int8} + +Given a path to images, loads images then classifies votes 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. # Arguments -- `int_img::AbstractArray`: Integral image to be classified +- `images::Vector{String}`: list of paths to images; OR `image_path::String`: Path to images dir - `classifiers::Vector{HaarLikeObject}`: List of classifiers # Returns -`votes::AbstractArray`: A list of assigned votes (see ensemble_vote). +`votes::Vector{Int8}`: A list of assigned votes (see ensemble_vote). """ +function ensemble_vote_all( + images::Vector{String}, + classifiers::Vector{HaarLikeObject}; + scale::Bool=false, + scale_to::Tuple=(200, 200) + ) + + return Int8[ensemble_vote(load_image(i, scale=scale, scale_to=scale_to), classifiers) for i in images] +end function ensemble_vote_all( image_path::String, classifiers::Vector{HaarLikeObject}; @@ -164,26 +182,25 @@ function ensemble_vote_all( scale_to::Tuple=(200, 200) ) - return Int8[ensemble_vote(load_image(i, scale=scale, scale_to=scale_to), classifiers) for i in filtered_ls(image_path)] + return ensemble_vote_all(filtered_ls(image_path), classifiers; scale = scale, scale_to = scale_to) end """ - get_faceness(feature::HaarLikeObject{I, F}, int_img::Array{T, N}) -> Number + get_faceness(feature::HaarLikeObject{I, F}, int_img::IntegralArray{T, N}) -> Number Get facelikeness for a given feature. # Arguments - `feature::HaarLikeObject`: given Haar-like feature (parameterised replacement of Python's `self`) -- `int_img::AbstractArray`: Integral image array +- `int_img::IntegralArray`: Integral image array # Returns - `score::Number`: Score for given feature """ -function get_faceness(feature::HaarLikeObject{I, F}, int_img::Array{T, N}) where {I, F, T, N} +function get_faceness(feature::HaarLikeObject{I, F}, int_img::IntegralArray{T, N}) where {I, F, T, N} 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 c3dd7e4cc..860a47633 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -14,7 +14,7 @@ Logging.disable_logging(Logging.Info) pos_testing_path = joinpath(main_data_path, "pos_testing") neg_testing_path = joinpath(main_data_path, "neg_testing") a, b, c, d, e, f = tuple(rand(Int), rand(Int)), tuple(rand(Int), rand(Int)), rand(Int), rand(Int), rand((0, 1)), rand((0, 1)) - arr = rand(Int, 100, 100) + arr = FaceDetection.IntegralArray{Int, 2, Matrix{Int}}(rand(Int, 100, 100)) int_img = load_image(rand(vcat(filtered_ls.([pos_training_path, neg_training_path, pos_testing_path, neg_testing_path])...)), scale = true, scale_to = (24, 24)) feature_2v = HaarLikeObject(FEATURE_TYPES.two_vertical, (1, 1), 10, 10, 100000, 1) feature_2h = HaarLikeObject(FEATURE_TYPES.two_horizontal, (1, 1), 10, 10, 100000, 1)