-
Notifications
You must be signed in to change notification settings - Fork 143
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Add sub-pixel corner detection capability #682 #685
Conversation
@@ -18,6 +18,7 @@ using Base: depwarn | |||
using Base.Order: Ordering, ForwardOrdering, ReverseOrdering | |||
|
|||
using Compat | |||
using StaticArrays |
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.
minor comment: this needs a corresponding entry in the REQUIRE
file
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.
Thanks, I will amend the commit and add the entry in the REQUIRE
file. I will also add some more tests as per the codecov suggestions. I'm not sure what to do about the failing tests on the win32 build. It seems to be a matter of precision because the approximately equal symbol is failing. Do you have any suggestions for that?
e4f62c6
to
a5ad5d9
Compare
Codecov Report
@@ Coverage Diff @@
## master #685 +/- ##
=========================================
+ Coverage 88.77% 89.18% +0.4%
=========================================
Files 13 13
Lines 1167 1211 +44
=========================================
+ Hits 1036 1080 +44
Misses 131 131
Continue to review full report at Codecov.
|
Looking at the failed build for The problem was that the length of
|
a5ad5d9
to
72f7db2
Compare
src/Images.jl
Outdated
# divide each coordinate by the last coordinate everytime we attempt to index | ||
# into a matrix which could induce a performance hit. Note also that we return | ||
# a tuple of (y,x) since matrices are indexed according to row and then column. | ||
Base.to_indices(A::AbstractArray, p::Tuple{<: HomogeneousPoint2D}) = round(Int, p[1].coords[2]), round(Int, p[1].coords[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.
Just to double check: p
is limited to a 1-element tuple, but this is on purpose right?
It may be interesting to note (but no need to change it here) that Tuple
get special treatment in Julia. For example they are a covariant type. This means that you could actually write p::Tuple{HomogeneousPoint2D}
because for any T <: HomogeneousPoint2D
it holds that Tuple{T} <: Tuple{HomogeneousPoint2D}
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.
Yes, p
is limited to a 1-element tuple. SimonDanish explained why in one of his replies to my question
I have quoted him below:
That's indeed confusing and is the aftermath of indexing a multi dimensional array with a single element.
to_indices
takes a tuple because it gets called called like this in the abstract array code:
#note, that indices isn't splatted, so it turns into a tuple:
getindex(A::AbstractArray, indices...) = getindex(A, to_indices(indices)...)
so callinggetindex(A, Point(1.5, 1.5))
will callto_indices(A, (Point(1.5, 1.5),))
.
Thanks for your remark about covariant types, I will do some more reading on the subject.
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 one of these painful decisions. From a performance standpoint I completely understand why you've done it, OTOH it breaks the core equivalence-class property of HomogeneousPoint
. How bad is the hit if you add @assert p.coords[end] == 1
? @assert
statements get skipped if you start julia with julia -O3
.
See [`corner2subpixel`](@ref) for more details of the interpolation scheme. | ||
|
||
""" | ||
function imcorner_subpixel(img::AbstractArray; method::Function = harris, args...) |
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.
@timholy Do (or should) we have a convention for naming catch-all optional positional or named arguments?
Personally I adapted the convention of using args...
for positional arguments and kw...
for keyword arguments.
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.
In this instance I just copied and pasted the current definition of imcorner
, so whatever change you suggest I will then also apply to the existing imcorner
code.
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.
Images developed over a long enough period of time that there isn't as much consistency as would be desirable. I also tend to use args...
for positional, and kw...
or kwargs...
for keywords.
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.
Since I have followed the current convention of imcorner
, how about we leave it as args
for the purpose of this commit? We could raise a new issue for systematically checking the Image package for consistent usage of args and kw? I just noticed that there appear to be several places where kwargs
should have been used, such as gradcovs.
In the meantime, I will address the other issues that Evizero
has pointed out and amend the current commit.
src/corner.jl
Outdated
nrows,ncolumns = size(corner_indicator) | ||
row, col, _ = findnz(corner_indicator) | ||
ncorners = length(row) | ||
corners = fill(Images.HomogeneousPoint2D((0.0,0.0,0.0)),ncorners) |
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.
Let's remove the Images.
prefix here, since we are within the Images
module
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.
Thanks for spotting that, I thought I had systematically removed any reference to Images.
src/corner.jl
Outdated
``` | ||
Refines integer corner coordinates to sub-pixel precision. | ||
|
||
The function takes as input a matrix reresenting corner responses |
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.
typo representing
src/corner.jl
Outdated
\end{bmatrix} | ||
``` | ||
the coefficients of the quadratic polynomial can be found by solving the | ||
system of equations `b = Ax`. The result is given by `x = inv(A)b`. |
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.
Since you used the math environment in the previous paragraph I think its worth considering doing it here for b = Ax
and below as well (the two backticks environment instead of one)
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.
Sure, no worries. Do you know how I might visualise what the LaTeX markup will look like? My version of Juno does not support Latex preview, and I didn't see any such functionality in Visual Studio Code. I'm not sure how to build the documentation so that I can actually see the LaTeX markup.
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.
Do you have access to Jupyter? I think it does the appropriate rendering for docstrings
test/corner.jl
Outdated
@@ -29,6 +29,118 @@ using Base.Test, Images, Colors, FixedPointNumbers | |||
for id in ids expected_corners[id] = 0 end | |||
@test sum(corners .!= expected_corners) < 3 | |||
end | |||
|
|||
@testset "Corners Sub-pixel API" begin |
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.
These are tabs I think. Could you change this to use spaces instead?
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.
How can you tell that they are tabs not spaces? I was using tab, but I thought that the editors (VS Code and Juno) were set up to automatically convert tabs to spaces.
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.
Just by selecting the text with the mouse basically. You'll see that you can just select one giant white space. In contrast if using spaces it would select a bunch of small whitespaces
This looks like a nicely made addition. I left a couple of minor and style related comments. I am afraid I don't know much about the subject itself. Maybe someone else here could review the functionality. |
I'll review this, but first I'd really like to thank @Evizero. In recent months my administrative responsibilities at work have increased enormously, to the point where it's hard for me to put much time into development. Having help with some of the review/bugfix/merge responsibilities will allow me to focus on higher-level issues, and collectively we'll ensure that JuliaImages keeps thriving! |
happy to help where my skillset allows it. You were a great tutor to me when I started working with Images.jl, so I'd like to give back how I can (though my knowledge about image processing is very spotty) |
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.
Love it, this is great! Thanks for being so conscientious about the docstrings and tests.
src/Images.jl
Outdated
@@ -64,6 +66,33 @@ Indicate that `x` should be interpreted as a [percentile](https://en.wikipedia.o | |||
""" | |||
struct Percentile{T} <: Real p::T end | |||
|
|||
|
|||
""" | |||
HomogeneousPoint2D(x::NTuple{3, 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.
Would it be worth calling this
struct HomogeneousPoint{T<:AbstractFloat,N}
coords::NTuple{N,T}
end
The concept is a general one, even if its first application (imcorner
) is currently limited to 2d.
src/Images.jl
Outdated
# divide each coordinate by the last coordinate everytime we attempt to index | ||
# into a matrix which could induce a performance hit. Note also that we return | ||
# a tuple of (y,x) since matrices are indexed according to row and then column. | ||
Base.to_indices(A::AbstractArray, p::Tuple{<: HomogeneousPoint2D}) = round(Int, p[1].coords[2]), round(Int, p[1].coords[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.
This is one of these painful decisions. From a performance standpoint I completely understand why you've done it, OTOH it breaks the core equivalence-class property of HomogeneousPoint
. How bad is the hit if you add @assert p.coords[end] == 1
? @assert
statements get skipped if you start julia with julia -O3
.
src/corner.jl
Outdated
""" | ||
``` | ||
corners = corner2subpixel(responses::Matrix{Float64},corner_indicator::Array{Bool,2}) | ||
-> Vector{HomogeneousPoint2D{3,Float64}} |
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 see you considered having an extra parameter to HomogeneousPoint
already 🙂. Whatever gets finally decided, make sure this docstring is in sync.
src/corner.jl
Outdated
The function takes as input a matrix reresenting corner responses | ||
and a boolean indicator matrix denoting the integer coordinates of a corner | ||
in the image. The output is a vector of type `HomogeneousPoint2D` | ||
(an `NTuple{3, Float64}`) storing the sub-pixel coordinates of the corners. |
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.
change to [`HomogeneousPoint`](@ref)
and drop the statement in parentheses
src/corner.jl
Outdated
invA = @SMatrix [0.5 -1.0 0.5; -0.5 0.0 0.5; 0.0 1.0 -0.0] | ||
for k = 1:ncorners | ||
# Corners on the perimeter of the image will not be interpolated. | ||
if row[k] == 1 || row[k] == nrows || col[k] == 1 || col[k] == ncolumns |
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.
imcorner
works even for non-1 arrays:
img = testimage("cam")
imgo = OffsetArray(img, (-2, 5))
imcorner(imgo)
It would be better to compare to the first
/last
values of indices(corner_indicator)
.
src/corner.jl
Outdated
Corners on the boundary of the image are not refined to sub-pixel precision. | ||
|
||
""" | ||
function corner2subpixel(responses::Matrix{Float64}, corner_indicator::Array{Bool,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.
I would favor more general choices like corner_indicator::AbstractMatrix{Bool}
. See below (re indices
) to see how other types might arise.
src/corner.jl
Outdated
|
||
""" | ||
function unsafe_neighbourhood_4(matrix::Matrix{Float64},r::Int,c::Int) | ||
@views center = matrix[r,c] |
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 you need the @views
here, you're just extracting a single value from the array.
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.
In light of your comment regarding OffsetArray
, I presume I have to change matrix::Matrix{Float64}
to matrix::Union{OffsetArrays.OffsetArray{Float64,2,Array{Float64,2}}, Matrix{Float64}}
?
Similarly, responses::Matrix{Float64}
would need to become responses::Union{OffsetArrays.OffsetArray{Float64,2,Array{Float64,2}}, Matrix{Float64}}
?
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.
Why put a type on at all? (There is no performance advantage to specifying the type.) The only thing that adding the type does here is ensure that a MethodError
gets thrown by an inappropriate call to this function, rather than some error thrown by, e.g., the indexing operations inside the body of this function.
It wouldn't be crazy to restrict it to two-dimensional AbstractArray
s, though:
unsafe_neighbourhood_4(matrix::AbstractMatrix, r::Int, c::Int)
test/corner.jl
Outdated
|
||
ids = map(HomogeneousPoint2D, | ||
[(4.244432486132958, 4.244432486132958, 1.0), | ||
(4.244432486132958, 16.755567513867042, 1.0), |
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.
indentation issues
test/corner.jl
Outdated
@@ -188,4 +300,4 @@ using Base.Test, Images, Colors, FixedPointNumbers | |||
|
|||
end | |||
|
|||
nothing | |||
nothing |
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.
Add blank line at end
72f7db2
to
66fcf68
Compare
The latest pull-request addresses all the comments and suggestions. If I have left anything out I did so unintentionally. |
66fcf68
to
e254a97
Compare
I fixed a typo I spotted in one if the docstrings, |
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.
Just the docstring change and this is ready to go.
src/Images.jl
Outdated
# the corresponding Cartesian coordinates. | ||
Base.to_indices(A::AbstractArray, p::Tuple{<: HomogeneousPoint}) = homogeneous_point_to_indices(A, p) | ||
|
||
function homogeneous_point_to_indices(A::AbstractArray, p::Tuple{<: HomogeneousPoint}) |
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 might be prettier if you extract the HomogeneousPoint in to_indices
and just pass that:
Base.to_indices(A::AbstractArray, p::Tuple{<: HomogeneousPoint}) = homogeneous_point_to_indices(p[1])
because then you don't have to write p[1]
everywhere in homogeneous_point_to_indices
.
A more typical "Julian" style would be to avoid the branch and provide two methods:
function homogeneous_point_to_indices(p::HomogeneousPoint{T,3}) where T
if p.coords[end] == 1
...
end
function homogeneous_point_to_indices(p::HomogeneousPoint)
if p.coords[end] == 1
...
end
However, the compiler should be smart enough that it will evaluate your if (length(p[1]).coords) == 3
at compile time so it avoids introducing another branch (in other words, in terms of generated code yours will be the same as mine).
All these are optional, just wanted you to know.
src/corner.jl
Outdated
""" | ||
``` | ||
corners = corner2subpixel(responses::AbstractMatrix,corner_indicator::AbstractMatrix{Bool}) | ||
-> Vector{HomogeneousPoint{3,Float64}} |
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.
Parameters are swapped (should be Float64,3
).
Introduces the function imcorner_subpixel as well as several requisite support functions to facilitate the ability to detect corners to sub-pixel precision. The imcorner_subpixel uses the existing imcorner functionality to obtain initial corner response values and a binary corner indicator matrix. The corner response values and the indicator matrix are used in a univariate quadratic interpolation scheme to determine the corner coordinates to sub-pixel precision. The new imcorner_subpixel function returns a length-3 NTuple of type HomogeneousPoint, which represents the homogeneous coordinates of a planar point.
e254a97
to
d611b92
Compare
Done. Thank you for all the excellent advice. |
Sorry I forgot to hit merge! Thanks again! |
@timholy If I want this to appear when I do |
Just tagged (will have to wait for the METADATA.jl PR to be merged). Thanks for the reminder! |
Introduces the function imcorner_subpixel as well as several requisite
support functions to facilitate the ability to detect corners to
sub-pixel precision.
The imcorner_subpixel uses the existing imcorner functionality to obtain
initial corner response values and a binary corner indicator matrix. The
corner response values and the indicator matrix are used in a univariate
quadratic interpolation scheme to determine the corner coordinates to
sub-pixel precision.
The new imcorner_subpixel function returns a length-3 NTuple of type
HomogeneousPoint2D, which represents the homogeneous coordinates of a
planar point.