diff --git a/setup.sh b/setup.sh index 420421d4a..60269e911 100755 --- a/setup.sh +++ b/setup.sh @@ -113,9 +113,9 @@ done main() { - setupWD - checkPackages - obtainDataset1 + # setupWD + # checkPackages + # obtainDataset1 obtainDataset2 } diff --git a/src/Adaboost.jl b/src/Adaboost.jl index 150a70b07..a8a6be4f7 100755 --- a/src/Adaboost.jl +++ b/src/Adaboost.jl @@ -16,19 +16,18 @@ include("HaarLikeFeature.jl") function learn(positiveIIs::AbstractArray, negativeIIs::AbstractArray, numClassifiers::Int64=-1, minFeatureWidth::Int64=1, maxFeatureWidth::Int64=-1, minFeatureHeight::Int64=1, maxFeatureHeight::Int64=-1) - """ - Selects a set of classifiers. Iteratively takes the best classifiers based - on a weighted error. - :param positive_iis: List of positive integral image examples - :type positive_iis: list[numpy.ndarray] - :param negative_iis: List of negative integral image examples - :type negative_iis: list[numpy.ndarray] - :param num_classifiers: Number of classifiers to select, -1 will use all - classifiers - :type num_classifiers: int - :return: List of selected features - :rtype: list[violajones.HaarLikeFeature.HaarLikeFeature] - """ + #= + 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 inverselt proportional to the training errors. + This function selects a set of classifiers. Iteratively takes the best classifiers based on a weighted error. + + parameter `positiveIIs`: List of positive integral image examples [type: Abstracy Array] + parameter `negativeIIs`: List of negative integral image examples [type: Abstract Array] + parameter `numClassifiers`: Number of classifiers to select. -1 will use all + classifiers [type: Integer] + + return `classifiers`: List of selected features [type: HaarLikeFeature] + =# + numPos = length(positiveIIs) numNeg = length(negativeIIs) global numImgs = numPos + numNeg @@ -61,6 +60,7 @@ function learn(positiveIIs::AbstractArray, negativeIIs::AbstractArray, numClassi The series of `deep*` functions——though useful in general——were designed from awkward arrays of tuples of arrays, which came about from a translation error in this case. =# weights = vcat(posWeights, negWeights) + # println(weights) # weights = vcat(posWeights, negWeights) # weights = hcat((posWeights, negWeights)) # weights = vcat([posWeights, negWeights]) @@ -86,6 +86,7 @@ function learn(positiveIIs::AbstractArray, negativeIIs::AbstractArray, numClassi # bar = progressbar.ProgressBar() # @everywhere numImgs begin + # println(votes) @everywhere begin n = numImgs processes = length(numImgs) @@ -107,7 +108,7 @@ function learn(positiveIIs::AbstractArray, negativeIIs::AbstractArray, numClassi n = numClassifiers p = Progress(n, 1) # minimum update interval: 1 second - for i in processes + for _ in processes # println(typeof(length(featureIndices))) classificationErrors = zeros(length(featureIndices)) @@ -139,6 +140,8 @@ function learn(positiveIIs::AbstractArray, negativeIIs::AbstractArray, numClassi bestError = classificationErrors[minErrorIDX] bestFeatureIDX = featureIndices[minErrorIDX] + # println(classificationErrors) + # println(weights) # set feature weight bestFeature = features[bestFeatureIDX] featureWeight = 0.5 * log((1 - bestError) / bestError) @@ -173,6 +176,19 @@ end function _create_features(imgHeight::Int64, imgWidth::Int64, minFeatureWidth::Int64, maxFeatureWidth::Int64, minFeatureHeight::Int64, maxFeatureHeight::Int64) + #= + Iteratively creates the Haar-like feautures + + parameter `imgHeight`: The height of the image [type: Integer] + parameter `imgWidth`: The width of the image [type: Integer] + parameter `minFeatureWidth`: The minimum width of the feature (used for computation efficiency purposes) [type: Integer] + parameter `maxFeatureWidth`: The maximum width of the feature [type: Integer] + parameter `minFeatureHeight`: The minimum height of the feature [type: Integer] + parameter `maxFeatureHeight`: The maximum height of the feature [type: Integer] + + return `features`: an array of Haar-like features found for an image [type: Abstract Array] + =# + println("Creating Haar-like features...") # features = Array() features = [] diff --git a/src/HaarLikeFeature.jl b/src/HaarLikeFeature.jl index c2608b6c1..cee9c37b7 100755 --- a/src/HaarLikeFeature.jl +++ b/src/HaarLikeFeature.jl @@ -23,6 +23,10 @@ abstract type HaarFeatureType end # make structure struct HaarLikeFeature <: HaarFeatureType#struct HaarLikeFeature{T} <: HaarObject where {T <: HaarFeatureType} + #= + Struct representing a Haar-like feature. + =# + featureType::Tuple{Int64, Int64} position::Tuple{Int64, Int64} topLeft::Tuple{Int64, Int64} @@ -32,6 +36,8 @@ struct HaarLikeFeature <: HaarFeatureType#struct HaarLikeFeature{T} <: HaarObjec threshold::Int64 polarity::Int64 weight::Number# number because it can be -Inf due to log(0) + # weight::Int64 + # weight::Any # constructor; equivalent of __init__ method within class # ::CartesianIndex function HaarLikeFeature(featureType::Tuple{Int64,Int64}, position::Tuple{Int64, Int64}, width::Int64, height::Int64, threshold::Int64, polarity::Int64) @@ -108,13 +114,15 @@ end # end structure # end function getScore(feature::HaarLikeFeature, intImg::Array) - """ + #= Get score for given integral image array. - :param int_img: Integral image array - :type int_img: numpy.ndarray - :return: Score for given feature - :rtype: float - """ + + parameter `feature`: given Haar-like feature (parameterised replacement of Python's `self`) [type: HaarLikeFeature] + parameter `intImg`: Integral image array [type: Abstract Array] + + return `score`: Score for given feature [type: Float] + =# + score = 0 if feature.featureType == FeatureTypes[1] # two vertical @@ -187,20 +195,25 @@ end function getVote(feature::HaarLikeFeature, intImg::AbstractArray) - """ + #= Get vote of this feature for given integral image. - :param int_img: Integral image array - :type int_img: numpy.ndarray - :return: 1 iff this feature votes positively, otherwise -1 - :rtype: int - """ + + parameter `feature`: given Haar-like feature (parameterised replacement of Python's `self`) [type: HaarLikeFeature] + parameter `intImg`: Integral image array [type: Abstract Array] + + return: + 1 ⟺ this feature votes positively + -1 otherwise + [type: Integer] + =# + score = getScore(feature, intImg) # return feature.weight * (1 if score < feature.polarity * feature.threshold else -1) - return feature.weight * ((score < (feature.polarity * feature.threshold)) ? 1 : -1) + # return 1 * ((score < (feature.polarity * feature.threshold)) ? 1 : -1) end diff --git a/src/IntegralImage.jl b/src/IntegralImage.jl index 75fad143d..2be296946 100755 --- a/src/IntegralImage.jl +++ b/src/IntegralImage.jl @@ -4,19 +4,26 @@ "${BASH_SOURCE[0]}" "$@" =# -""" -Any one pixel in a given image has the value that is the sum of all of the pixels above it and to the left. +#= +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 +-------- +------------ | 1 2 3 . | 1 3 6 . | 4 5 6 . | 5 12 21 . | . . . . | . . . . . -""" +=# function toIntegralImage(imgArr::AbstractArray) - """ + #= + Calculates the integral image based on this instance's original image data. + + parameter `imgArr`: Image source data [type: Abstract Array] + + return `integralImageArr`: Integral image for given image [type: Abstract Array] + https://www.ipol.im/pub/art/2014/57/article_lr.pdf, p. 346 - """ + =# arrRows, arrCols = size(imgArr) # get size only once in case rowSum = zeros(arrRows, arrCols) @@ -62,12 +69,15 @@ end function sumRegion(integralImageArr::AbstractArray, topLeft::Tuple{Int64,Int64}, bottomRight::Tuple{Int64,Int64}) - """ - Calculates the sum in the rectangle specified by the given tuples - topLeft: (x, y) of the rectangle's top left corner - bottomRight: (x, y) of the rectangle's bottom right corner - return The sum of all pixels in the given rectangle - """ + #= + parameter `integralImageArr`: The intermediate Integral Image [type: Abstract Array] + Calculates the sum in the rectangle specified by the given tuples: + parameter `topLeft`: (x,y) of the rectangle's top left corner [type: Tuple] + parameter `bottomRight`: (x,y) of the rectangle's bottom right corner [type: Tuple] + + return: The sum of all pixels in the given rectangle defined by the parameters topLeft and bottomRight + =# + # swap tuples # topLeft = [topLeft[2], topLeft[1]] topLeft = (topLeft[2], topLeft[1]) diff --git a/src/Utils.jl b/src/Utils.jl index 6ea68c0f2..02d477df4 100755 --- a/src/Utils.jl +++ b/src/Utils.jl @@ -44,12 +44,52 @@ deepfloat(a) = deepfloat.(a) # for adaboost -function partial(f,a...) # for ... syntax see https://en.wikibooks.org/wiki/Introducing_Julia/Functions#Functions_with_variable_number_of_arguments +function partial(f,a...) + #= + Tentative partial function (like Python's partial function) which takes a function as input and *some of* its variables. + + parameter `f`: a function + parameter `a`: one of the function's arguments + + for `...` syntax see https://en.wikibooks.org/wiki/Introducing_Julia/Functions#Functions_with_variable_number_of_arguments + =# ( (b...) -> f(a...,b...) ) end +function loadImages(imageDir::AbstractString) + #= + Given a path to a directory of images, recursively loads those images. + + parameter `imageDir`: path to a directory of images [type: Abstract String (path)] + + return `images`: a list of images from the path provided [type: Abstract Array] + =# + + # imageDir = joinpath(dirname(dirname(@__FILE__)), "test", "images") + + images = [] + + for file in readdir(imageDir, join=true, sort=false) # join true for getImageMatrix to find file from absolute path + if basename(file) == ".DS_Store" # silly macos stuff >:( + continue + end + images = push!(images, getImageMatrix(file)) + end + + return images +end + + function getImageMatrix(imageFile::AbstractString) + #= + Takes an image and constructs a matrix of greyscale intensity values based on it. + + parameter `imageFile`: the path of the file of the image to be turned into an array [type: Abstract String (path)] + + return `imgArr`: The array of greyscale intensity values from the image [type: Absrtact Array] + =# + img = load(imageFile) # img = imresize(img, ratio=1/8) img = imresize(img, (10,10)) # for standardised size @@ -66,22 +106,6 @@ function getImageMatrix(imageFile::AbstractString) # print(img) end - -function loadImages(imageDir::AbstractString) - # imageDir = joinpath(dirname(dirname(@__FILE__)), "test", "images") - - images = [] - - for file in readdir(imageDir, join=true, sort=false) # join true for getImageMatrix to find file - if basename(file) == ".DS_Store" # silly macos stuff >:( - continue - end - images = push!(images, getImageMatrix(file)) - end - - return images -end - # to emulate python's function function pow(x::Number, y::Number) @@ -90,57 +114,58 @@ end function ensembleVote(intImg::AbstractArray, classifiers::AbstractArray) - """ - Classifies given integral image (numpy array) using given classifiers, i.e. - if the sum of all classifier votes is greater 0, image is classified - positively (1) else negatively (0). The threshold is 0, because votes can be - +1 or -1. - :param int_img: Integral image to be classified - :type int_img: numpy.ndarray - :param classifiers: List of classifiers - :type classifiers: list[violajones.HaarLikeFeature.HaarLikeFeature] - :return: 1 iff sum of classifier votes is greater 0, else 0 - :rtype: int - """ - if sum([c.get_vote(int_img) for c in classifiers]) >= 0 - return 1 - else - return 0 - end + #= + 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. + + parameter `intImg`: Integral image to be classified [type: AbstractArray] + parameter `classifiers`: List of classifiers [type: AbstractArray (array of HaarLikeFeatures)] + + return: + 1 ⟺ sum of classifier votes > 0 + 0 otherwise + [type: Integer] + =# + + # if sum([c.get_vote(int_img) for c in classifiers]) >= 0 + # return 1 + # else + # return 0 + # end + + return (sum([c.get_vote(int_img) for c in classifiers]) >= 0 ? 1 : 0) end function ensembleVoteAll(intImgs::AbstractArray, classifiers::AbstractArray) - """ - Classifies given list of integral images (numpy arrays) using classifiers, - i.e. if the sum of all classifier votes is greater 0, an image is classified - positively (1) else negatively (0). The threshold is 0, because votes can be - +1 or -1. - :param int_imgs: List of integral images to be classified - :type int_imgs: list[numpy.ndarray] - :param classifiers: List of classifiers - :type classifiers: list[violajones.HaarLikeFeature.HaarLikeFeature] - :return: List of assigned labels, 1 if image was classified positively, else - 0 - :rtype: list[int] - """ - partial = (f, a...) -> (b...) -> f(a..., b...) + #= + 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. + + parameter `intImg`: Integral image to be classified [type: AbstractArray] + parameter `classifiers`: List of classifiers [type: AbstractArray (array of HaarLikeFeatures)] + + return list of assigned labels: + 1 if image was classified positively + 0 otherwise + [type: Abstract Arrays (array of Integers)] + =# + votePartial = partial(ensembleVote, classifiers=classifiers) + return map(votePartial, intImgs) end function reconstruct(classifiers::HaarLikeFeature, imgSize::Tuple) - """ - Creates an image by putting all given classifiers on top of each other - producing an archetype of the learned class of object. - :param classifiers: List of classifiers - :type classifiers: list[violajones.HaarLikeFeature.HaarLikeFeature] - :param img_size: Tuple of width and height - :type img_size: (int, int) - :return: Reconstructed image - :rtype: PIL.Image - """ + #= + Creates an image by putting all given classifiers on top of each other producing an archetype of the learned class of object. + + parameter `classifiers`: List of classifiers [type: Abstract Array (array of HaarLikeFeatures)] + parameter `imgSize`: Tuple of width and height [Tuple] + + return `result`: Reconstructed image [type: PIL.Image??] + =# + image = zeros(imgSize) + for c in classifiers # map polarity: -1 -> 0, 1 -> 1 polarity = pow(1 + c.polarity, 2)/4