Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP]Add boosting tracker #20

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions Manifest.toml

This file was deleted.

28 changes: 28 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,30 @@
name = "ImageTracking"
uuid = "5cb2747a-620a-11e9-38cd-3dc946e3e5f7"
version = "0.1.0"

[deps]
AxisAlgorithms = "13072b0f-2c55-5437-9ae7-d433b7a33950"
CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298"
ImageFiltering = "6a3955dd-da59-5b1f-98d4-e7296123deb5"
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
AxisAlgorithms = ">= 0.2"
ImageFiltering = ">= 0.3"
Images = ">= 0.17"
Interpolations = ">= 0.7"
julia = ">= 0.7"

[extras]
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
QuartzImageIO = "dca85d43-d64c-5e67-8c65-017450d5d020"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"

[targets]
test = ["Test", "TestImages", "ImageMagick", "QuartzImageIO"]
6 changes: 0 additions & 6 deletions REQUIRE

This file was deleted.

10 changes: 8 additions & 2 deletions src/ImageTracking.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ include("core.jl")
include("optical_flow.jl")
include("haar.jl")
include("utility.jl")
include("tracker/tracker.jl")

export

Expand Down Expand Up @@ -54,8 +55,13 @@ export

# types that select implementation
ConvolutionImplementation,
MatrixImplementation

MatrixImplementation,

# tracking algorithms
BoxROI,
CurrentSampler,
TrackerBoosting,
initialize!,
update!

end
20 changes: 20 additions & 0 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,23 @@ function pinv2x2(M::AbstractArray)
D = SMatrix{2,2,Float64}( S[1,1] > tol ? 1/S[1,1] : 0.0 , 0.0, 0.0, S[2,2] > tol ? 1/S[2,2] : 0.0 )
U*D*V'
end

abstract type AbstractTrackerTargetState end
mutable struct BoostingTrackerTargetState <: AbstractTrackerTargetState
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be kept in a separate file as well.

#Initialized
position::SVector{2, Float64}
height::Integer
width::Integer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this stands for tracking region

position::SVector{2, Float64}
height::Integer
width::Integer

you can use Range to represent it as well.

is_target::Bool

#Uninitialized
responses::Array{T, 2} where T
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of responses? If it is needed, then you should declare T as part of the struct, for example:
mutable struct BoostingTrackerTargetState{T <: AbstractArray} <: AbstractTrackerTargetState
responses::T
See: https://docs.julialang.org/en/v1/manual/performance-tips/index.html#Avoid-containers-with-abstract-type-parameters-1

BoostingTrackerTargetState(position::SVector{2, Float64}, height::Integer, width::Integer, is_target::Bool) =
new(position, height, width, is_target)
end


mutable struct ConfidenceMap{T <: AbstractTrackerTargetState, F <: AbstractFloat}
states::Vector{T}
confidence::Vector{F}
end
29 changes: 29 additions & 0 deletions src/tracker/boosting_tracker.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#-----------------------------------
# Implementation of boosting tracker
#-----------------------------------

function initialize!(tracker::TrackerBoosting)
int_image = integral_image(tracker.box.img)

# sampling
int_box = BoxROI(int_image, tracker.box.bound)

tracker.sampler.mode = :positive
positive_samples = sample_roi(tracker.sampler, int_box)

tracker.sampler.mode = :negative
negative_samples = sample_roi(tracker.sampler, int_box)

if isempty(positive_samples) || isempty(negative_samples)
error("Couldn't get initial samples")
end
# compute harr haar_features

# model

# Run model estimation and update for initial iterations
end

function update!(tracker::TrackerBoosting, image::AbstractArray)

end
57 changes: 57 additions & 0 deletions src/tracker/tracker.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#---------------------
# TRACKER COMPONENTS
#---------------------

include("../core.jl")
include("tracker_sampler.jl")
# include("tracker_state_estimator.jl")
# include("tracker_model.jl")
# include("tracker_features.jl")

abstract type AbstractTracker end
mutable struct TrackerBoosting{I <: Integer, B <: BoxROI, CS <: CurrentSampler} <: AbstractTracker
# initialized
box::B
sampler::CS
num_of_classifiers::I
initial_iterations::I
num_of_features::I

# constructor
function TrackerBoosting{I, B, CS}(box::B, sampler::CS, num_of_classifiers::I = 100, initial_iterations::I = 20,
num_of_features::I = 1050)where {I <: Integer, B <: BoxROI, CS <: CurrentSampler}
if size(box.bound, 1) != 4
error("Invalid bounding box size")
end

if box.bound[1] < 1 && box.bound[1] > size(box.img)[1]
Deepank308 marked this conversation as resolved.
Show resolved Hide resolved
error("Invalid bounding box")
end
if box.bound[2] < 1 && box.bound[2] > size(box.img)[2]
error("Invalid bounding box")
end
if box.bound[3] < 1 && box.bound[3] > size(box.img)[1]
error("Invalid bounding box")
end
if box.bound[4] < 1 && box.bound[4] > size(box.img)[2]
error("Invalid bounding box")
end

if box.bound[1] > box.bound[3]
error("Invalid bounding box")
end
if box.bound[2] > box.bound[4]
error("Invalid bounding box")
end
new(box, sampler, num_of_classifiers, initial_iterations, num_of_features)
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that in general you can write inequalities like this in Julia:

 if 0 < x < 1 
   do something
end

rather than

if x > 0 && x < 1
  do something
end

The former is much more readable.


TrackerBoosting(box::B, sampler::CS, num_of_classifiers::I, initial_iterations::I, num_of_features::I) where{I <: Integer, B <: BoxROI, CS <: CurrentSampler} = TrackerBoosting{I, B, CS}(box, sampler, num_of_classifiers, initial_iterations, num_of_features)


#------------------
# IMPLEMENTATIONS
#------------------

include("boosting_tracker.jl")
100 changes: 100 additions & 0 deletions src/tracker/tracker_sampler.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
abstract type AbstractROI end
mutable struct BoxROI{T <: AbstractArray, S <: AbstractArray} <: AbstractROI
Copy link
Member

@johnnychen94 johnnychen94 Apr 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mutable seems to be an overkill, and in julia we use Tuple for bound type, i.e., NTuple{4,Integer}.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be storing the entire image as a field in BoxROI. An abstract region of interest could just be a set of AbstractRange types, one for each dimensions. It would also not need to be mutable. You just instantiate a new when the ROI changes. I imagine something like this:

struct BoxROI{R₁ <: AbstractRange, R₂} <: AbstractROI
    span₁::R₁ 
    span₂::R₂
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is img used through the entire tracking life? If the answer is yes, we then have some reason to hold it in some struct( e.g., Tracker or BoxROI). If it's only used for initialization purpose, it's better not to do so.

img::T
bound::S
end

abstract type AbstractTrackerSampler end
mutable struct CurrentSampler{F <: AbstractFloat} <: AbstractTrackerSampler
overlap::F
search_factor::F
mode::S where S <: Symbol
Deepank308 marked this conversation as resolved.
Show resolved Hide resolved
sampling_region::SVector{4, Integer}
valid_region::SVector{4, Integer}
tracked_patch::SVector{4, Integer}
CurrentSampler{F}(overlap, search_factor) where{F <: AbstractFloat} = new(overlap, search_factor)
Deepank308 marked this conversation as resolved.
Show resolved Hide resolved
end

CurrentSampler(overlap::F = 0.99, search_factor::F = 1.8) where{F <: AbstractFloat} = CurrentSampler{F}(overlap, search_factor)

function sample_roi(sampler::CurrentSampler, box::BoxROI)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would then pass the image as a parameter (instead of retrieving it from BoxROI).

sampler.tracked_patch = box.bound
sampler.valid_region = SVector{4, Integer}([1, 1, size(box.img, 1), size(box.img, 2)])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could just be a tuple taken from AbstractRange type family.


height = box.bound[3] - box.bound[1] + 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can write a custom size(b::BoxROI) which returns the width and height as a tuple. Since the BoxROI consists of AbstractRange objects, this will be trivial.

width = box.bound[4] - box.bound[2] + 1

sampling_region_min_y = max(0, floor(Integer, box.bound[1] - height*((sampler.search_factor - 1) / 2) + 1))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of all of the .fieldname usage, I think its better to write a get_search_factor(s::Sampler) method which returns the search_factor and assign it once at the top.

search_factor = get_search_factor(sampler)
sampling_region_min_y = ...

So that you don't keep writing sampler.search_factor.
In general, try to minimize the number of .field expression and in particular .filed[] expressions. In my view it decreases the readability. The code is unnecessarily lengthy otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll take care of this.

sampling_region_min_x = max(0, floor(Integer, box.bound[2] - width*((sampler.search_factor - 1) / 2) + 1))

sampling_region_height = min(floor(Integer, height*sampler.search_factor), size(box.img, 1) - sampling_region_min_y + 1)
sampling_region_width = min(floor(Integer, width*sampler.search_factor), size(box.img, 2) - sampling_region_min_x + 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please clarify the role of the search_factor. Do we need all of these floor and min operations inside a hot loop? Could this all be computed when the sampler is constructed instead of being computed every time a ROI is sampled?


sampling_region_max_y = sampling_region_min_y + sampling_region_height - 1
sampling_region_max_x = sampling_region_min_x + sampling_region_width - 1

sampling_region = SVector{4, Integer}([sampling_region_min_y, sampling_region_min_x, sampling_region_max_y, sampling_region_max_x])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a good idea. You are allocating an array by invoking the square brackets [..], only to immediately create another array on the stack. You don't need the square brackets here. Perhaps this ought to just be a range objects anyway? i.e. sampling_region_min_y:sampling_region_max_y and sampling_region_min_x:sampling_region_max_x.


sample_mode_roi(sampler, box, sampling_region)
end

function sample_mode_roi(sampler::CurrentSampler, box::BoxROI, sampling_region::SVector{4, Integer})
if sampler.valid_region == sampling_region
sampler.sampling_region = sampling_region
else
sampler.sampling_region = SVector{4, Integer}(max(sampler.valid_region[1], sampling_region[1]), max(sampler.valid_region[2],sampling_region[2]),
min(sampler.valid_region[3], sampling_region[3]), min(sampler.valid_region[4], sampling_region[4]))
end

if sampler.mode == :positive
positive_sample = box.img[sampler.tracked_patch[1]:sampler.tracked_patch[3],
sampler.tracked_patch[2]:sampler.tracked_patch[4]]
positive_sample_pos = SVector{2, Integer}(sampler.tracked_patch[1], sampler.tracked_patch[2])
samples = fill((positive_sample, positive_sample_pos), (1, 4))

return samples
end

height = sampler.tracked_patch[3] - sampler.tracked_patch[1] + 1
width = sampler.tracked_patch[4] - sampler.tracked_patch[2] + 1

tl_block = SVector{4, Integer}(sampler.sampling_region[1], sampler.sampling_region[2], sampler.sampling_region[1] + height - 1,
sampler.sampling_region[2] + width - 1)
tr_block = SVector{4, Integer}(sampler.sampling_region[1], sampler.sampling_region[4] - width + 1, sampler.sampling_region[1] + height - 1,
sampler.sampling_region[4])
bl_block = SVector{4, Integer}(sampler.sampling_region[3] - height + 1, sampler.sampling_region[2], sampler.sampling_region[3],
sampler.sampling_region[2] + width - 1)
br_block = SVector{4, Integer}(sampler.sampling_region[3] - height + 1, sampler.sampling_region[4] - width + 1, sampler.sampling_region[3],
sampler.sampling_region[4])

if sampler.mode == :negative
tl_sample = box.img[tl_block[1]:tl_block[3], tl_block[2]:tl_block[4]]
tr_sample = box.img[tr_block[1]:tr_block[3], tr_block[2]:tr_block[4]]
bl_sample = box.img[bl_block[1]:bl_block[3], bl_block[2]:bl_block[4]]
br_sample = box.img[br_block[1]:br_block[3], br_block[2]:br_block[4]]
samples = [(tl_sample, SVector{2, Integer}(tl_block[1:2])) (tr_sample, SVector{2, Integer}(tr_block[1:2])) (bl_sample, SVector{2, Integer}(bl_block[1:2])) (br_sample, SVector{2, Integer}(br_block[1:2]))]

return samples
end

if sampler.mode == :classify
step_row = max(1, floor(Int, (1 - sampler.overlap)*height + 0.5))
step_col = max(1, floor(Int, (1 - sampler.overlap)*width + 0.5))
max_row = sampler.sampling_region[3] - sampler.sampling_region[1] - height + 1
max_col = sampler.sampling_region[4] - sampler.sampling_region[2] - width + 1

samples = Array{Tuple{Array{T, 2}, SVector{2, Integer}}} where T <: Integer
for i = 1:step_col:max_col
for j = 1:step_row:max_row
push!(samples, (box.img[j + sampler.sampling_region[1] - 1:j + sampler.sampling_region[1] + height - 2,
i + sampler.sampling_region[2] - 1:i + sampler.sampling_region[2] + width - 2],
SVector{2, Integer}(j + sampler.sampling_region[1]-1, i + sampler.sampling_region[2]-1)))
end
end

return samples
else
Deepank308 marked this conversation as resolved.
Show resolved Hide resolved
error("Incorrect mode.")
end

end
5 changes: 0 additions & 5 deletions test/REQUIRE

This file was deleted.

3 changes: 1 addition & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using ImageTracking
using Test


include("farneback.jl")
include("lucas_kanade.jl")
include("haar.jl")
include("visualization.jl")
include("error_evaluation.jl")

include("tracker/sample_roi.jl")
28 changes: 28 additions & 0 deletions test/tracker/sample_roi.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Images
using ImageTracking:sample_roi

@testset "ROI sampling" begin
@info "Running ROI sampling tests"
box = BoxROI(ones(300, 300), [125, 125, 175, 175])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make it less error-prone, I think you need to cover the following types forbox.img:

img = ones(300, 300)
testcolors = (RGB,Gray)
testtypes =  (Float32, Float64, N0f8, N0f16)
for C in testcolors, T in testtypes
    box = BoxROI(img .|> C{T}, [125, 125, 175, 175])
    ...
end

If the function don't work for N0f8 type, manually convert it to float type in your src codes and document it (or throw an error as early as you can), otherwise, they're annoying here and there from my personal experiences.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

sampler_overlap = 0.5
search_factor = 2.0
int_img = integral_image(box.img)
currentsample = CurrentSampler(sampler_overlap, search_factor)
# positive sampling
currentsample.mode = :positive
@time samples = sample_roi(currentsample, BoxROI(int_img, box.bound))

@test size(samples) == (1, 4)
@test samples[1][1] == int_img[125:175, 125:175]

# negative sampling
currentsample.mode = :negative
@time samples = sample_roi(currentsample, BoxROI(int_img, box.bound))

@test size(samples) == (1, 4)
@test samples[1][1] == int_img[100:150, 100:150]
@test samples[2][1] == int_img[100:150, 151:201]
@test samples[3][1] == int_img[151:201, 100:150]
@test samples[4][1] == int_img[151:201, 151:201]

end;