-
Notifications
You must be signed in to change notification settings - Fork 29
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
Rationale for Vector of CartesianIndex{2} for output 'matches' of match_keypoints #45
Comments
Also relevant: while this package is pretty 2d-focused, I intend someday to add some features suitable for 3d biomedical images. Overall JuliaImages is in much better shape than most image frameworks for unifying "computer vision" and "multidimensional biomedical imaging"; traditionally those are pretty separate fields, but the intent with JuliaImages is to make sure the two communities can leverage each others' efforts where applicable. Hence we emphasize general constructs rather than special hacks when possible. |
I think I focused on only a portion of your question, the I would agree that one vector or 2 is more debatable. This is really the age-old struct-of-arrays vs array-of-structs question. In the current model we return a list of pairs, and I think you're asking for a pair of lists. That's not unreasonable, but I'm not certain one is a lot better than another. If we switched to a tuple model, you could do the following: julia> a = rand(1:100, 4, 100)
4×100 Array{Int64,2}:
69 26 73 57 70 82 96 16 90 58 87 7 24 37 51 20 3 88 7 52 42 … 70 76 31 30 59 97 93 61 84 45 12 36 42 98 67 79 4 76 48 68
54 87 69 62 60 4 52 39 25 93 1 3 53 26 52 41 99 52 95 7 46 79 72 39 46 29 82 18 73 21 21 15 36 37 67 41 43 61 33 42 87
36 13 97 2 9 93 96 91 20 36 12 60 65 83 96 16 99 37 59 63 89 95 41 64 15 21 7 15 35 5 53 64 57 98 18 34 20 12 26 48 100
11 81 43 52 8 100 6 21 25 20 1 90 4 23 56 78 71 59 93 48 12 86 70 25 54 2 23 75 55 13 54 21 54 20 67 30 98 36 39 94 62
julia> b = reinterpret(NTuple{2,CartesianIndex{2}}, a, (100,))
100-element Array{Tuple{CartesianIndex{2},CartesianIndex{2}},1}:
(CartesianIndex{2}((69, 54)), CartesianIndex{2}((36, 11)))
... and its converse. Would that help? As far as elegant ways to work with the current structure, now that I've looked I'd agree some operations are a little awkward now. You have to implement the mean of matches across the first image like this: julia> (sum(p->p[1], b).I)./length(b)
(49.89, 51.13) You're right that's not as easy as |
Many thanks for the clarifications and examples. I think switching to a tuple model, One benefit of keeping a 'list of pairs' model like you currently do would be when one considers matches between a sequence of views. In that case, one might naturally expect the If you switch to a 'pair of lists', then when you have three views you would need to return three lists etc. and when working with a video sequence this may get unwieldy very quickly. One feature that would be nice to have is if there was an option to ask the |
I usually use GeometryTypes.Point for transformations. I just like to call it Point in GeometryTypes, but this is under the hood just a StaticArrays.SVector, so they're interchangeable. points::Vector{Point{2, Float32}}
const mat = eye(Mat4f0) # static 4x4 matrix of Float32
map!(points, points) do p
p4 = mat * Point(p[1], p[2], 0, 1)
(p4[1], p4[2]) # might as well return a tuple, since `convert(Point, x::Tuple)` is defined
end Which uses much less memory and is very fast! Of course you can also do: indices::Vector{CartesianIndex{2}}
points = reinterpret(SVector{2, Int}, indices) |
@timholy How do you handle the case where Keypoints are detected at the sub-pixel level? If the Keypoints are defined as |
Yes, I agree there's reason to support sub-pixel alignment. Are there established methods for finding such keypoints? If so, I'm open to switching. |
Yes, there are established methods for finding keypoints to sub-pixel accuracy. Actually, the methods are quite generic and can be applied regardless of what particular keypoint detection algorithm was employed. For example, one approach is based on fitting a quadratic to a neighbourhood of pixels surrounding the keypoint and taking the sub-pixel estimate as the minimum of the quadratic. An alternative approach is to consider the x and y coordinates separately and then fit a parabola through 3 points---the keypoint and its two immediate neighbours. There are of course several other methods, some of which are customised for camera calibration. Sub-pixel accuracy is a necessity in multiple-view geometry. What alternative to |
Yes, I think that's a great choice. |
If we make the proposed change, we may presumably break peoples code? How do we proceed from here? What is the formal process of deprecating the code? Do you have an inkling of other code in JuliaImages that may be affected? |
Great questions. Wherever possible, we don't break things, and Julia has a nice I think there are two potential ways we could go. One would be to create a matches = NTuple{2,SVector{N,Float64}}[] # N will be something specific here, of course
match_keypoints!(matches, keypoints1, keypoints2, ...) If it's possible to make matches = Vector{Vector{CartesianIndex{N}}} then there's really nothing lost in this change. It might be hard to handle both a vector and a tuple however (I wouldn't know until trying). In that case I'm inclined to switch to the tuple anyway, all those The second is to not worry about it, make the switch, and hope that by using a version bump we allow people to cope as needed by introducing version bounds in the package manager. We're going to have to bump the minor version anyway because if we change the default output type, that's a breaking change. As far as other dependencies, I'm not sure. I tihnk of ImageFeatures as one of the "top level" packages in the JuliaImages ecosystem, which is borne out by julia> Pkg.dependents("ImageFeatures")
0-element Array{AbstractString,1} (It was registered only a few months ago, so is fairly new in terms of its visibility to the package manager.) So in terms of registered packages, there is nothing to worry about. People's private code might be a different matter, of course. |
@timholy What if we modify Thinking beyond the particular problem of sub-pixel corners, I can imagine that one may want to be able to iterate over a multi-dimensional array by using the integer part of a set of coordinates. For example, perhaps our data is represented by a discrete set of voxels, but we have done some interpolation and so our coordinates do not fall on the voxel grid. Nevertheless, we may want to write algorithms that access the discrete neighbours. Do you think a modification of |
You could also define your own point type, and define a
You could also implement interpolation for that type! It would be nice if this could be a descendant of |
@SimonDanisch Thank you very much for the advice and for taking the time to come up with an example. The example was very helpful. Since I am still familiarising myself with Julia it took me a while to understand the example. Hence I decided to write down what I understood for the benefit of any other newcomer who may stumble upon this thread. The example:
The type The function This function is implemented as follows:
The statement :
says that each element of the x should be rounded to the nearest integer. The The second argument of I got stuck on the fact that I was thinking that if we make the change to |
That's indeed confusing and is the aftermath of indexing a multi dimensional array with a single element.
so calling |
I think that's a bad idea - personally I don't have anything against it. But all floating point indexing got deprecated in Julia Base, so I imagine that you will have a hard time getting back floating point indexing into a base type. |
Especially if one considers, that an interpolated |
I was wondering if someone could explain the design choice for making the match_keypoints function return
Vector{CartesianIndex{2}}
.Coming from MATLAB, I was expecting this function to return two matrices, either an N x 2 matrix, or a 2 x N matrix, where the first matrix contains the points in the first view, and the second matrix contains the points in the second view. I work in the field of Multiple View Geometry (MVG), and am in the process of writing a package for Julia that achieves feature parity with MATLAB's Computer Vision toolbox. Typical steps in MVG include converting between homogeneous coordinates (i.e. appending an extra dimension with the value of '1'), standardising the homogeneous coordinates (dividing each point by its third dimension) and converting back to Cartesian coordinates (removing the third coordinate after standardising). Furthermore, we often need to transform coordinates by making the data points have zero mean, and scaling each data point to lie inside a unit box. These steps are very easy when the points are represented as matrices. For example, it allows one to write code such as mean(pts[1,:]) to obtain the mean x coordinate. Transforming all of the data points is also simple, since it just involves multiplying the points by a matrix.
I am struggling to perform these types of operations elegantly with the Vector{CartesianIndex{2}} data structure. At present, I intend to loop over this data structure and explicitly form the two matrices I mentioned. However, this duplication of work and unnecessary performance hit feels like a hack.
Perhaps we could write a second variant of the match_keypoints function that returns two matrices instead? Or alternatively, there is some elegant Julian way of achieving the transformations I mentioned with this Vector of CartesianIndex type?
The text was updated successfully, but these errors were encountered: