-
Notifications
You must be signed in to change notification settings - Fork 8
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
base: master
Are you sure you want to change the base?
Conversation
I think implementing codes in one file or multiple files doesn't make much difference in the beginning since it's in one module, you could just start with two files: |
Codecov Report
@@ Coverage Diff @@
## master #20 +/- ##
==========================================
- Coverage 64.8% 63.28% -1.52%
==========================================
Files 7 8 +1
Lines 375 384 +9
==========================================
Hits 243 243
- Misses 132 141 +9
Continue to review full report at Codecov.
|
Codecov Report
@@ Coverage Diff @@
## master #20 +/- ##
=========================================
Coverage ? 58.05%
=========================================
Files ? 10
Lines ? 515
Branches ? 0
=========================================
Hits ? 299
Misses ? 216
Partials ? 0
Continue to review full report at Codecov.
|
@Deepank308 Since you're implementing from scratch, I'd like to get feedback from you on JuliaImages/ImageBinarization.jl#26 |
After going through all the above discusisons and referenced issues, I am thinking of something like this : abstract type Tracker end
struct TrackerAlgoName <: Tracker end
function (tracker::TrackerAlgoName)(img, bounding_box)
# initialization code goes here
end
function update_tracker(tracker::TrackerAlgoName, img)
# updation code goes here
end or, abstract type Tracker end
struct TrackerAlgoName <: Tracker end
abstract type RegionOfInterest end
struct BoxROI{T, W<:Integer} <: RegionOfInterest
img::Array{T,2}
bound::MVector{4, W}
end
function (tracker::TrackerAlgoName)(roi::BoxROI)
# initialization code goes here
end
function update_tracker(tracker::TrackerAlgoName, img)
# updation code goes here
end I would then declare the tracker structs in |
LGTM, this’s just how we organize the way we’re thinking, at this point you could start the coding, and discuss more detailed issues later
|
In case you misunderstood, the following two pieces of codes are totally different: https://docs.julialang.org/en/v1/manual/constructors/ function TracerAlgoName(img, bounding_box)
# constructor
end https://docs.julialang.org/en/v1/manual/methods/#Function-like-objects-1 function (tracker::TracerAlgoName)(img)
# function like object
end My recommendation is:
Looks like we're good on the first one. But I guess you need something like mutable struct TrackerAlgoName <: Tracker
roi::BoxROI
end or mutable struct TrackerAlgoName <: Tracker
img
bound::BoxROI # or ::MVector
end you make the choice according to your implementation. The idea of the second item here is: intuitively, when you input some image to a As for the third item. When we really need an explicit I'm not sure which name is better: If you have some other ideas/thoughts when implementing your algorithm, please don't hesitate to share it with us. |
@johnnychen94 I have much to do in the initialization part of the tracker. For example - I have to prepare negative and positive samples from image, calculate harr features, initialize the model and its estimation. I read somewhere(one reference is here) that doing heavy computation in constructors is a bad practice to be avoided. But, I don't know much about this as far as Julia is concerned. Experiences welcomed. So, what I am thinking as of now is to just check the constraints in the constructor while still sticking with a |
I think you're right here, to save resources, it's usually a good practice to compute or allocate memories only when you really need, e.g., lazy-copy and separated init function here. |
I can't wait to see your implementations! 🚀 🚀 🚀 |
I wonder whether we shouldn't start incorporating the term abstract type AbstractTracker end
struct TrackerAlgoName <: AbstractTracker end
abstract type AbstractROI end
struct BoxROI{T, W<:Integer} <: AbstractROI
img::Array{T,2}
bound::SVector{4, W}
end I also think it may be more performant to have abstract type AbstractTracker end
struct TrackerAlgoName <: AbstractTracker end
abstract type AbstractROI end
struct BoxROI{T, ConcreteStaticArray <: StaticArray} <: AbstractROI
img::Array{T,2}
bound::ConcreteStaticArray
end so that we can support |
As for struct BoxROI{T, ConcreteStaticArray <: StaticArray} <: AbstractROI
img::Array{T,2}
bound::ConcreteStaticArray
end I prefer to define a more general version, struct BoxROI{T <: AbstractArray, S <: AbstractArray} <: AbstractROI
img::T
bound::S
end and move the usage of |
I have committed the final API for everyone to have a look. As @johnnychen94 pointed out here it will be good to have The steps involved in the boosting tracker initialization are :
I'll be following these steps for implementation. |
I will add tests for sampling in the next commit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still, I didn't go to the algorithm details since I'm not familiar with that, but I trust you and the workflow of your algorithm looks very descriptive even to me who knows almost nothing about image tracking. 👍
The algorithm details should be reviewed by others.
|
||
abstract type AbstractTrackerTargetState end | ||
|
||
mutable struct BoostingTrackerTargetState <: AbstractTrackerTargetState |
There was a problem hiding this comment.
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.
Things are becoming better now, I'm looking forward to review from @zygmuntszpak |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left some comments on style and test :)
|
||
@testset "ROI sampling" begin | ||
@info "Running ROI sampling tests" | ||
box = BoxROI(ones(300, 300), [125, 125, 175, 175]) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
I have some suggestions which I intend to type up later today. |
@@ -0,0 +1,100 @@ | |||
abstract type AbstractROI end | |||
mutable struct BoxROI{T <: AbstractArray, S <: AbstractArray} <: AbstractROI |
There was a problem hiding this comment.
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}
.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made some initial comments but I think there is still a lot more to think about.
It would be very helpful if you could describe all of the key steps that you think the algorithm needs to perform in a comment and how you are currently modelling the problem. I'm not convinced that we need all of these mutable structs, and I'm not sure whether we have the type hierarchy and containment completely correct yet.
We need to clarify what the "hot loops" are going to be, what gets constructed for each frame, and what new work needs to be done for every next frame.
We need to be very careful how we design this if we want to lay the foundations for a performant implementation.
is_target::Bool | ||
|
||
#Uninitialized | ||
responses::Array{T, 2} where T |
There was a problem hiding this comment.
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
@@ -0,0 +1,100 @@ | |||
abstract type AbstractROI end | |||
mutable struct BoxROI{T <: AbstractArray, S <: AbstractArray} <: AbstractROI |
There was a problem hiding this comment.
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
|
||
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) |
There was a problem hiding this comment.
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)]) | ||
|
||
height = box.bound[3] - box.bound[1] + 1 |
There was a problem hiding this comment.
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.
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]) |
There was a problem hiding this comment.
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
.
|
||
function sample_roi(sampler::CurrentSampler, box::BoxROI) | ||
sampler.tracked_patch = box.bound | ||
sampler.valid_region = SVector{4, Integer}([1, 1, size(box.img, 1), size(box.img, 2)]) |
There was a problem hiding this comment.
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 | ||
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)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
end | ||
new(box, sampler, num_of_classifiers, initial_iterations, num_of_features) | ||
end | ||
end |
There was a problem hiding this comment.
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.
Thank you @zygmuntszpak for your suggestions. I will surely inculcate them into the imlpementations but at this very time I'm quite occupied with my End-Semester examinations(starting from the next week). I wanted to ask you whether it is fine if I continue after 30th April? Apologies for the same. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @zygmuntszpak that there're many unnecessary fields declared here, which would make future changes incompatible. We can't say much about BoostingTrackerTargetState
and ConfidenceMap
at present since they're not used in the implementation.
Here're some of my thoughts.
One rule of thumb that can be useful is occam's razor: don't give the struct too much information and mutability until it's really necessary.
However, it's quite hard to judge in practice. Take the BoxROI
as an example,
On one hand, as @zygmuntszpak says, ROI
is already self-explained without the content of a concrete image. I see you need img
here to check if the ROI
is valid for the image but that's not the real need. The intuition of its unnecessity here is: ROI
only become invalid until we combine it with an image.
On the other hand, ROI
is always an ROI
of something, so holding an image as a field of it is also justified.
I don't have any preference for the ROI
example, whatever gives better performance wins.
It's absolutely okay to continue it after your exams, nobody in the community will push you, and I've already seen your productivity here and there. 👍 By all means, we do this work with our enthusiasm.
@@ -0,0 +1,100 @@ | |||
abstract type AbstractROI end | |||
mutable struct BoxROI{T <: AbstractArray, S <: AbstractArray} <: AbstractROI |
There was a problem hiding this comment.
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.
#Initialized | ||
position::SVector{2, Float64} | ||
height::Integer | ||
width::Integer |
There was a problem hiding this comment.
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.
After going through the current PR for the boosting tracker I have planned to maintain the following structure for tracker implementation :
Let's suppose I'm adding tracker algo
algo-name
.Then :
init
andupdate
APIs intracker.jl
./algo-name_tracker/
in thesrc
sub-folder for main algo implementation.core.jl
.tracker_features.jl
.tracker_model.jl
.tracker_sampler.jl
.tracker_state_estimator.jl
.I will make incremental changes and include tests where ever required.
@zygmuntszpak please share your views for any change or should I go forward with this?