Created
October 27, 2020 00:50
-
-
Save jakewilliami/32e4ba55e6133f67b88bf9f9fc908234 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Images | |
using BenchmarkTools | |
using Profile | |
import Base: size, getindex, LinearIndices | |
using Images: Images, coords_spatial | |
struct IntegralArray{T, N, A} <: AbstractArray{T, N} | |
data::A | |
end | |
function to_integral_image(img_arr::AbstractArray) | |
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) | |
for i = 2:length(sd) | |
cumsum!(integral_image_arr, integral_image_arr; dims=sd[i]) | |
end | |
return Array{eltype(img_arr), ndims(img_arr)}(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]...) | |
function sum_region( | |
integral_image_arr::AbstractArray, | |
top_left::Tuple{Int64,Int64}, | |
bottom_right::Tuple{Int64,Int64} | |
) | |
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(Integer) | |
sum -= top_left[2] > 1 ? integral_image_arr[top_left[2] - 1, bottom_right[1]] : zero(Integer) | |
sum += top_left[2] > 1 && top_left[1] > 1 ? integral_image_arr[top_left[2] - 1, top_left[1] - 1] : zero(Integer) | |
return sum | |
end | |
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)) | |
abstract type HaarFeatureAbstractType end | |
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 | |
# 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 | |
) | |
top_left = position | |
bottom_right = (position[1] + width, position[2] + height) | |
weight = 1 | |
new(feature_type, position, top_left, bottom_right, width, height, threshold, polarity, weight) | |
end # end constructor | |
end # end structure | |
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) | |
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) | |
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) | |
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) | |
score = first - second + third | |
faceness = 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)))) | |
# 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)))) | |
# 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])) | |
# 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) | |
score = first - second - third + fourth | |
faceness = 5 | |
end | |
return score, faceness | |
end | |
function get_vote(feature::HaarLikeObject, int_img::AbstractArray) | |
score = get_score(feature, int_img)[1] # we only care about score here | |
return (feature.weight * score) < (feature.polarity * feature.threshold) ? one(Integer) : -one(Integer) | |
end | |
function filtered_ls(path::AbstractString)::Array{String, 1} | |
return filter!(f -> ! occursin(r".*\.DS_Store", f), readdir(path, join=true, sort=false)) | |
end | |
function load_image( | |
image_path::AbstractString; | |
scale::Bool=false, | |
scale_to::Tuple=(200,200) | |
)::Array{Float64, 2} | |
img = load(image_path) | |
img = convert(Array{Float64}, Gray.(img)) | |
if scale | |
img = imresize(img, scale_to) | |
end | |
return to_integral_image(img) | |
end | |
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 | |
) | |
features = [] | |
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 | |
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]) | |
for x in 1:(img_width - feature_width) | |
for y in 1:(img_height - feature_height) | |
features = push!(features, HaarLikeObject(feature, (x, y), feature_width, feature_height, 0, 1)) | |
features = push!(features, HaarLikeObject(feature, (x, y), feature_width, feature_height, 0, -1)) | |
end # end for y | |
end # end for x | |
end # end for feature height | |
end # end for feature width | |
end # end for feature in feature types | |
return features | |
end | |
function learn(positive_path::AbstractString,negative_path::AbstractString; scale::Bool=false, scale_to::Tuple=(577, 577)) | |
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 | |
image_files = vcat(positive_files, negative_files) | |
min_feature_height = 302 | |
max_feature_height = 304 | |
min_feature_width = 302 | |
max_feature_width = 304 | |
img_height, img_width = scale_to | |
features = _create_features(img_height, img_width, min_feature_width, max_feature_width, min_feature_height, max_feature_height) | |
println("$(length(features)) features created") | |
num_features = length(features) | |
votes = zeros((num_imgs, num_features)) | |
num_processed = 0 | |
println("loading and processing $(num_imgs) images") | |
Base.Threads.@threads for image_file in image_files | |
ii_img = load_image(image_file, scale=scale, scale_to=scale_to) | |
num_processed += 1 | |
votes[num_processed, :] .= map(f -> get_vote(f, ii_img), features) | |
# map!(f -> get_vote(f, ii_img), view(votes, num_processed, :), features) | |
end # end loop through images | |
end | |
@time learn(joinpath(dirname(@__DIR__), "data", "main", "trainset", "faces"), joinpath(dirname(@__DIR__), "data", "main", "trainset", "non-faces"), scale = true, scale_to = (577, 577)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment