Skip to content

Instantly share code, notes, and snippets.

@jakewilliami
Created October 27, 2020 00:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jakewilliami/32e4ba55e6133f67b88bf9f9fc908234 to your computer and use it in GitHub Desktop.
Save jakewilliami/32e4ba55e6133f67b88bf9f9fc908234 to your computer and use it in GitHub Desktop.
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