diff --git a/_git2_a31664 b/_git2_a31664 new file mode 120000 index 000000000..9a2c7732f --- /dev/null +++ b/_git2_a31664 @@ -0,0 +1 @@ +testing \ No newline at end of file diff --git a/src/AdaBoost.jl b/src/AdaBoost.jl index 1b8870301..b438c5533 100755 --- a/src/AdaBoost.jl +++ b/src/AdaBoost.jl @@ -9,8 +9,7 @@ # TODO: attentional cascading # include("HaarLikeFeature.jl") -include("Utils.jl") -include("IntegralImage.jl") + using Base.Threads: @threads using Base.Iterators: partition @@ -19,15 +18,27 @@ using ProgressMeter: @showprogress, Progress, next! function get_feature_votes( 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::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); scale::Bool = false, - scale_to::Tuple = (200, 200) + scale_to::Tuple = (Int32(200), Int32(200)) ) + #this transforms everything to maintain type stability + s1 ,s2 = 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) + + _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) @@ -43,13 +54,13 @@ function get_feature_votes( temp_image = nothing # unload temporary image # Maximum feature width and height default to image width and height - max_feature_height = isequal(max_feature_height, -1) ? img_height : max_feature_height - max_feature_width = isequal(max_feature_width, -1) ? img_height : max_feature_width + max_feature_height = isequal(max_feature_height, _Int(-1)) ? img_height : max_feature_height + max_feature_width = isequal(max_feature_width, _Int(-1)) ? img_height : max_feature_width # Create features for all sizes and locations features = create_features(img_height, img_width, min_feature_width, max_feature_width, min_feature_height, max_feature_height) num_features = length(features) - num_classifiers = isequal(num_classifiers, -1) ? num_features : num_classifiers + num_classifiers = isequal(num_classifiers, _Int(-1)) ? num_features : num_classifiers # create an empty array with dimensions (num_imgs, numFeautures) votes = Matrix{Int8}(undef, num_features, num_imgs) @@ -73,7 +84,7 @@ function get_feature_votes( return votes, features end -#= +""" learn( positive_iis::AbstractArray, negative_iis::AbstractArray, @@ -84,8 +95,8 @@ end 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. +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 @@ -99,7 +110,7 @@ This function selects a set of classifiers. Iteratively takes the best classifie - `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, @@ -122,6 +133,7 @@ function learn( labels = vcat(ones(Int8, num_pos), ones(Int8, num_neg) * -one(Int8)) num_features = length(features) + feature_indices = Array(1:num_features) num_classifiers = isequal(num_classifiers, -1) ? num_features : num_classifiers @@ -129,7 +141,7 @@ function learn( # select classifiers classifiers = [] p = Progress(num_classifiers, 1) # minimum update interval: 1 second - @threads for t in 1:num_classifiers + 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}}$ @@ -166,6 +178,7 @@ function learn( return classifiers end + function learn( positive_path::AbstractString, negative_path::AbstractString, @@ -193,7 +206,7 @@ function learn( return learn(positive_path, negative_path, features, votes, num_classifiers) end -#= +""" create_features( img_height::Integer, img_width::Integer, @@ -217,7 +230,7 @@ Iteratively creates the Haar-like feautures # Returns - `features::AbstractArray`: an array of Haar-like features found for an image -=# +""" function create_features( img_height::Integer, img_width::Integer, diff --git a/src/FaceDetection.jl b/src/FaceDetection.jl index db9e63aa6..352db4db8 100755 --- a/src/FaceDetection.jl +++ b/src/FaceDetection.jl @@ -6,6 +6,9 @@ module FaceDetection +import Base: size, getindex, LinearIndices +using Images: Images, coords_spatial + export to_integral_image, sum_region export learn export feature_types, HaarLikeObject, get_score, get_vote @@ -13,8 +16,10 @@ export displaymatrix, notify_user, filtered_ls, load_image, ensemble_vote_all, reconstruct, get_random_image, generate_validation_image, get_faceness, determine_feature_size + +include("HaarLikeFeature.jl") +include("Utils.jl") # Utils.jl exports HaarLikeFeature.jl functions include("IntegralImage.jl") include("AdaBoost.jl") -include("Utils.jl") # Utils.jl exports HaarLikeFeature.jl functions end # end module diff --git a/src/HaarLikeFeature.jl b/src/HaarLikeFeature.jl index 4399687ef..9135d316e 100755 --- a/src/HaarLikeFeature.jl +++ b/src/HaarLikeFeature.jl @@ -5,27 +5,38 @@ =# -include("IntegralImage.jl") -feature_types = Dict{String, Tuple{Integer, Integer}}("two_vertical" => (1, 2), "two_horizontal" => (2, 1), "three_horizontal" => (3, 1), "three_vertical" => (1, 3), "four" => (2, 2)) +const feature_types = ( + two_vertical = (1, 2), + two_horizontal = (2, 1), + three_horizontal = (3, 1), + three_vertical = (1, 3), + four = (2, 2) + ) abstract type HaarFeatureAbstractType end # abstract type AbstractHaarLikeObject <: HaarFeatureAbstractType end -#= -Struct representing a Haar-like feature. -=# -mutable struct HaarLikeObject <: HaarFeatureAbstractType - feature_type::Tuple{Integer, Integer} - position::Tuple{Integer, Integer} - top_left::Tuple{Integer, Integer} - bottom_right::Tuple{Integer, Integer} - width::Integer - height::Integer - threshold::Integer - polarity::Integer - weight::AbstractFloat - +""" + mutable struct HaarLikeObject{I<:Integer,F<:AbstractFloat} + + Struct representing a Haar-like feature. +""" +mutable struct HaarLikeObject{I<:Integer,F<:AbstractFloat} <: HaarFeatureAbstractType + feature_type::Tuple{I, I} + position::Tuple{I, I} + top_left::Tuple{I, I} + bottom_right::Tuple{I, I} + width::I + height::I + 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}, @@ -35,15 +46,21 @@ mutable struct HaarLikeObject <: HaarFeatureAbstractType threshold::Integer, polarity::Integer ) + + #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 = 1 + weight = float(one(p1)) #to make a float of the same size - new(feature_type, position, top_left, bottom_right, width, height, threshold, polarity, weight) - end # end constructor -end # end structure -#= + 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} Get score for given integral image array. @@ -56,44 +73,44 @@ Get score for given integral image array. # Returns - `score::Number`: Score for given feature -=# -function get_score(feature::HaarLikeObject, int_img::Array) - score = 0 - faceness = 0 - - if feature.feature_type == feature_types["two_vertical"] - first = sum_region(int_img, feature.top_left, (feature.top_left[1] + feature.width, Int(round(feature.top_left[2] + feature.height / 2)))) - second = sum_region(int_img, (feature.top_left[1], Int(round(feature.top_left[2] + feature.height / 2))), feature.bottom_right) +""" +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 - faceness = 1 - elseif feature.feature_type == feature_types["two_horizontal"] - first = sum_region(int_img, feature.top_left, (Int(round(feature.top_left[1] + feature.width / 2)), feature.top_left[2] + feature.height)) - second = sum_region(int_img, (Int(round(feature.top_left[1] + feature.width / 2)), feature.top_left[2]), feature.bottom_right) + 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 - faceness = 2 - elseif feature.feature_type == feature_types["three_horizontal"] - first = sum_region(int_img, feature.top_left, (Int(round(feature.top_left[1] + feature.width / 3)), feature.top_left[2] + feature.height)) - second = sum_region(int_img, (Int(round(feature.top_left[1] + feature.width / 3)), feature.top_left[2]), (Int(round(feature.top_left[1] + 2 * feature.width / 3)), feature.top_left[2] + feature.height)) - third = sum_region(int_img, (Int(round(feature.top_left[1] + 2 * feature.width / 3)), feature.top_left[2]), feature.bottom_right) + 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 - faceness = 3 - elseif feature.feature_type == feature_types["three_vertical"] - first = sum_region(int_img, feature.top_left, (feature.bottom_right[1], Int(round(feature.top_left[2] + feature.height / 3)))) - second = sum_region(int_img, (feature.top_left[1], Int(round(feature.top_left[2] + feature.height / 3))), (feature.bottom_right[1], Int(round(feature.top_left[2] + 2 * feature.height / 3)))) - third = sum_region(int_img, (feature.top_left[1], Int(round(feature.top_left[2] + 2 * feature.height / 3))), feature.bottom_right) + 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 - faceness = 4 - elseif feature.feature_type == feature_types["four"] + faceness = I(4) + elseif feature.feature_type == feature_types.four # top left area - first = sum_region(int_img, feature.top_left, (Int(round(feature.top_left[1] + feature.width / 2)), Int(round(feature.top_left[2] + feature.height / 2)))) + 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)))) # top right area - second = sum_region(int_img, (Int(round(feature.top_left[1] + feature.width / 2)), feature.top_left[2]), (feature.bottom_right[1], Int(round(feature.top_left[2] + feature.height / 2)))) + 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)))) # bottom left area - third = sum_region(int_img, (feature.top_left[1], Int(round(feature.top_left[2] + feature.height / 2))), (Int(round(feature.top_left[1] + feature.width / 2)), feature.bottom_right[2])) + 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])) # bottom right area - fourth = sum_region(int_img, (Int(round(feature.top_left[1] + feature.width / 2)), Int(round(feature.top_left[2] + feature.height / 2))), feature.bottom_right) + 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 - faceness = 5 + faceness = I(5) end return score, faceness @@ -116,7 +133,6 @@ Get vote of this feature for given integral image. -1 otherwise =# function get_vote(feature::HaarLikeObject, int_img::AbstractArray) - score = get_score(feature, int_img)[1] # we only care about score here - + 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 79ec44aa0..9cd795660 100755 --- a/src/IntegralImage.jl +++ b/src/IntegralImage.jl @@ -14,14 +14,13 @@ Original Integral | . . . . | . . . . . =# -import Base: size, getindex, LinearIndices -using Images: Images, coords_spatial + struct IntegralArray{T, N, A} <: AbstractArray{T, N} data::A end -#= +""" to_integral_image(img_arr::AbstractArray) -> AbstractArray Calculates the integral image based on this instance's original image data. @@ -33,8 +32,8 @@ Calculates the integral image based on this instance's original image data. # Returns - `integral_image_arr::AbstractArray`: Integral image for given image -=# -function to_integral_image(img_arr::AbstractArray) + """ +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) @@ -43,15 +42,15 @@ function to_integral_image(img_arr::AbstractArray) cumsum!(integral_image_arr, integral_image_arr; dims=sd[i]) end - return Array{eltype(img_arr), ndims(img_arr)}(integral_image_arr) + return Array{T, N}(integral_image_arr) end LinearIndices(A::IntegralArray) = Base.LinearFast() -size(A::IntegralArray) = size(A.data) -getindex(A::IntegralArray, i::Int...) = A.data[i...] -getindex(A::IntegralArray, ids::Tuple...) = getindex(A, ids[1]...) +@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]...) -#= +""" sum_region( integral_image_arr::AbstractArray, top_left::Tuple{Int64,Int64}, @@ -67,16 +66,17 @@ getindex(A::IntegralArray, ids::Tuple...) = getindex(A, ids[1]...) # Returns - `sum::Number` The sum of all pixels in the given rectangle defined by the parameters top_left and bottom_right -=# +""" function sum_region( integral_image_arr::AbstractArray, - top_left::Tuple{Integer,Integer}, - bottom_right::Tuple{Integer,Integer} -) + 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(Int64) - sum -= top_left[2] > 1 ? integral_image_arr[top_left[2] - 1, bottom_right[1]] : zero(Int64) - sum += top_left[2] > 1 && top_left[1] > 1 ? integral_image_arr[top_left[2] - 1, top_left[1] - 1] : zero(Int64) - + 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) return sum end + diff --git a/src/Utils.jl b/src/Utils.jl index a5474e4e9..3407dbd02 100755 --- a/src/Utils.jl +++ b/src/Utils.jl @@ -5,8 +5,6 @@ =# -include("HaarLikeFeature.jl") -include("IntegralImage.jl") using Images: save, load, Colors, clamp01nan, Gray, imresize using ImageDraw: draw, Polygon, Point @@ -75,7 +73,7 @@ function load_image( image_path::AbstractString; scale::Bool=false, scale_to::Tuple=(200,200) - )::Array{Float64, 2} + )::Matrix{Float64} img = load(image_path) img = convert(Array{Float64}, Gray.(img)) @@ -334,7 +332,7 @@ function get_random_image( return file_name end -#= +""" scale_box( top_left::Tuple{Integer, Integer}, bottom_right::Tuple{Integer, Integer}, @@ -357,28 +355,28 @@ Scales the bounding box around classifiers if the image we are pasting it on is - `bottom_left::Tuple{Integer, Integer},`: new bottom left of box after scaling - `bottom_right::Tuple{Integer, Integer},`: new bottom right of box after scaling - `top_right::Tuple{Integer, Integer},`: new top right of box after scaling -=# +""" function scale_box( top_left::Tuple{Integer, Integer}, bottom_right::Tuple{Integer, Integer}, genisis_size::Tuple{Integer, Integer}, img_size::Tuple{Integer, Integer} ) - + T = typeof(first(top_left)) image_ratio = (img_size[1]/genisis_size[1], img_size[2]/genisis_size[2]) bottom_left = (top_left[1], bottom_right[2]) top_right = (bottom_right[1], top_left[2]) - top_left = convert.(Int, round.(top_left .* image_ratio)) - bottom_right = convert.(Int, round.(bottom_right .* image_ratio)) - bottom_left = convert.(Int, round.(bottom_left .* image_ratio)) - top_right = convert.(Int, round.(top_right .* image_ratio)) + top_left = convert.(T, round.(top_left .* image_ratio)) + bottom_right = convert.(T, round.(bottom_right .* image_ratio)) + bottom_left = convert.(T, round.(bottom_left .* image_ratio)) + top_right = convert.(T, round.(top_right .* image_ratio)) return top_left, bottom_left, bottom_right, top_right end -#= +""" generate_validation_image(image_path::AbstractString, classifiers::AbstractArray) -> AbstractArray Generates a bounding box around the face of a random image. @@ -391,7 +389,7 @@ Generates a bounding box around the face of a random image. # Returns - `validation_image::AbstractArray`: The new image with a bounding box -=# +""" function generate_validation_image(image_path::AbstractString, classifiers::Array{HaarLikeObject, 1}) # === THIS FUNCTION IS A WORK IN PROGRESS ===