-
-
Notifications
You must be signed in to change notification settings - Fork 827
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
Integration of AprilTag library #950
Conversation
(alicevision#949). For now, only the smallest family of tags (tag16h5) is supported, which consists of 30 coded tags. For each marker, 5 keypoints are generated: - the center point (computed from the four corner points), with IDs 0-29 - the 4 corner points (in counter-clockwise order), with IDs 30-59, 60-89, 90-119, and 120-149 Thus, the feature descriptor has 150 dimensions. The implementation follows along the lines of the CCTag markers, whereever possible. In most places where CCTags were used before, AprilTags are now an additional option. Exceptions are the camera and rig calibration and localization functions, as well as a function which seems to create visualizations of feature matching results. The Dockerfile for Centos has been adapted to start an AliceVision build using AprilTags. I did not dare touch the Dockerfiles for Ubuntu, as the Centos one seemed to be the main one in use, and it also was the one where the build process went quite smooth. This has not been tested on Windows, sorry. It seems that AprilTags officially support only Linux, although Windows should be possible according to the official repo. There is one change which I do not really like, where it might make sense to change the underlying classes/interfaces a bit to make different marker feature extractors easier. This is the change in src/aliceVision/sfm/pipeline/ReconstructionEngine.cpp, which only works by testing the results of dynamic casts on feature::Regions subclasses, although the code that follows is rather similar for both CCTags and AprilTags, and could probably substituted by something more general. The reason for this is that the marker ID is computed from the descriptor, for which there could be an abstract function in the superclass. I did not dare to make any such deep changes to the code, though.
Ouch, sorry, just pushed another commit to the fork which was meant for a different branch, not realizing that this would go into this pull request. |
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.
Hi, thanks for the nice PR!
The major remark is to avoid having the opencv dependency when writing code for apritag as it is not strictly necessary. I think it should not be much work to remove that.
Other than that it makes a very neat addition to the lib!
ImageDescriber_APRILTAG::AprilTagParameters::AprilTagParameters() | ||
{ | ||
} |
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.
ImageDescriber_APRILTAG::AprilTagParameters::AprilTagParameters() | |
{ | |
} | |
ImageDescriber_APRILTAG::AprilTagParameters::AprilTagParameters() = default |
U can even move it to the hpp
const cv::Mat graySrc(cv::Size(image.Width(), image.Height()), CV_8UC1, (unsigned char *) image.data(), cv::Mat::AUTO_STEP); | ||
// Make an image_u8_t header for the Mat data |
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 think aprilTag (the library) does not depends on opencv.
Opencv is optional in alicevision, it would be nice keep aprilTag independent from opencv.
Otherwise, even in the cmake u should tie aprilTag to the fact that opencv is ON
Here for example i think u can drop the cv::Mat and use image
to fill the image_u8_t
cv::Point2d tl(det->p[0][0], det->p[0][1]); | ||
cv::Point2d bl(det->p[1][0], det->p[1][1]); | ||
cv::Point2d br(det->p[2][0], det->p[2][1]); | ||
cv::Point2d tr(det->p[3][0], det->p[3][1]); | ||
double denominator = ((tl.x-br.x) * (bl.y-tr.y) - (tl.y-br.y) * (bl.x-tr.x)); | ||
cv::Point2d center( | ||
((tl.x*br.y - tl.y*br.x) * (bl.x-tr.x) - (tl.x-br.x) * (bl.x*tr.y - bl.y*tr.x)) / denominator, | ||
((tl.x*br.y - tl.y*br.x) * (bl.y-tr.y) - (tl.y-br.y) * (bl.x*tr.y - bl.y*tr.x)) / denominator | ||
); | ||
cv::Point2d points[5] = { center, tl, bl, br, tr }; | ||
std::size_t indices[5] = { det->id, 30 + det->id, 60 + det->id, 90 + det->id, 120 + det->id}; | ||
// compute scale from max side length and diagonals (divided by sqare root of 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.
same here, cv:Point2d can be replaced by Vec2
, less handy to manage (no .x and .y) but it eases things for the dependencies
cv::Point2d points[5] = { center, tl, bl, br, tr }; | ||
std::size_t indices[5] = { det->id, 30 + det->id, 60 + det->id, 90 + det->id, 120 + det->id}; | ||
// compute scale from max side length and diagonals (divided by sqare root of 2): | ||
double scale = std::max({cv::norm(tl-bl), cv::norm(bl-br), cv::norm(br-tr), cv::norm(tr-tl), 0.707*cv::norm(tl-br), 0.707*cv::norm(tr-bl)}); |
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.
double scale = std::max({cv::norm(tl-bl), cv::norm(bl-br), cv::norm(br-tr), cv::norm(tr-tl), 0.707*cv::norm(tl-br), 0.707*cv::norm(tr-bl)}); | |
const double scale = std::max({cv::norm(tl-bl), cv::norm(bl-br), cv::norm(br-tr), cv::norm(tr-tl), 0.707*cv::norm(tl-br), 0.707*cv::norm(tr-bl)}); |
const feature::CCTAG_Regions* cctagRegions = dynamic_cast<const feature::CCTAG_Regions*>(®ions); | ||
const feature::APRILTAG_Regions* apriltagRegions = dynamic_cast<const feature::APRILTAG_Regions*>(®ions); |
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.
@fabiencastan Not sure what happens here, but i have the impression that the 2 dyn cast can work even if the the input regions are not the correct type.
Maybe we should have a "special" region type "markers" from which all the other (cctag, april...) inherit and refactor the code to retrieve the iID?
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 changed the existing dynamic cast from reference to pointer. If the input regions do not have the requested type the result will be a null pointer. This is used in the following if statements to execute the piece of code appropriate for the marker type.
I agree that some kind of marker polymorphism might be good once there are multiple types of markers, but I did not dare making any such deep changes together with this otherwise rather shallow (in terms of change of the code base) pull request...
AprilTag is not yet integrated in vcpkg, but there is an issue for it: |
@elektrokokke Very sorry for the delay to merge this nice PR. |
- Removed all usages of OpenCV. - Removed empty constructor definition in favor of "= default" in header. - Changed scale to const.
06f8339
to
5c3b8cc
Compare
@fabiencastan sorry this took so long. Just removed the two commits as requested. |
Thanks. Could you also rebase it on the "develop" branch to fix the small conflict? |
I hope I got this one right... |
Hi, I'm the maintainer of the AprilTag project. I don't know anything about your application, but I would very much recommend using any other tag family than tag16h5. Since it has the fewest bits of any of our tag families, it is the most likely to have false positives. If you have no specific requirements for the choice of a tag family, the default choice should be tagStandard41h12 |
@mkrogius Thanks a lot for the feedback! As a first step, I will focus on getting this merged (as it's waiting for too long!) and I hope @elektrokokke will have time to look how we can update to other marker types in another PR (which will probably require to add a new way to deal with markers vs descriptors to support more IDs). |
@mkrogius and @elektrokokke, just for future reference, the only part of code that changes from one type to another is this one? apriltag_family_t *tf = tag16h5_create();
apriltag_detector_t *td = apriltag_detector_create();
apriltag_detector_add_family(td, tf); Say we want to switch to tagStandard41h12, we just need to have (I guess) something like |
tagStandard41h12_create(); ^^ Yep, just like that. Although if you've already printed out tags then you'd have to print out stuff in the new tag family |
Hi!
I would say that ideally the way markers are handled would be changed.
Right now they are descriptor with a dimensionality corresponding to the
number of possible marker ids and only one dimension is non zero for any
one marker. It would make sense IMO to encode a marker’s id as just an
integer.
But for now, in addition to the listed changes one would need to adjust the
dimensionality of the descriptor. For larger tag families this quickly gets
large, and it’s wasted considering only one integer could do the same. Also
computationally it does not scale well, as the current code iterates over
one descriptor to get back the marker ID. Marker matching is similarly
expensive.
Note that the implementation right now produces 5 markers for each tag, one
for the center and four for the corners.
Cheers,
Jarne
… Hi, I'm the maintainer of the AprilTag project. I don't know anything
about your application, but I would very much recommend using any other tag
family than tag16h5. Since it has the fewest bits of any of our tag
families, it is the most likely to have false positives. If you have no
specific requirements for the choice of a tag family, the default choice
should be tagStandard41h12
@mkrogius <https://github.com/mkrogius> and @elektrokokke
<https://github.com/elektrokokke>, just for future reference, the only
part of code that changes from one type to another is this one?
apriltag_family_t *tf = tag16h5_create();
apriltag_detector_t *td = apriltag_detector_create();
apriltag_detector_add_family(td, tf);
Say we want to switch to tagStandard41h12, we just need to have (I guess)
something like apriltag_family_t *tf = tag41h12_create();?
Or do we also need, e.g. change the way the id are encoded in the
descriptors?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#950 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEHUECROEO7EZW6Q2Y6VFTTMP3V5ANCNFSM4UW4XVXA>
.
|
(fix #949).
For now, only the smallest family of tags (tag16h5) is supported,
which consists of 30 coded tags.
For each marker, 5 keypoints are generated:
Thus, the feature descriptor has 150 dimensions.
The implementation follows along the lines of the CCTag markers, wherever possible.
In most places where CCTags were used before, AprilTags are now an additional option.
Exceptions are the camera and rig calibration and localization functions,
as well as a function which seems to create visualizations of feature matching results.
The Dockerfile for Centos has been adapted to start an AliceVision build using AprilTags.
I did not dare touch the Dockerfiles for Ubuntu, as the Centos one seemed to be the main one in use,
and it also was the one where the build process went quite smooth.
This has not been tested on Windows, sorry. It seems that AprilTags officially support only Linux,
although Windows should be possible according to the official repo.
There is one change which I do not really like, where it might make sense to change the
underlying classes/interfaces a bit to make different marker feature extractors easier.
This is the change in src/aliceVision/sfm/pipeline/ReconstructionEngine.cpp, which
only works by testing the results of dynamic casts on feature::Regions subclasses, although
the code that follows is rather similar for both CCTags and AprilTags, and could probably
substituted by something more general. The reason for this is that the marker ID is computed
from the descriptor, for which there could be an abstract function in the superclass.
I did not dare to make any such deep changes to the code, though.