diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..05609e1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,71 @@ +cmake_minimum_required (VERSION 2.8.3) +project (lidar_tag) + +# CMAKE TWEAKS +#======================================================================== +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O3 -DEIGEN_NO_DEBUG -march=native -Wl,--no-as-needed") + +find_package(catkin REQUIRED COMPONENTS + roscpp + rospy + tf + sensor_msgs + visualization_msgs + message_filters + std_msgs + velodyne_pointcloud + roslib + lidartag_msgs +) +# CHECK THE DEPENDENCIES +# PCL +find_package(PCL 1.2 REQUIRED) +find_package(Boost REQUIRED + COMPONENTS filesystem system signals regex date_time program_options thread +) +find_package(Eigen3) +if(NOT EIGEN3_FOUND) + # Fallback to cmake_modules + find_package(cmake_modules REQUIRED) + find_package(Eigen REQUIRED) + set(EIGEN3_INCLUDE_DIRS ${EIGEN_INCLUDE_DIRS}) + set(EIGEN3_LIBRARIES ${EIGEN_LIBRARIES}) # Not strictly necessary as Eigen is head only + # Possibly map additional variables to the EIGEN3_ prefix. +else() + set(EIGEN3_INCLUDE_DIRS ${EIGEN3_INCLUDE_DIR}) +endif() + +catkin_package( + INCLUDE_DIRS include + CATKIN_DEPENDS + message_filters + roscpp + sensor_msgs + std_msgs + tf + + DEPENDS + Eigen3 +) + +include_directories( + include + ${catkin_INCLUDE_DIRS} + ${PCL_INCLUDE_DIRS} + ${Boost_INCLUDE_DIRS} + ${EIGEN3_INCLUDE_DIRS} +) +link_directories(${PCL_LIBRARY_DIRS}) +add_definitions(${PCL_DEFINITIONS}) + +# COMPILE THE SOURCE +#======================================================================== +add_executable(lidar_tag_main src/main.cc src/lidar_tag.cc src/apriltag_utils.cc src/utils.cc src/tag49h14.cc src/tag16h5.cc) +add_dependencies(lidar_tag_main ${${PROJECT_NAME}_EXPORTED_TARGETS} + ${catkin_EXPORTED_TARGETS}) +target_link_libraries(lidar_tag_main + ${catkin_LIBRARIES} + ${Boost_LIBRARIES} + ${PCL_LIBRARIES} +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5cf59ac --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# LiDARTag +## Overview + +This is a ROS package for detecting the LiDARTag via ROS. + +* Author: Bruce JK Huang +* Maintainer: [Bruce JK Huang](https://www.brucerobot.com/), brucejkh[at]gmail.com +* Affiliation: [The Biped Lab](https://www.biped.solutions/), the University of Michigan + +This package has been tested under [ROS] Kinetic and Ubuntu 16.04. +More detail introduction will be update shortly. Sorry for the inconvenient! + +## Quick View + + + +# Why LiDAR? +Robust to lighting!! + + + + + + +## Presentation and Video +https://www.brucerobot.com/lidartag + +## Installation +TODO + +## Parameters +TODO + +## Examples +TODO + +## Citations +The LiDARTag is described in: + +*Jiunn-Kai Huang, Maani Ghaffari, Ross Hartley, Lu Gan, Ryan M. Eustice and Jessy W. Grizzle “LiDARTag: A Real-Time Fiducial Tag using Point Clouds” (under review) + + diff --git a/figure/figure1-2.png b/figure/figure1-2.png new file mode 100644 index 0000000..895a9cb Binary files /dev/null and b/figure/figure1-2.png differ diff --git a/figure/figure1.png b/figure/figure1.png new file mode 100644 index 0000000..8d17536 Binary files /dev/null and b/figure/figure1.png differ diff --git a/figure/lighting1.jpg b/figure/lighting1.jpg new file mode 100644 index 0000000..87d2693 Binary files /dev/null and b/figure/lighting1.jpg differ diff --git a/figure/lighting2.jpg b/figure/lighting2.jpg new file mode 100644 index 0000000..20dadd3 Binary files /dev/null and b/figure/lighting2.jpg differ diff --git a/figure/lighting3.jpg b/figure/lighting3.jpg new file mode 100644 index 0000000..3622b38 Binary files /dev/null and b/figure/lighting3.jpg differ diff --git a/include/apriltag_utils.h b/include/apriltag_utils.h new file mode 100644 index 0000000..2651857 --- /dev/null +++ b/include/apriltag_utils.h @@ -0,0 +1,59 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + +#ifndef APRILTAG_UTILS_H +#define APRILTAG_UTILS_H + +#include "lidar_tag.h" + +namespace BipedAprilLab{ + + static inline int imax(int a, int b); + + + /** if the bits in w were arranged in a d*d grid and that grid was + * rotated, what would the new bits in w be? + * The bits are organized like this (for d = 3): + * + * 8 7 6 2 5 8 0 1 2 + * 5 4 3 ==> 1 4 7 ==> 3 4 5 (rotate90 applied twice) + * 2 1 0 0 3 6 6 7 8 + **/ + uint64_t rotate90(uint64_t w, uint32_t d); + + void QuickDecodeAdd(BipedLab::QuickDecode_t *qd, uint64_t code, int id, int hamming); + + void QuickDecodeInit(BipedLab::GrizTagFamily_t *family, int maxhamming); + void QuickDecodeCodeword(BipedLab::GrizTagFamily_t *tf, uint64_t rcode, + BipedLab::QuickDecodeEntry_t *entry); +} // namespace +#endif + + diff --git a/include/lidar_tag.h b/include/lidar_tag.h new file mode 100644 index 0000000..5a44afd --- /dev/null +++ b/include/lidar_tag.h @@ -0,0 +1,482 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + + + +#pragma once +#ifndef LIDARTAG_H +#define LIDARTAG_H + +#include // std::queue +#include + +#include +#include +#include +#include +#include // Marker +#include // Marker + +#include + +// To trasnform to pcl format +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lidartag_msgs/LiDARTagDetection.h" +#include "lidartag_msgs/LiDARTagDetectionArray.h" +#include "types.h" + +// #include "tensorflow_ros_test/lib.h" + +namespace BipedLab{ + class LiDARTag{ + public: + LiDARTag(); + + private: + /***************************************************** + * Variables + *****************************************************/ + // Flags for functioning + int _adaptive_thresholding; // Use adaptive thresholding or not + int _collect_dataset; // To collect dataset (only publish one tag) + int _sleep_to_display; // Sleep for visualization + double _sleep_time_for_vis; // Sleep for how long? + int _valgrind_check; // Debugging with Valgrind + int _fake_tag; + int _decode_method; // Which decode methods to use? + int _grid_viz; // visualize remapping grid + bool _id_decoding; // decode id or not + bool _write_CSV; // Write CSV files + std::string _assign_id; // Directly assign Id, mainly for calibration usage + + // ROS + ros::NodeHandle _nh; + ros::Subscriber _LiDAR1_sub; + ros::Publisher _original_pc_pub; + ros::Publisher _edge_pub; + ros::Publisher _cluster_pub; + ros::Publisher _payload_pub; + + ros::Publisher _cluster_marker_pub; // cluster markers + ros::Publisher _boundary_marker_pub; // cluster boundary + ros::Publisher _payload_marker_pub; // payload boundary + ros::Publisher _payload_grid_pub; // grid visualization + ros::Publisher _payload_grid_line_pub; // grid visualization + ros::Publisher _lidartag_pose_pub; // Publish LiDAR pose + + // Flag + int _point_cloud_received; // check if a scan of point cloud has received or not + int _stop; // Just a switch for exiting this program while using valgrind + + // ros::Publisher DebugPointCheckPub_; // Debug + // ros::Publisher DebugBoundaryPointPub_; // Debug + // ros::Publisher DebugNoEdgePub_; // Debug + // ros::Publisher DebugExtractPayloadPub_; // Debug + + //ros::Publisher TextPub_; // publish text + // visualization_msgs::MarkerArray _markers; + + + // Queue for pc data + std::queue _point_cloud1_queue; + + // lock + boost::mutex _refine_lock; + boost::mutex _buff_to_pcl_vector_lock; + boost::mutex _point_cloud1_queue_lock; + + // LiDAR parameters + ros::Time _current_scan_time; // store current time of the lidar scan + std::string _pointcloud_topic; // subscribe channel + std::string _pub_frame; // publish under what frame? + + // Overall LiDAR system parameters + LiDARSystem_t _LiDAR_system; + int _beam_num; + int _vertical_fov; + + // PointCould data (for a single scan) + int _point_cloud_size; + std_msgs::Header _point_cloud_header; + + // Edge detection parameters + double _intensity_threshold; + double _depth_threshold; + + // If on-board processing is limited, limit range of points + double _distance_bound; + + // fiducial marker parameters + double _payload_size; // physical payload size + int _tag_family; // what tag family ie tag16h5, this should be 16 + int _tag_hamming_distance; // what tag family ie tag16h5, this should be 5 + int _max_decode_hamming; // max hamming while decoding + int _black_border; // black boarder of the fiducial marker + + // Cluster parameters + double _threshold; + double _RANSAC_threshold; + int _fine_cluster_threshold; // TODO: REPLACE WITH TAG PARAMETERS + int _filling_gap_max_index; // TODO: CHECK + int _filling_max_points_threshold; //TODO: REMOVE + double _points_threshold_factor; // TODO: CHECK + + // Payload + double _line_intensity_bound; // TODO: not sure but may okay to remove it for releasing + double _payload_intensity_threshold; + int _min_returns_per_grid; + GrizTagFamily_t *tf; + lidartag_msgs::LiDARTagDetectionArray _lidartag_pose_array; // an array of apriltags + + + // NN + // LoadWeight *NNptr_; + std::string _latest_model; + std::string _weight_path; + int _max_point_on_payload; + int _XYZRI; // input channels + + // Debug + Debug_t _debug_cluster; + Timing_t _timing; + + // Statistics + Statistics_t _result_statistics; + + + /***************************************************** + * Functions + *****************************************************/ + + /* [Main loop] + * main loop of process + */ + void _mainLoop(); + + + /* [basic ros] + * A function to get all parameters from a roslaunch + * if not get all parameters then it will use hard-coded parameters + */ + void _getParameters(); + + + /* [basic ros] + * A function of ros spin + * reason: in order to put it into background as well as able to run other tasks + */ + inline void _rosSpin(); + + + /* [basic ros] + * A function to make sure the program has received at least one pointcloud + * from subscribed channel + * at the very start of this program + */ + inline void _waitForPC(); + + + /* [basic ros] + * A function to push the received pointcloud into a queue in the class + */ + inline void _pointCloudCallback(const sensor_msgs::PointCloud2ConstPtr &pc); + + + /* [Transform/Publish] + * A function to transform pcl msgs to ros msgs and then publish + * WhichPublisher should be a string of "organized" or "original" regardless + * lowercase and uppercase + */ + void _publishPC(const pcl::PointCloud::Ptr &t_source_PC, + const std::string &t_frame_name, std::string t_which_publisher); + + /* [Type transformation] + * A function to transform from a customized type (LiDARpoints_t) of vector of + * vector (EdgeBuff) into a standard type (PointXYZRI) of pcl vector (out) + */ + void _buffToPclVector(const std::vector> &t_edge_buff, + pcl::PointCloud::Ptr t_out); + + /* [Pre-Processing] + * A function to slice the Veloydyne full points to sliced pointed + * based on ring number + */ + inline void _fillInOrderedPC(const pcl::PointCloud::Ptr &t_pcl_pointcloud, + std::vector< std::vector > &t_ordered_buff); + + + /* [Type transformation] + * A function to get pcl OrderedBuff from a ros sensor-msgs form of + * pointcould queue + */ + std::vector> _getOrderBuff(); + // void GetOrderBuff(std::vector>& OrderedBuff); + + + /* [LiDAR analysis] + * A function to get a LiDAR system parameters such as max, min points per scan + * and how many points per ring + */ + void _analyzeLiDARDevice(); + + + /* [LiDAR analysis] + * A function to find maximum points and minimum points in a single scan, i.e. to + * find extrema within 32 rings + */ + void _maxMinPtsInAScan(std::vector &t_point_count_table, + std::vector &t_max_min_table, + std::vector &t_ring_average_table, + const std::vector> &t_ordered_buff); + + + /* [LiDAR analysis] + * A function to calculate how many points are supposed to be on a cluster at 1 + * meter away + */ + void _pointsPerSquareMeterAtOneMeter(); + + + /* + * A function to get a number of points on a given-distance tag or object + * from LiDAR analysis + */ + int _areaPoints(const double &t_distance, const double &t_obj_width, const double &t_obj_height); + + + + /* [LiDARTag detection] + * Given lidar pointcloud, this function performs + * lidartag detection and decode the corresponding id + */ + pcl::PointCloud::Ptr + _lidarTagDetection(const std::vector> &t_ordered_buff, + std::vector &t_cluster_buff); + + + /* [Edge detection and clustering] + * A function to + * (1) calculate the depth gradient and the intensity gradient at a point of a pointcloud + * (2) group detected 'edge' into different group + */ + void _gradientAndGroupEdges(const std::vector> &t_ordered_buff, + std::vector> &t_edge_buff, + std::vector &t_cluster_buff); + + + /* [Clustering-Linkage] + * A function to cluster a single edge point into a new cluster or an existing cluster + */ + void _clusterClassifier(const LiDARPoints_t &point, std::vector &t_cluster_buff); + + + /* [Clustering-Update] + * A function update some information about a cluster if this point belongs to + * this cluster; if not belonging to this cluster then return and create a new + * one + */ + void _updateCluster(const LiDARPoints_t &t_point, + ClusterFamily_t &t_old_cluster, + TestCluster_t *t_new_cluster); + + + + /* [Clustering-Validation] + * A function to + * (1) remove invalid cluster based on the index is too far or not + * (2) fill in the points between index of edges + * (3) after filling, if the points are too less (based on the analyzed system + * and given distant of the cluster), then remove this cluster + * (4) Adaptive thresholding (Maximize and minimize intensity) by comparing + * with the average value + */ + void _fillInCluster(const std::vector> &t_ordered_buff, + std::vector &t_cluster_buff); + + + /* [Clustering-Validation] + * A valid cluster, valid tag, the points from the original point cloud that belong to the cluster + * could be estimated from the LiDAR system + * Therefore, if the points on the tag is too less, which means it is not a valid + * tag where it might just a shadow of a valid tag + */ + bool _clusterPointsCheck(ClusterFamily_t &t_cluster); + + + /* [Clustering-Validation] TODO:RENAME + * A function to + * (1) do adaptive thresholding (Maximize and minimize intensity) by comparing + * with the average value and + * (2) sort points with ring number and re-index with current cluster into + * tag_edges vector so as to do regression boundary lines + * (3) It will *remove* if linefitting fails + */ + bool _adaptiveThresholding(ClusterFamily_t &t_cluster); + + + /* [Clustering-Validation] + * A function to fit 4 lines of a payload in a cluster by + * (1) finding the edges of the payload (how to find is stated below) + * (2) rejecting and removing the cluster if one of the line is too short + */ + bool _detectPayloadBoundries(ClusterFamily_t &t_cluster); + + + /* [Payload extraction] + * A function to extract the payload points from a valid cluster. + * Let's say we have 10 points on the left boundary (line) of the tag and 9 points on the right boundary + * (line) of the tag. + * It is separated into two parts. + * TODO: should use medium instead of max points + * (i) Find the max points that we have on a ring in this cluster by + * exmaming the average points on the first 1/2 rings int((10+9)/4) + * (ii) For another half of the rings, we just find the start index and add the + * average number of points to the payload points + */ + void _extractPayloadWOThreshold(ClusterFamily_t &t_cluster); + + + + + /* [Type transformation] + * A function to transform an eigen type of vector to pcl point type + */ + void _eigenVectorToPointXYZRI(const Eigen::Vector4f &t_vector, PointXYZRI &t_point); + + + /* [Normal vector] + * A function to estimate the normal vector of a potential payload + */ + Eigen::MatrixXf _estimateNormalVector(ClusterFamily_t &cluster); + + + /* [Pose: tag to robot] + * A function to publish pose of tag to the robot + */ + void _tagToRobot(const int &t_cluster_id, const Eigen::Vector3f &t_normal_vec, + Homogeneous_t &t_pose, + tf::Transform &t_transform, const PointXYZRI &t_ave); + + + /* [Payload decoding] + * A function to decode payload with different means + * 0: Naive decoding + * 1: Weighted Gaussian + * 2: Deep learning + * 3: Gaussian Process + * 4: ?! + */ + bool _decodPayload(ClusterFamily_t &t_cluster); + + + /* [Decoder] + * A function to determine a codeword on a payload using equal weight + * methods + */ + void _getCodeNaive(std::string &t_code, pcl::PointCloud t_payload); + + + /* [Decoder] + * Decode using Weighted Gaussian weight + * return 0: normal + * return -1: not enough return + * return -2: fail corner detection + */ + int _getCodeWeightedGaussian(std::string &Code, Homogeneous_t &t_pose, + int &t_payload_points, + const PointXYZRI &ave, + const pcl::PointCloud &t_payload, + const std::vector &t_payload_boundary_ptr); + + /* [Decoder] + * Create hash table of chosen tag family + */ + void _initDecoder(); + + + /* [Decoder] + * Feed a code to test if initialized correctly + */ + void _testInitDecoder(); + + + /* [ros-visualization] + * A function to prepare for visualization results in rviz + */ + void _clusterToPclVectorAndMarkerPublisher(const std::vector &t_cluster, + pcl::PointCloud::Ptr t_out_cluster, + pcl::PointCloud::Ptr t_out_payload, + visualization_msgs::MarkerArray &t_marker_array); + + + /* [Drawing] + * A function to draw lines in rviz + */ + void _assignLine(visualization_msgs::Marker &marker, + visualization_msgs::MarkerArray t_mark_array, + const uint32_t shape, const std::string ns, + const double r, const double g, const double b, + const PointXYZRI t_point1, const PointXYZRI t_point2, + const int t_count); + + + /* [Drawing] + * A function to assign markers in rviz + */ + void _assignMarker(visualization_msgs::Marker &t_marker, const uint32_t t_shape, + const std::string t_namespace, + const double r, const double g, const double b, + const PointXYZRI &t_point, const int t_count, + const double t_size, const std::string Text=""); + + + /***************************************************** + * not used + *****************************************************/ + + // Clean up + void _freeUp(std::vector &ClusterBuff); + void _freeCluster(ClusterFamily_t &Cluster); + template + void _freeVec(Container& c); + void _freePCL(pcl::PointCloud &vec); + void _freeTagLineStruc(TagLines_t &TagEdges); + }; // GrizTag +} // namespace +#endif diff --git a/include/tag16h5.h b/include/tag16h5.h new file mode 100644 index 0000000..e4302a8 --- /dev/null +++ b/include/tag16h5.h @@ -0,0 +1,39 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + + +#ifndef _TAG16H5 +#define _TAG16H5 + +#include "lidar_tag.h" + +BipedLab::GrizTagFamily_t *tag16h5_create(); +void tag16h5_destroy(BipedLab::GrizTagFamily_t *tf); +#endif diff --git a/include/tag49h14.h b/include/tag49h14.h new file mode 100644 index 0000000..6c1fa29 --- /dev/null +++ b/include/tag49h14.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + +#ifndef _TAG49H14 +#define _TAG49H14 + +#include "lidar_tag.h" + +BipedLab::GrizTagFamily_t *tag49h14_create(); +void tag49h14_destroy(BipedLab::GrizTagFamily_t *tf); +#endif diff --git a/include/types.h b/include/types.h new file mode 100644 index 0000000..64d1a53 --- /dev/null +++ b/include/types.h @@ -0,0 +1,246 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + +namespace BipedLab { + typedef velodyne_pointcloud::PointXYZIR PointXYZRI; + typedef struct QuickDecodeEntry { + uint64_t rcode; // the queried code + uint16_t id; // the tag id (a small integer) + uint16_t hamming; // how many errors corrected? + uint16_t rotation; // number of rotations [0, 3] + }QuickDecodeEntry_t; + + typedef struct QuickDecode { + int nentries; + QuickDecodeEntry_t *entries; + }QuickDecode_t; + + typedef struct PayloadVoting{ + PointXYZRI *p; + float weight; + int cell; + PointXYZRI centroid; + }PayloadVoting_t; + + + // velodyne_pointcloud::PointXYZIR operator+ (const PointXYZRI& p1, const PointXYZRI p2) { + // PointXYZRI tmp; + // tmp.x = p1.x + p2.x; + // tmp.y = p1.y + p2.y; + // tmp.z = p1.z + p2.z; + // tmp.intensity = p1.intensity + p2.intensity; + // return tmp; + // }; + typedef struct MaxMin { + int min; + int average; + int max; + } MaxMin_t; + + // Structure for LiDAR system + typedef struct LiDARSystem { + std::vector> point_count_table; // point per ring PointCountTable[Scan][ring] + std::vector max_min_table; // max min points in a scan + std::vector ring_average_table; // max, min, average points in a ring, examed through out a few seconds + double points_per_square_meter_at_one_meter; // TODO: only assume place the tag at dense-point area + double beam_per_vertical_radian; + double point_per_horizontal_radian; + } LiDARSystem_t; + + // Struture for LiDAR PointCloud with index + typedef struct LiDARPoints { + PointXYZRI point; + int index; + double depth_gradient; // only take abs value due to uncertain direction + double intensity_gradient; // Also account for direction by knowing tag is white to black + double threshold_intensity; + } LiDARPoints_t; + + typedef struct TagLines{ + int upper_ring; + int lower_ring; + std::vector upper_line; // basically just a specific ring, just point to it should be fine + std::vector lower_line; // same above + std::vector left_line; // same + std::vector right_line; // same above + + + std::vector bottom_left; // basically just a specific ring, just point to it should be fine + std::vector bottom_right; // same above + std::vector top_left; // same + std::vector top_right; // same above + } TagLines_t; + + typedef struct TagBoundaries{ + int status; // 0 is up right, 1 is tilted + std::vector line_one; // basically just a specific ring, just point to it should be fine + std::vector line_two; // same above + std::vector line_three; // same + std::vector line_four; // same above + } TagBoundaries_t; + + typedef struct Homogeneous{ + // EIGEN_MAKE_ALIGNED_OPERATOR_NEW + float roll; + float pitch; + float yaw; + Eigen::Matrix translation; + Eigen::Matrix rotation; + Eigen::Matrix homogeneous; + } Homogeneous_t; + + typedef struct Grid{ + float cx; + float cz; + float cy; + } Grid_t; + + typedef struct ClusterFamily { + // EIGEN_MAKE_ALIGNED_OPERATOR_NEW + int cluster_id; + int valid; + PointXYZRI top_most_point; + PointXYZRI bottom_most_point; + + PointXYZRI front_most_point; + PointXYZRI back_most_point; + + PointXYZRI right_most_point; + PointXYZRI left_most_point; + + PointXYZRI average; // Average point + PointXYZRI max_intensity; // Maximux intensity point + PointXYZRI min_intensity; // Minimum intensity point + pcl::PointCloud data; + + std::vector max_min_index_of_each_ring; // to fill in points between end points in this cluster + std::vector> ordered_points_ptr; // of the cluster (to find black margin of the tag) + std::vector accumulate_intensity_of_each_ring; // to find the upper/lower lines of the tag + TagLines_t tag_edges; // store line segment from points + TagBoundaries_t tag_boundaries; + + std::vector payload_right_boundary_ptr; // of the cluster (to find black margin of the tag) + std::vector payload_left_boundary_ptr; // of the cluster (to find black margin of the tag) + std::vector payload_boundary_ptr; // of the cluster (to find black margin of the tag) + + pcl::PointCloud payload; // payload points with boundary + int payload_without_boundary; // size of payload points without boundary + // Eigen::Vector3f NormalVector; // Normal vectors of the payload + Eigen::Matrix normal_vector; + QuickDecodeEntry_t entry; + Homogeneous_t pose; + tf::Transform transform; + + + + /* VectorXf: + * point_on_line.x : the X coordinate of a point on the line + * point_on_line.y : the Y coordinate of a point on the line + * point_on_line.z : the Z coordinate of a point on the line + * line_direction.x : the X coordinate of a line's direction + * line_direction.y : the Y coordinate of a line's direction + * line_direction.z : the Z coordinate of a line's direction + */ + std::vector line_coeff; // Upper, left, bottom, right line (count-clockwise) + } ClusterFamily_t; + + typedef struct GrizTagFamily { + // How many codes are there in this tag family? + uint32_t ncodes; + + // The codes in the family. + uint64_t *codes; + + // how wide (in bit-sizes) is the black border? (usually 1) + uint32_t black_border; + + // how many bits tall and wide is it? (e.g. 36bit tag ==> 6) + uint32_t d; + + // minimum hamming distance between any two codes. (e.g. 36h11 => 11) + uint32_t h; + + // a human-readable name, e.g., "tag36h11" + char *name; + + // some detector implementations may preprocess codes in order to + // accelerate decoding. They put their data here. (Do not use the + // same apriltag_family instance in more than one implementation) + void *impl; + }GrizTagFamily_t; + + typedef struct ClusterRemoval { + int removed_by_point_check; + int boundary_point_check; + int no_edge_check; + int minimum_return; + int decode_fail; + int decoder_not_return; + int decoder_fail_corner; + }ClusterRemoval_t; + + typedef struct Statistics { + ClusterRemoval_t cluster_removal; + int original_cluster_size; + int remaining_cluster_size; + int point_cloud_size; + int edge_cloud_size; + }Statistics_t; + + typedef struct Timing{ + // in us + clock_t start_total_time; + clock_t start_computation_time; + clock_t timing; + + double total_time; + double computation_time; + double edgingand_clustering_time; + double to_pcl_vector_time; + double point_check_time; + double line_fitting_time; + double payload_extraction_time; + double normal_vector_time; + double payload_decoding_time; + double tag_to_robot_time; + }Timing_t; + + typedef struct TestCluster { + int flag; + ClusterFamily_t new_cluster; + }TestCluster_t; + + typedef struct Debug{ + std::vector point_check; + std::vector boundary_point; + std::vector no_edge; + std::vector extract_payload; + }Debug_t; +} // namespace diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 0000000..389b217 --- /dev/null +++ b/include/utils.h @@ -0,0 +1,171 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + +#pragma once +#ifndef UTILS_H +#define UTILS_H + +#include +#include // to use to_lower function +#include // for variadic functions +#include + + +#include +#include +#include "lidar_tag.h" + + +namespace BipedLab { + + //typedef velodyne_pointcloud::PointXYZIR PointXYZRI; + namespace utils { + double spendTime(const std::clock_t &t_end, const std::clock_t &t_start); + std::string tranferToLowercase(std::string &t_data); + void pressEnterToContinue(); + double deg2Rad(double t_degree); + double rad2Deg(double t_radian); + bool isRotationMatrix(Eigen::Matrix3f &t_R); + Eigen::Vector3f rotationMatrixToEulerAngles(Eigen::Matrix3f &t_R); + + + bool checkParameters(int t_n, ...); + void COUT(const velodyne_pointcloud::PointXYZIR& t_p); + bool compareIndex(LiDARPoints_t *A, LiDARPoints_t *B); + uint64_t bitShift(std::string const& t_value); + + + + void normalizeByAve(std::vector &t_x, std::vector &t_y, + std::vector &t_z, std::vector &t_I, + const pcl::PointCloud payload); + void normalize(std::vector &t_x, std::vector &t_y, + std::vector &t_z, std::vector &t_I, + const pcl::PointCloud t_payload); + + velodyne_pointcloud::PointXYZIR pointsAddDivide ( + const velodyne_pointcloud::PointXYZIR& t_p1, + const velodyne_pointcloud::PointXYZIR& t_p2, float t_d=1); + + velodyne_pointcloud::PointXYZIR vectorize ( + const velodyne_pointcloud::PointXYZIR& t_p1, + const velodyne_pointcloud::PointXYZIR& t_p2); + + float dot (const velodyne_pointcloud::PointXYZIR& t_p1, + const velodyne_pointcloud::PointXYZIR& t_p2); + + float Norm (const velodyne_pointcloud::PointXYZIR& t_p); + + // a function to determine the step of given two points + float getStep(const velodyne_pointcloud::PointXYZIR &t_p1, + const velodyne_pointcloud::PointXYZIR &t_p2, const int t_d); + + void getProjection(const velodyne_pointcloud::PointXYZIR &t_p1, + const velodyne_pointcloud::PointXYZIR &t_p2, + const velodyne_pointcloud::PointXYZIR &t_p, + float &t_k, Eigen::Vector2f &t_v); + + double MVN(const float &t_tag_size, const int &t_d, + const Eigen::Vector2f &t_X, const Eigen::Vector2f t_mean); + + void assignCellIndex(const float &t_tag_size, + const Eigen::Matrix3f &t_R, + velodyne_pointcloud::PointXYZIR &t_p_reference, + const velodyne_pointcloud::PointXYZIR &t_average, + const int t_d, PayloadVoting_t &t_vote); + + void sortPointsToGrid(std::vector> &t_grid, + std::vector &t_votes); + + Eigen::Vector2f pointToLine(const velodyne_pointcloud::PointXYZIR &t_p1, + const velodyne_pointcloud::PointXYZIR &t_p2, + const velodyne_pointcloud::PointXYZIR &t_p); + + void formGrid(Eigen::MatrixXf &t_vertices, + float t_x, float t_y, float t_z, float t_tag_size); + + void fitGrid(Eigen::MatrixXf &t_vertices, + Eigen::Matrix3f &t_R, + const velodyne_pointcloud::PointXYZIR &t_p1, + const velodyne_pointcloud::PointXYZIR &t_p2, + const velodyne_pointcloud::PointXYZIR &t_p3, + const velodyne_pointcloud::PointXYZIR &t_p4); + + float distance( + const velodyne_pointcloud::PointXYZIR &t_p1, + const velodyne_pointcloud::PointXYZIR &t_p2); + template + float getAngle (T a, U b); + + + int checkCorners( + const float t_tag_size, + const velodyne_pointcloud::PointXYZIR &t_p1, + const velodyne_pointcloud::PointXYZIR &t_p2, + const velodyne_pointcloud::PointXYZIR &t_p3, + const velodyne_pointcloud::PointXYZIR &t_p4); + + velodyne_pointcloud::PointXYZIR toVelodyne(const Eigen::Vector3f &t_p); + Eigen::Vector3f toEigen(const velodyne_pointcloud::PointXYZIR &t_point); + void minus(velodyne_pointcloud::PointXYZIR &t_p1, + const velodyne_pointcloud::PointXYZIR &t_p2); + + template + T blockMatrix(int t_n, ...); + + + // template + Eigen::Matrix4d poseToEigenMatrix(const geometry_msgs::Pose &t_pose); + // Eigen::Matrix4d poseToEigenMatrix(const T &pose); + + template + Eigen::Matrix3d qToR(const T &t_pose); + + Eigen::Matrix3d qToR(const Eigen::Vector3f &t_pose); + + // q1q2 = q2q1q2^-1 + Eigen::Matrix3d qMultiplication(const double &t_q1_w, const Eigen::Vector3f &t_q1, + const double &t_q2_w, const Eigen::Vector3f &t_q2); + + + template + bool goodNumber(T t_number){ + if (std::isinf(t_number) || std::isnan(t_number)) + return false; + else return true; + } + + + + // std::ostream& operator<<(std::ostream& os, const velodyne_pointcloud::PointXYZIR& p); + + } +} // Bipedlab +#endif diff --git a/launch/LiDARTag.launch b/launch/LiDARTag.launch new file mode 100644 index 0000000..c2d58b5 --- /dev/null +++ b/launch/LiDARTag.launch @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/package.xml b/package.xml new file mode 100644 index 0000000..b58fce2 --- /dev/null +++ b/package.xml @@ -0,0 +1,74 @@ + + + lidar_tag + 0.0.0 + A package for lidar_tag + brucebot + + + + + + BSD + + + + + + + + + + + + + + + + + + + + + + + + + + catkin + roscpp + rospy + tf + std_msgs + visualization_msgs + sensor_msgs + message_filters + apriltag_msgs + lidartag_msgs + + + + roscpp + rospy + tf + std_msgs + visualization_msgs + sensor_msgs + message_filters + apriltag_msgs + lidartag_msgs + + + roslib + rosunit + + + + + + + + + + + diff --git a/src/apriltag_utils.cc b/src/apriltag_utils.cc new file mode 100644 index 0000000..3f91315 --- /dev/null +++ b/src/apriltag_utils.cc @@ -0,0 +1,305 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + +/* Copyright (C) 2013-2016, The Regents of The University of Michigan. + All rights reserved. + + This software was developed in the APRIL Robotics Lab under the + direction of Edwin Olson, ebolson@umich.edu. This software may be + available under alternative licensing terms; contact the address above. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + The views and conclusions contained in the software and documentation are + those + of the authors and should not be interpreted as representing official + policies, + either expressed or implied, of the Regents of The University of + Michigan. + */ + +#include "apriltag_utils.h" + +// Adapted/modified from apriltags code +namespace BipedAprilLab{ + + static inline int imax(int a, int b) { + return (a > b) ? a : b; + } + + /** if the bits in w were arranged in a d*d grid and that grid was + * rotated, what would the new bits in w be? + * The bits are organized like this (for d = 3): + * + * 8 7 6 2 5 8 0 1 2 + * 5 4 3 ==> 1 4 7 ==> 3 4 5 (rotate90 applied twice) + * 2 1 0 0 3 6 6 7 8 + **/ + + uint64_t rotate90(uint64_t w, uint32_t d) { + uint64_t wr = 0; + + for (int32_t r = d-1; r >=0; r--) { + for (int32_t c = 0; c < d; c++) { + int32_t b = r + d*c; + + wr = wr << 1; + + if ((w & (((uint64_t) 1) << b))!=0) + wr |= 1; + } + } + + return wr; + } + + void QuickDecodeAdd(BipedLab::QuickDecode_t *qd, uint64_t code, int id, int hamming) { + uint32_t bucket = code % qd->nentries; + + while (qd->entries[bucket].rcode != UINT64_MAX) { + bucket = (bucket + 1) % qd->nentries; + } + + qd->entries[bucket].rcode = code; + qd->entries[bucket].id = id; + qd->entries[bucket].hamming = hamming; + } + + void QuickDecodeInit(BipedLab::GrizTagFamily_t *family, int maxhamming){ + assert(family->impl == NULL); + assert(family->ncodes < 65535); + + printf("Requesting memory\n"); + BipedLab::QuickDecode_t *qd = new BipedLab::QuickDecode_t; + + int capacity = family->ncodes; + + int nbits = family->d * family->d; + + if (maxhamming >= 1) + capacity += family->ncodes * nbits; + + if (maxhamming >= 2) + capacity += family->ncodes * nbits * (nbits-1); + + if (maxhamming >= 3) + capacity += family->ncodes * nbits * (nbits-1) * (nbits-2); + + if (maxhamming >= 4) + capacity += family->ncodes * nbits * (nbits-1) * (nbits-2) * (nbits-3); + + if (maxhamming >= 5) + capacity += family->ncodes * nbits * (nbits-1) * (nbits-2) * (nbits-3) * (nbits-4); + + if (maxhamming >= 6) + capacity += family->ncodes * nbits * (nbits-1) * (nbits-2) * (nbits-3) * (nbits-4) * (nbits-5); + + if (maxhamming >= 7) + capacity += family->ncodes * nbits * (nbits-1) * (nbits-2) * (nbits-3) * (nbits-4) * (nbits-5) * (nbits-6); + + qd->nentries = capacity * 3; + + qd->entries = new BipedLab::QuickDecodeEntry_t[qd->nentries]; + if (qd->entries == NULL) { + printf("apriltag_utils.c: failed to allocate hamming decode table. Reduce max hamming size.\n"); + exit(-1); + } + + printf("Adding codes..\n"); + for (int i = 0; i < qd->nentries; i++) + qd->entries[i].rcode = UINT64_MAX; + + for (int i = 0; i < family->ncodes; i++) { + uint64_t code = family->codes[i]; + + // add exact code (hamming = 0) + QuickDecodeAdd(qd, code, i, 0); + + if (maxhamming >= 1) { + // add hamming 1 + for (int j = 0; j < nbits; j++) + QuickDecodeAdd(qd, code ^ (1L << j), i, 1); + } + + if (maxhamming >= 2) { + // add hamming 2 + for (int j = 0; j < nbits; j++) + for (int k = 0; k < j; k++) + QuickDecodeAdd(qd, code ^ (1L << j) ^ (1L << k), i, 2); + } + + if (maxhamming >= 3) { + // add hamming 3 + for (int j = 0; j < nbits; j++) + for (int k = 0; k < j; k++) + for (int m = 0; m < k; m++) + QuickDecodeAdd(qd, code ^ (1L << j) ^ (1L << k) ^ (1L << m), i, 3); + } + + if (maxhamming >= 4) { + // add hamming 3 + for (int j = 0; j < nbits; j++) + for (int k = 0; k < j; k++) + for (int m = 0; m < k; m++) + for (int n = 0; n < m; n++) + QuickDecodeAdd(qd, + code ^ (1L << j) ^ (1L << k) ^ (1L << m) ^ (1L << n), + i, 4); + } + + if (maxhamming >= 5) { + // add hamming 3 + for (int j = 0; j < nbits; j++) + for (int k = 0; k < j; k++) + for (int m = 0; m < k; m++) + for (int n = 0; n < m; n++) + for (int o = 0; o < n; o++) + QuickDecodeAdd(qd, + code ^ (1L << j) ^ (1L << k) ^ (1L << m) ^ (1L << n) ^ (1L << o), + i, 5); + } + + if (maxhamming >= 6) { + // add hamming 3 + for (int j = 0; j < nbits; j++) + for (int k = 0; k < j; k++) + for (int m = 0; m < k; m++) + for (int n = 0; n < m; n++) + for (int o = 0; o < n; o++) + for (int p = 0; p < o; p++) + QuickDecodeAdd(qd, + code ^ (1L << j) ^ (1L << k) ^ (1L << m) ^ (1L << n) ^ (1L << o) ^ (1L << p), + i, 6); + } + + if (maxhamming >= 7) { + // add hamming 3 + for (int j = 0; j < nbits; j++) + for (int k = 0; k < j; k++) + for (int m = 0; m < k; m++) + for (int n = 0; n < m; n++) + for (int o = 0; o < n; o++) + for (int p = 0; p < o; p++) + for (int q = 0; q < p; q++) + QuickDecodeAdd(qd, + code ^ (1L << j) ^ (1L << k) ^ (1L << m) ^ (1L << n) ^ (1L << o) ^ (1L << p) ^ (1L << q), + i, 7); + } + + + if (maxhamming > 7) { + printf("apriltag_utils.c: maxhamming beyond 7 not supported\n"); + } + } + + // printf("done adding..\n"); + family->impl = qd; + + if (1) { + int longest_run = 0; + int run = 0; + int run_sum = 0; + int run_count = 0; + + // This accounting code doesn't check the last possible run that + // occurs at the wrap-around. That's pretty insignificant. + for (int i = 0; i < qd->nentries; i++) { + if (qd->entries[i].rcode == UINT64_MAX) { + if (run > 0) { + run_sum += run; + run_count ++; + } + run = 0; + } else { + run ++; + longest_run = imax(longest_run, run); + } + } + + printf("quick decode: longest run: %d, average run %.3f\n", longest_run, 1.0 * run_sum / run_count); + } + } + + + void QuickDecodeCodeword(BipedLab::GrizTagFamily_t *tf, uint64_t rcode, + BipedLab::QuickDecodeEntry_t *entry) { + BipedLab::QuickDecode_t *qd = (BipedLab::QuickDecode_t*) tf->impl; + + for (int ridx = 0; ridx < 4; ridx++) { + + for (int bucket = rcode % qd->nentries; + qd->entries[bucket].rcode != UINT64_MAX; + bucket = (bucket + 1) % qd->nentries) { + // std::cout << "bucket: " << bucket << std::endl; + + if (qd->entries[bucket].rcode == rcode) { + *entry = qd->entries[bucket]; + entry->rotation = ridx; + //printf("rotation: %i\n", entry->rotation); + return; + } + } + + rcode = rotate90(rcode, tf->d); + } + + entry->rcode = 0; + entry->id = 9999; + entry->hamming = 255; + entry->rotation = 0; + } +} + diff --git a/src/ctags_file b/src/ctags_file new file mode 100644 index 0000000..464b2f6 --- /dev/null +++ b/src/ctags_file @@ -0,0 +1,125 @@ +!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ +!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ +!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ +!_TAG_PROGRAM_NAME Exuberant Ctags // +!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ +!_TAG_PROGRAM_VERSION 5.9~svn20110310 // +AdaptiveThresholding_ griz_tag.cc /^ bool GrizTag::AdaptiveThresholding_(ClusterFamily_t &Cluster){$/;" f class:BipedLab::GrizTag +AnalyzeSystem_ griz_tag.cc /^ void GrizTag::AnalyzeSystem_(){$/;" f class:BipedLab::GrizTag +AreaPoints griz_tag.cc /^ int GrizTag::AreaPoints(const double Distance, const double ObjWidth, const double ObjHeight){$/;" f class:BipedLab::GrizTag +AssignLine griz_tag.cc /^ void GrizTag::AssignLine(visualization_msgs::Marker &Marker, visualization_msgs::MarkerArray MarkArray,$/;" f class:BipedLab::GrizTag +AssignMarker griz_tag.cc /^ void GrizTag::AssignMarker(visualization_msgs::Marker &Marker, const uint32_t Shape, const string NameSpace,$/;" f class:BipedLab::GrizTag +BipedAprilLab apriltag_utils.cc /^namespace BipedAprilLab{$/;" n file: +BipedLab griz_tag.cc /^namespace BipedLab {$/;" n file: +BipedLab utils.cc /^namespace BipedLab{$/;" n file: +BitShift utils.cc /^ uint64_t BitShift(std::string const& value) {$/;" f namespace:BipedLab::utils +BuffToPclVector griz_tag.cc /^ void GrizTag::BuffToPclVector(const std::vector> &EdgeBuff,$/;" f class:BipedLab::GrizTag +COUT utils.cc /^ void COUT(const velodyne_pointcloud::PointXYZIR& p) {$/;" f namespace:BipedLab::utils +ClusterClassifier_ griz_tag.cc /^ void GrizTag::ClusterClassifier_(const LiDARPoints_t &Point, vector &ClusterBuff){$/;" f class:BipedLab::GrizTag +ClusterPointsCheck_ griz_tag.cc /^ bool GrizTag::ClusterPointsCheck_(ClusterFamily_t &Cluster){ $/;" f class:BipedLab::GrizTag +ClusterToPclVectorAndMarkerPublisher_ griz_tag.cc /^ void GrizTag::ClusterToPclVectorAndMarkerPublisher_(const std::vector &Cluster,$/;" f class:BipedLab::GrizTag +Codewords_ lib.cc /^ vector Codewords_;$/;" m class:LoadWeight::impl file: +CompareIndex utils.cc /^ bool CompareIndex(LiDARPoints_t *A, LiDARPoints_t *B){$/;" f namespace:BipedLab::utils +ComputeRANSAC_ griz_tag.cc /^ Eigen::VectorXf GrizTag::ComputeRANSAC_(std::vector &Line){ $/;" f class:BipedLab::GrizTag +Decoder griz_tag.cc /^ void GrizTag::Decoder(){$/;" f class:BipedLab::GrizTag +DecoderTest griz_tag.cc /^ void GrizTag::DecoderTest(){$/;" f class:BipedLab::GrizTag +DegToRad utils.cc /^ double DegToRad(double Degree){$/;" f namespace:BipedLab::utils +Dot utils.cc /^ float Dot$/;" f namespace:BipedLab::utils +EdgeDetection_ griz_tag.cc /^ GrizTag::EdgeDetection_(const std::vector>& OrderedBuff, $/;" f class:BipedLab::GrizTag +EigenVectorToPointXYZRI_ griz_tag.cc /^ void GrizTag::EigenVectorToPointXYZRI_(const Eigen::Vector4f &Vector, PointXYZRI &Point){$/;" f class:BipedLab::GrizTag +ExtractPayload griz_tag.cc /^ void GrizTag::ExtractPayload(ClusterFamily_t &Cluster){$/;" f class:BipedLab::GrizTag +ExtractPayloadWOThreshold_ griz_tag.cc /^ void GrizTag::ExtractPayloadWOThreshold_(ClusterFamily_t &Cluster){$/;" f class:BipedLab::GrizTag +FillInCluster_ griz_tag.cc /^ void GrizTag::FillInCluster_(const std::vector>& OrderedBuff, $/;" f class:BipedLab::GrizTag +FillInOrderedPc_ griz_tag.cc /^ void GrizTag::FillInOrderedPc_(const pcl::PointCloud::Ptr PclPointcloud, $/;" f class:BipedLab::GrizTag +FitGrid utils.cc /^ void FitGrid(Eigen::MatrixXf &GridVertices,$/;" f namespace:BipedLab::utils +FormGrid utils.cc /^ void FormGrid(Eigen::MatrixXf &vertices, $/;" f namespace:BipedLab::utils +Forward lib.cc /^void LoadWeight::Forward(const float *x, const float *y, const float *z, $/;" f class:LoadWeight +ForwardInternally lib.cc /^ void ForwardInternally(const float *x, const float *y, const float *z, const float *I, $/;" f class:LoadWeight::impl +FreeCluster griz_tag.cc /^ void GrizTag::FreeCluster(ClusterFamily_t &Cluster){$/;" f class:BipedLab::GrizTag +FreePCL griz_tag.cc /^ void GrizTag::FreePCL(pcl::PointCloud &vec){$/;" f class:BipedLab::GrizTag +FreeTagLineStruc griz_tag.cc /^ void GrizTag::FreeTagLineStruc(TagLines_t &TagEdges){$/;" f class:BipedLab::GrizTag +FreeUp griz_tag.cc /^ void GrizTag::FreeUp(vector &ClusterBuff){$/;" f class:BipedLab::GrizTag +FreeVec griz_tag.cc /^ void GrizTag::FreeVec(Container& c) { $/;" f class:BipedLab::GrizTag +GetCode griz_tag.cc /^ bool GrizTag::GetCode(string &Code, const pcl::PointCloud Payload){$/;" f class:BipedLab::GrizTag +GetCodeNaive griz_tag.cc /^ void GrizTag::GetCodeNaive(string &Code, pcl::PointCloud Payload){$/;" f class:BipedLab::GrizTag +GetInnerOutputs lib.cc /^ float* GetInnerOutputs(){$/;" f class:LoadWeight::impl +GetOrderBuff griz_tag.cc /^ std::vector> GrizTag::GetOrderBuff(){$/;" f class:BipedLab::GrizTag +GetOutput lib.cc /^float* LoadWeight::GetOutput(){$/;" f class:LoadWeight +GetParameters_ griz_tag.cc /^ void GrizTag::GetParameters_(){$/;" f class:BipedLab::GrizTag +GetProjection utils.cc /^ void GetProjection(const velodyne_pointcloud::PointXYZIR &p1, $/;" f namespace:BipedLab::utils +GetStep utils.cc /^ float GetStep(const velodyne_pointcloud::PointXYZIR &p1, const velodyne_pointcloud::PointXYZIR &p2, const int d){$/;" f namespace:BipedLab::utils +GetWeightAssignGrid utils.cc /^ void GetWeightAssignGrid(const int d, PayloadVoting_t &Vote, float &SumWeight, $/;" f namespace:BipedLab::utils +GotAllParameters utils.cc /^ bool GotAllParameters(int n, ...){$/;" f namespace:BipedLab::utils +GradientAndGroupEdges_ griz_tag.cc /^ void GrizTag::GradientAndGroupEdges_(const std::vector>& OrderedBuff, $/;" f class:BipedLab::GrizTag +GrizTag griz_tag.cc /^ GrizTag::GrizTag():$/;" f class:BipedLab::GrizTag +GroupEdges_ griz_tag.cc /^ void GrizTag::GroupEdges_(std::vector> &EdgeBuff){$/;" f class:BipedLab::GrizTag +LineFittingWOThreshold_ griz_tag.cc /^ bool GrizTag::LineFittingWOThreshold_(ClusterFamily_t &Cluster){$/;" f class:BipedLab::GrizTag +LineFitting_ griz_tag.cc /^ bool GrizTag::LineFitting_(ClusterFamily_t &Cluster){$/;" f class:BipedLab::GrizTag +LoadInternally lib.cc /^ void LoadInternally(const char *ModelPath, const char *CkptPath, const int TagFamily, float *result = nullptr) {$/;" f class:LoadWeight::impl +LoadWeight lib.cc /^LoadWeight::LoadWeight(const char *model_path, const char *ckpt_path, const int TagFamily)$/;" f class:LoadWeight +LoadWeight_ lib.cc /^int LoadWeight_(const char *ModelPath, const char *CheckpointPath, float *result) {$/;" f +MAX_INTENSITY griz_tag.cc 35;" d file: +MIN_INTENSITY griz_tag.cc 36;" d file: +MVN utils.cc /^ double MVN(const Eigen::Vector2f &X, const Eigen::Vector2f Mean){$/;" f namespace:BipedLab::utils +MaxMinPtsInAScan_ griz_tag.cc /^ void GrizTag::MaxMinPtsInAScan_(std::vector &PointCountTable, $/;" f class:BipedLab::GrizTag +MergeCluster_ griz_tag.cc /^ void GrizTag::MergeCluster_(std::vector &ClusterBuff) {$/;" f class:BipedLab::GrizTag +Norm utils.cc /^ float Norm $/;" f namespace:BipedLab::utils +NormalEstimation griz_tag.cc /^ GrizTag::NormalEstimation(ClusterFamily_t &Cluster){$/;" f class:BipedLab::GrizTag +PayloadDecoder griz_tag.cc /^ void GrizTag::PayloadDecoder(ClusterFamily_t &Cluster){$/;" f class:BipedLab::GrizTag +PointCloudCallBack_ griz_tag.cc /^ void GrizTag::PointCloudCallBack_(const sensor_msgs::PointCloud2ConstPtr &pc){$/;" f class:BipedLab::GrizTag +PointXYZRI test.cc /^typedef velodyne_pointcloud::PointXYZIR PointXYZRI;$/;" t file: +PointsPerSquareMeterAtOneMeter griz_tag.cc /^ void GrizTag::PointsPerSquareMeterAtOneMeter(){$/;" f class:BipedLab::GrizTag +PressEnterToContinue utils.cc /^ void PressEnterToContinue() {$/;" f namespace:BipedLab::utils +PublishPC_ griz_tag.cc /^ void GrizTag::PublishPC_(const pcl::PointCloud::Ptr SourcePC, $/;" f class:BipedLab::GrizTag +QuickDecodeAdd apriltag_utils.cc /^ void QuickDecodeAdd(BipedLab::QuickDecode_t *qd, uint64_t code, int id, int hamming) {$/;" f namespace:BipedAprilLab +QuickDecodeCodeword apriltag_utils.cc /^ void QuickDecodeCodeword(BipedLab::GrizTagFamily_t *tf, uint64_t rcode,$/;" f namespace:BipedAprilLab +QuickDecodeInit apriltag_utils.cc /^ void QuickDecodeInit(BipedLab::GrizTagFamily_t *family, int maxhamming){$/;" f namespace:BipedAprilLab +ROSToDecoder griz_tag.cc /^ void GrizTag::ROSToDecoder(std::vector &ClusterBuff){$/;" f class:BipedLab::GrizTag +RadToDeg utils.cc /^ double RadToDeg(double Radian){$/;" f namespace:BipedLab::utils +RefineCluster_ griz_tag.cc /^ void GrizTag::RefineCluster_(std::vector &ClusterBuff) { $/;" f class:BipedLab::GrizTag +RosSpin_ griz_tag.cc /^ void GrizTag::RosSpin_(){$/;" f class:BipedLab::GrizTag +SQRT2 griz_tag.cc 34;" d file: +SortNormalizeWeight utils.cc /^ void SortNormalizeWeight(std::vector> &Grid, $/;" f namespace:BipedLab::utils +Spin_ griz_tag.cc /^ void GrizTag::Spin_(){$/;" f class:BipedLab::GrizTag +TagFamily_ lib.cc /^ int TagFamily_;$/;" m class:LoadWeight::impl file: +TagToRobot griz_tag.cc /^ void GrizTag::TagToRobot(const int ClusterID, const Eigen::Vector3f NormalVec, Homogeneous_t &Pose, $/;" f class:BipedLab::GrizTag +ToVelodyne utils.cc /^ velodyne_pointcloud::PointXYZIR ToVelodyne(const Eigen::Vector3f &p){$/;" f namespace:BipedLab::utils +TranferToLowercase utils.cc /^ std::string TranferToLowercase(std::string &data){$/;" f namespace:BipedLab::utils +UpdateCluster_ griz_tag.cc /^ void GrizTag::UpdateCluster_(const LiDARPoints_t &Point, ClusterFamily_t &OldCluster, TestCluster_t *NewCluster){$/;" f class:BipedLab::GrizTag +Vectorize utils.cc /^ velodyne_pointcloud::PointXYZIR Vectorize$/;" f namespace:BipedLab::utils +WaitForPC_ griz_tag.cc /^ void GrizTag::WaitForPC_(){$/;" f class:BipedLab::GrizTag +WeightedDecode griz_tag.cc /^ bool GrizTag::WeightedDecode(string &Code, const PointXYZRI &Average, $/;" f class:BipedLab::GrizTag +_Known_bound lib.cc /^ typedef void _Known_bound;$/;" t struct:std::_Unique_if file: +_Single_object lib.cc /^ typedef unique_ptr _Single_object;$/;" t struct:std::_Unique_if file: +_Unique_if lib.cc /^ template struct _Unique_if {$/;" s namespace:std file: +_Unique_if lib.cc /^ template struct _Unique_if {$/;" s namespace:std file: +_Unique_if lib.cc /^ template struct _Unique_if {$/;" s namespace:std file: +_Unknown_bound lib.cc /^ typedef unique_ptr _Unknown_bound;$/;" t struct:std::_Unique_if file: +imax apriltag_utils.cc /^ static inline int imax(int a, int b) {$/;" f namespace:BipedAprilLab +impl lib.cc /^class LoadWeight::impl{$/;" c class:LoadWeight file: +main example.cc /^int main()$/;" f +main load_weight.cc /^int main() {$/;" f +main main.cc /^int main(int argc, char **argv){$/;" f +main normal.cc /^int main()$/;" f +main random_sample_consensus.cpp /^main(int argc, char** argv)$/;" f +main test.cc /^int main(int argc, char* argv[]) {$/;" f +main test_.cc /^int main( int argc, char** argv )$/;" f +main worker_pool.cc /^int main (int argc, char *argv[]) {$/;" f +make_unique lib.cc /^ make_unique(Args&&... args) {$/;" f namespace:std +make_unique lib.cc /^ make_unique(size_t n) {$/;" f namespace:std +normalize test.cc /^void normalize(std::vector &x, std::vector &y, $/;" f +normalize utils.cc /^ void normalize(std::vector &x, std::vector &y, $/;" f namespace:BipedLab::utils +normalize_by_ave utils.cc /^ void normalize_by_ave(std::vector &x, std::vector &y, $/;" f namespace:BipedLab::utils +options_ lib.cc /^ tensorflow::SessionOptions options_;$/;" m class:LoadWeight::impl file: +outputs lib.cc /^ std::vector outputs;$/;" m class:LoadWeight::impl file: +points_add_divide utils.cc /^ velodyne_pointcloud::PointXYZIR points_add_divide$/;" f namespace:BipedLab::utils +rotate90 apriltag_utils.cc /^ uint64_t rotate90(uint64_t w, uint32_t d) {$/;" f namespace:BipedAprilLab +session_ lib.cc /^ Session* session_;$/;" m class:LoadWeight::impl file: +simpleVis random_sample_consensus.cpp /^simpleVis (pcl::PointCloud::ConstPtr cloud)$/;" f +std lib.cc /^namespace std {$/;" n file: +tag16h5_create tag16h5.cc /^BipedLab::GrizTagFamily_t *tag16h5_create()$/;" f +tag16h5_destroy tag16h5.cc /^void tag16h5_destroy(BipedLab::GrizTagFamily_t *tf)$/;" f +tag49h14_create tag49h14.cc /^BipedLab::GrizTagFamily_t *tag49h14_create()$/;" f +tag49h14_destroy tag49h14.cc /^void tag49h14_destroy(BipedLab::GrizTagFamily_t *tf)$/;" f +tensor_dict lib.cc /^typedef std::vector> tensor_dict;$/;" t file: +utils utils.cc /^ namespace utils {$/;" n namespace:BipedLab file: diff --git a/src/lidar_tag.cc b/src/lidar_tag.cc new file mode 100644 index 0000000..b410c88 --- /dev/null +++ b/src/lidar_tag.cc @@ -0,0 +1,2790 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include // SVD +#include // matrix exponential +#include // tensor output + +#include // package +#include "lidar_tag.h" +#include "apriltag_utils.h" +#include "utils.h" +#include "tag49h14.h" +#include "tag16h5.h" + +#include +#include // std::sort +#include /* sqrt, pow(a,b) */ +#include /* clock_t, clock, CLOCKS_PER_SEC */ +#include /* srand, rand */ +#include // log files + + +/* CONSTANT */ +#define SQRT2 1.41421356237 +#define MAX_INTENSITY 255 +#define MIN_INTENSITY 0 + +using namespace std; + +namespace BipedLab { + LiDARTag::LiDARTag(): + _nh("~"), + _point_cloud_received(0), + _pub_frame("velodyne"), // what frame of the published pointcloud should be + _stop(0) // Just a switch for exiting this program while using valgrind + { + LiDARTag::_getParameters(); + cout << "\033[1;32m\n\n===== loading tag family ===== \033[0m\n"; + LiDARTag::_initDecoder(); + + if (_decode_method!=0 && _decode_method!=1){ + ROS_ERROR("Please use 0 or 1 for decode_method in the launch file"); + ROS_INFO_STREAM("currently using: "<< _decode_method); + } + cout << "\033[1;32m=========================== \033[0m\n"; + cout << "\033[1;32m=========================== \033[0m\n"; + + ROS_INFO("ALL INITIALIZED!"); + _LiDAR1_sub = _nh.subscribe(_pointcloud_topic, 50, + &LiDARTag::_pointCloudCallback, this); + _edge_pub = _nh.advertise("EdgedPC", 10); + _cluster_pub = _nh.advertise("DetectedPC", 10); + _payload_pub = _nh.advertise("Payload", 10); + _boundary_marker_pub = _nh.advertise( "BoundaryMarker", 10); + _cluster_marker_pub = _nh.advertise("ClusterMarker", 10); + _payload_marker_pub = _nh.advertise("PayloadEdges", 10); + _payload_grid_pub = _nh.advertise("Grid", 10); + _payload_grid_line_pub = _nh.advertise("GridLine", 10); + _lidartag_pose_pub = _nh.advertise("LiDARTagPose", 1); + + boost::thread RosSpin(&LiDARTag::_rosSpin, this); // put ros spin into a background thread + + ROS_INFO("Waiting for pointcloud data"); + LiDARTag::_waitForPC(); + + // Exam the minimum distance of each point in a ring + ROS_INFO("Analyzing system"); + LiDARTag::_analyzeLiDARDevice(); + boost::thread ExtractionSpin(&LiDARTag::_mainLoop, this); + ExtractionSpin.join(); + RosSpin.join(); + } + + + /* + * A function to get all parameters from a roslaunch + * if not get all parameters then it will use hard-coded parameters + */ + void LiDARTag::_getParameters(){ + bool GotSleepToDisplay = ros::param::get("sleep_to_display", _sleep_to_display); + bool GotSleepTimeForVis = ros::param::get("sleep_time_for_visulization", _sleep_time_for_vis); + bool GotValgrindCheck = ros::param::get("valgrind_check", _valgrind_check); + bool GotFakeTag = ros::param::get("fake_data", _fake_tag); + + bool GotCSV = ros::param::get("write_csv", _write_CSV); + bool GotDecodeId = ros::param::get("decode_id", _id_decoding); + bool GotAssignId = ros::param::get("assign_id", _assign_id); + + bool GotAdaptiveThresholding = ros::param::get("adaptive_thresholding", _adaptive_thresholding); + bool GotCollectData = ros::param::get("collect_data", _collect_dataset); + + bool GotLidarTopic = ros::param::get("pointcloud_topic", _pointcloud_topic); + bool GotBeamNum = ros::param::get("beam_number", _beam_num); + bool GotSize = ros::param::get("tag_size", _payload_size); + + bool GotTagFamily = ros::param::get("tag_family", _tag_family); + bool GotTagHamming = ros::param::get("tag_hamming_distance", _tag_hamming_distance); + bool GotMaxDecodeHamming = ros::param::get("max_decode_hamming", _max_decode_hamming); + bool GotBlackBorder = ros::param::get("black_border", _black_border); + + bool GotDistanceBound = ros::param::get("distance_bound", _distance_bound); + bool GotIntensityBound = ros::param::get("intensity_bound", _intensity_threshold); + bool GotDepthBound = ros::param::get("depth_bound", _depth_threshold); + bool GotFineClusterThreshold = ros::param::get("fine_cluster_threshold", _fine_cluster_threshold); + bool GotVerticalFOV = ros::param::get("vertical_fov", _vertical_fov); + bool GotFillInGapThreshold = ros::param::get("fill_in_gap_threshold", _filling_gap_max_index); + bool GotFillInMaxPointsThreshold = ros::param::get("fill_in_max_points_threshold", _filling_max_points_threshold); + bool GotPointsThresholdFactor = ros::param::get("points_threshold_factor", _points_threshold_factor); + bool GotLineIntensityBound = ros::param::get("line_intensity_bound", _line_intensity_bound); + bool GotPayloadIntensityThreshold = ros::param::get("payload_intensity_threshold", _payload_intensity_threshold); + + bool GotLatestModel = ros::param::get("latest_model", _latest_model); + bool GotWeightPath = ros::param::get("weight_path", _weight_path); + + bool GotMaxPointsOnPayload = ros::param::get("max_points_on_payload", _max_point_on_payload); + bool GotXYZRI = ros::param::get("xyzri", _XYZRI); + bool GotMinPerGrid = ros::param::get("min_retrun_per_grid", _min_returns_per_grid); + bool GotDecodeMethod = ros::param::get("decode_method", _decode_method); + bool GotGridViz = ros::param::get("grid_viz", _grid_viz); + + bool Pass = utils::checkParameters(33, GotFakeTag, GotLidarTopic, GotBeamNum, + GotDecodeId, GotAssignId, GotCSV, + GotDistanceBound, GotIntensityBound, GotDepthBound, + GotSize, GotTagFamily, GotTagHamming, GotMaxDecodeHamming, + GotFineClusterThreshold, GotVerticalFOV, + GotFillInGapThreshold, GotFillInMaxPointsThreshold, + GotPointsThresholdFactor, GotLineIntensityBound, + GotAdaptiveThresholding, GotCollectData, GotSleepToDisplay, + GotSleepTimeForVis, GotValgrindCheck, + GotLatestModel, GotWeightPath, + GotMaxPointsOnPayload, GotXYZRI, GotMinPerGrid, GotDecodeMethod, + GotGridViz); + + if (!Pass){ + // TODO: check compleness + cout << "\033[1;32m=========================== \033[0m\n"; + cout << "use hard-coded parameters\n"; + cout << "\033[1;32m=========================== \033[0m\n"; + + // Default value + _pointcloud_topic = "/velodyne_points"; + _beam_num = 32; + _distance_bound = 7; // for edge gradient + _intensity_threshold = 2; // for edge gradient + _depth_threshold = 0.5; // for edge gradient + _payload_intensity_threshold = 30; + _payload_size = 0.15; + + _tag_family = 16; + _tag_hamming_distance = 5; + _max_decode_hamming = 2; + + _fine_cluster_threshold = 20; // if the points in a cluster is small than this, it'd get dropped + _vertical_fov = 40; + + // When fill in the cluster, if it the index is too far away then drop it + // TODO:Need a beteer way of doing it! + _filling_gap_max_index = 200; + _filling_max_points_threshold = 4500; + + _line_intensity_bound = 1000; // To determine the payload edge + + // if the points on a "valid" tag is less than this factor, then remove + // it (the higher, the looser) + _points_threshold_factor = 1.3; + _adaptive_thresholding = 0; + _collect_dataset = 1; + + _sleep_to_display = 1; + _sleep_time_for_vis = 0.05; + _valgrind_check = 0; + _black_border = 2; + _fake_tag = 0; + + _latest_model = "-337931"; + _weight_path = "/weight/"; + _max_point_on_payload = 150; + _XYZRI = 4 ; + _min_returns_per_grid = 3; + _decode_method = 2; + _grid_viz = 1; + } + else{ + cout << "\033[1;32m=========================== \033[0m\n"; + cout << "use parameters from the launch file\n"; + cout << "\033[1;32m=========================== \033[0m\n"; + } + + _threshold = _payload_size/4; // point association threshold (which cluster the point belongs to?) + _RANSAC_threshold = _payload_size/10; + + ROS_INFO("Subscribe to %s\n", _pointcloud_topic.c_str()); + ROS_INFO("Use %i-beam LiDAR\n", _beam_num); + ROS_INFO("_intensity_threshold: %f \n", _intensity_threshold); + ROS_INFO("_depth_threshold: %f \n", _depth_threshold); + ROS_INFO("_payload_size: %f \n", _payload_size); + ROS_INFO("_vertical_fov: %i \n", _vertical_fov); + ROS_INFO("_fine_cluster_threshold: %i \n", _fine_cluster_threshold); + ROS_INFO("_filling_gap_max_index: %i \n", _filling_gap_max_index); + ROS_INFO("_filling_max_points_threshold: %i \n", _filling_max_points_threshold); + ROS_INFO("_points_threshold_factor: %f \n", _points_threshold_factor); + ROS_INFO("_adaptive_thresholding: %i \n", _adaptive_thresholding); + ROS_INFO("_collect_dataset: %i \n", _collect_dataset); + ROS_INFO("_decode_method: %i \n", _decode_method); + ROS_INFO("Threshold_: %f \n", _threshold); + ROS_INFO("_RANSAC_threshold: %f \n", _RANSAC_threshold); + + usleep(2e6); + } + + + /* + * Main loop + */ + void LiDARTag::_mainLoop(){ + ROS_INFO("Start edge extraction"); + //ros::Rate r(10); // 10 hz + ros::Duration duration(_sleep_time_for_vis); + int Count = 0; // just to caculate average speed + clock_t StartAve = clock(); + pcl::PointCloud::Ptr ClusterPC(new pcl::PointCloud); + pcl::PointCloud::Ptr PayloadPC(new pcl::PointCloud); + ClusterPC->reserve(_point_cloud_size); + + // XXX Tunalbe + PayloadPC->reserve(_point_cloud_size); + int valgrind_check = 0; + while (ros::ok()) { + _lidartag_pose_array.detections.clear(); + _timing = {clock(), clock(), clock(), 0,0,0,0,0,0,0,0,0}; + + // Try to take a pointcloud from the buffer + std::vector> OrderedBuff = LiDARTag::_getOrderBuff(); + if (OrderedBuff.empty()){ + continue; + } + + // A vector of clusters + vector ClusterBuff; + + pcl::PointCloud::Ptr ExtractedEdgesPC = LiDARTag::_lidarTagDetection(OrderedBuff, + ClusterBuff); + ClusterPC->clear(); + PayloadPC->clear(); + + // Prepare results for rviz + visualization_msgs::MarkerArray ClusterMarkers; + LiDARTag::_clusterToPclVectorAndMarkerPublisher(ClusterBuff, ClusterPC, PayloadPC, ClusterMarkers); + + // publish lidartag poses + _lidartag_pose_pub.publish(_lidartag_pose_array); + + // publish results for rviz + LiDARTag::_publishPC(ExtractedEdgesPC, _pub_frame, string("edge")); + LiDARTag::_publishPC(ClusterPC, _pub_frame, string("Cluster")); + if (_collect_dataset){ + if (_result_statistics.remaining_cluster_size==1) + LiDARTag::_publishPC(PayloadPC, _pub_frame, string("Payload")); + else if (_result_statistics.remaining_cluster_size>1) + cout << "More than one!! " << endl; + else + cout << "Zero!! " << endl; + } + else + LiDARTag::_publishPC(PayloadPC, _pub_frame, string("Payload")); + + //exit(0); + if (_sleep_to_display) duration.sleep(); + Count++; + _timing.total_time = utils::spendTime(clock(), _timing.start_total_time); + ROS_INFO_STREAM("Hz (total): " << 1e3/_timing.total_time); + cout << "\033[1;31m====================================== \033[0m\n"; + if (_valgrind_check){ + valgrind_check++; + if (valgrind_check > 0) { + _stop = 1; + break; + } + } + } + } + + + /* + * A function to get pcl OrderedBuff from a ros sensor-msgs form of pointcould queue + * */ + std::vector> LiDARTag::_getOrderBuff(){ + _point_cloud1_queue_lock.lock(); + if (_point_cloud1_queue.size()==0) { + _point_cloud1_queue_lock.unlock(); + //ros::spinOnce(); + //cout << "Pointcloud queue is empty" << endl; + //cout << "size: " << Empty.size() << endl; + vector> Empty; + return Empty; + } + ROS_INFO_STREAM("Queue size: " << _point_cloud1_queue.size()); + + sensor_msgs::PointCloud2ConstPtr msg = _point_cloud1_queue.front(); + _current_scan_time = msg->header.stamp; + + // Convert to sensor_msg to pcl type + pcl::PointCloud::Ptr PclPointcloud(new pcl::PointCloud); + pcl::fromROSMsg(*msg, *PclPointcloud); + _point_cloud1_queue.pop(); + _point_cloud1_queue_lock.unlock(); + + std::vector> OrderedBuff(_beam_num); + + // Ordered pointcloud with respect to its own ring number + _fillInOrderedPC(PclPointcloud, OrderedBuff); + _point_cloud_size = PclPointcloud->size(); + + return OrderedBuff; + } + + + /* + * A function to get a LiDAR system parameters such as max, min points per scan + * and how many points per ring + * The data format is: + * + * (A) PointCountTable: + * PointCountTable[Scan][Ring] + * ----------------------------------------- + * 1 2 3 4 5 6 7 ... scan + * ----------------------------------------- + * 0 17xx ... + * ----------------------------------------- + * 1 16xx ... + * ----------------------------------------- + * 2 + * . + * . + * . + * 31 + * ring + * + * + * (B) MaxMinTable: + * ---------------------------------------- + * 1 2 3 .... scan + * Max Max Max + * Min Min Min + * --------------------------------------- + */ + void LiDARTag::_analyzeLiDARDevice(){ + clock_t begin = clock(); + int NumberOfScan = 0; + _LiDAR_system.point_count_table.resize(50); + _LiDAR_system.ring_average_table.reserve(_beam_num); + + // Initialize the table + MaxMin_t max_min{100000, -1, -1}; // min, ave, max + + for (int j=0; j<_beam_num; ++j){ + _LiDAR_system.ring_average_table.push_back(max_min); + } + + // Calulate for each scan with a few seconds + while (ros::ok()){ + std::vector> OrderedBuff = LiDARTag::_getOrderBuff(); + if (OrderedBuff.empty()){ + continue; + } + LiDARTag::_maxMinPtsInAScan(_LiDAR_system.point_count_table[NumberOfScan], + _LiDAR_system.max_min_table, + _LiDAR_system.ring_average_table, + OrderedBuff); + NumberOfScan++; + clock_t end = clock(); + if (((double) (end-begin)/ CLOCKS_PER_SEC) > 3){ + break; + } + } + for (auto i=_LiDAR_system.ring_average_table.begin(); i!=_LiDAR_system.ring_average_table.end(); ++i){ + (*i).average /= NumberOfScan; + } + + LiDARTag::_pointsPerSquareMeterAtOneMeter(); + + + + + // Check values of pointtable and maxmintable + // int k = 0; + // for (auto i=_LiDAR_system.PointCountTable.begin(); i!=_LiDAR_system.PointCountTable.end(); ++i, ++k){ + // cout << "Vector[" << k << "]:" << endl; + // for (auto j=(*i).begin(); j!=(*i).end(); ++j){ + // cout << "points: " << *j << endl; + // } + // } + + // k=0; + // for (auto i=_LiDAR_system.MaxMinTable.begin(); i!=_LiDAR_system.MaxMinTable.end(); ++i, ++k){ + // cout << "At scan [" << k << "]" << endl; + // cout << "Max: " << (*i).Max << endl; + // cout << "Min: " << (*i).Min << endl; + // } + + // k=0; + // for (auto i=_LiDAR_system.ring_average_table.begin(); i!=_LiDAR_system.ring_average_table.end(); ++i, ++k){ + // cout << "At ring [" << k << "]" << endl; + // cout << "Max: " << (*i).Max << endl; + // cout << "Ave: " << (*i).average << endl; + // cout << "Min: " << (*i).Min << endl; + // } + // exit(0); + } + + + /* + * A function to calculate how many points are supposed to be on a cluster at 1 + * meter away + */ + void LiDARTag::_pointsPerSquareMeterAtOneMeter(){ + double system_average; + + for (auto i=_LiDAR_system.ring_average_table.begin(); + i!=_LiDAR_system.ring_average_table.end(); ++i){ + system_average += (*i).average; + } + system_average /= _LiDAR_system.ring_average_table.size(); + _LiDAR_system.beam_per_vertical_radian = _beam_num / utils::deg2Rad(_vertical_fov); + _LiDAR_system.point_per_horizontal_radian = system_average / utils::deg2Rad(360); + } + + + /* + * A function to get a number of points on a given-distance tag or object + */ + int LiDARTag::_areaPoints(const double &Distance, const double &ObjWidth, const double &ObjHeight){ + double WAngle = ObjWidth * (1 + SQRT2) / abs(Distance); + + if (WAngle>=1) return (int) 1e6; // return big number to reject the cluster + + double HAngle = ObjHeight * (1 + SQRT2) / abs(Distance); + if (HAngle>=1) return (int) 1e6; // return big number to reject the cluster + + double HorizontalAngle = asin(WAngle); // in radian + double VerticalAngle = asin(HAngle); // in radian + int NumOfVerticalRing = floor(VerticalAngle * _LiDAR_system.beam_per_vertical_radian); + int NumOfHorizontalPoints = floor(HorizontalAngle * _LiDAR_system.point_per_horizontal_radian); + + // use 3 instead of 2 becasue of we assume the tag would be put in the dense + // region of LiDAR (LiDAR is denser in the middle) + int Area = floor(3 * (NumOfVerticalRing * NumOfHorizontalPoints) / (1 + SQRT2)); + + //cout << "distance: " << abs(Distance) << endl; + //cout << "HorizontalAngle: " << HorizontalAngle << endl; + //cout << "VerticalAngle: " << VerticalAngle << endl; + + //cout << "NumOfVerticalRing: " << NumOfVerticalRing << endl; + //cout << "NumOfHorizontalPoints: " << NumOfHorizontalPoints << endl; + //cout << "Area: " << Area << endl; + + return Area; + } + + + /* + * A function to find maximum points and minimum points in a single scan, i.e. to + * find extrema within 32 rings + */ + void LiDARTag::_maxMinPtsInAScan(std::vector &PointCountTable, + std::vector &MaxMinTable, + std::vector &RingAverageTable, + const std::vector>& OrderedBuff){ + + // every scan should have different largest/ smallest numbers + int Largest = -1; + int Smallest = 100000; + MaxMin_t max_min; + + int i = 0; + for (auto Ring=OrderedBuff.begin(); Ring!=OrderedBuff.end(); ++Ring){ + int RingSize = (*Ring).size(); + PointCountTable.push_back(RingSize); + + // first time (we had initialized with -1) + if (RingAverageTable[i].average<0) { + RingAverageTable[i].average = RingSize; + } + else{ + RingAverageTable[i].average = (RingAverageTable[i].average + RingSize); + } + + if (RingAverageTable[i].maxRingSize){ + RingAverageTable[i].min = RingSize; + } + + if (RingSize > Largest) { + Largest = RingSize; + max_min.max = RingSize; + } + + if (RingSize < Smallest) { + Smallest = RingSize; + max_min.min = RingSize; + } + i++; + } + MaxMinTable.push_back(max_min); + } + + + /* + * A function to transfer pcl msgs to ros msgs and then publish + * WhichPublisher should be a string of "organized" or "original" regardless + * lowercase and uppercase + */ + void LiDARTag::_publishPC(const pcl::PointCloud::Ptr &SourcePC, + const std::string &Frame, string WhichPublisher){ + utils::tranferToLowercase(WhichPublisher); // check letter cases + sensor_msgs::PointCloud2 PCsWaitedToPub; + pcl::toROSMsg(*SourcePC, PCsWaitedToPub); + PCsWaitedToPub.header.frame_id = Frame; + + try { + if (WhichPublisher=="edge") _edge_pub.publish(PCsWaitedToPub); + else if (WhichPublisher=="original") _original_pc_pub.publish(PCsWaitedToPub); + else if (WhichPublisher=="cluster") _cluster_pub.publish(PCsWaitedToPub); + else if (WhichPublisher=="payload") _payload_pub.publish(PCsWaitedToPub); + else { + throw "No such Publisher exists"; + } + } + catch (const char* msg){ + cout << "\033[1;31m========================= \033[0m\n"; + cerr << msg << endl; + cout << "\033[1;31m========================= \033[0m\n"; + exit(-1); + } + } + + + /* [basic ros] + * A function to push the received pointcloud into a queue in the class + */ + void LiDARTag::_pointCloudCallback(const sensor_msgs::PointCloud2ConstPtr &pc){ + _point_cloud_received = 1; // flag to make sure it receives a pointcloud at the very begining of the program + _point_cloud_header = pc->header; + boost::mutex::scoped_lock(_point_cloud1_queue_lock); + _point_cloud1_queue.push(pc); + } + + + /* + * A function to make sure the program has received at least one pointcloud at + * the very start of this program + */ + inline + void LiDARTag::_waitForPC(){ + while (ros::ok()){ + if (_point_cloud_received) { + ROS_INFO("Got pointcloud data"); + return; + } + ros::spinOnce(); + } + } + + + /* + * A function to slice the Veloydyne full points to sliced pointed based on ring + * number + * */ + inline + void LiDARTag::_fillInOrderedPC(const pcl::PointCloud::Ptr &PclPointcloud, + std::vector>& OrderedBuff) { + LiDARPoints_t LiDARPoint; + int index [_beam_num] = {0}; + for (auto && p : *PclPointcloud) { + LiDARPoint.point = p; + LiDARPoint.index = index[p.ring]; + index[p.ring] += 1; + OrderedBuff[p.ring].push_back(LiDARPoint); + } + } + + + /* + * Main function + */ + pcl::PointCloud::Ptr + LiDARTag::_lidarTagDetection(const std::vector>& OrderedBuff, + std::vector &ClusterBuff){ + + _timing.start_computation_time = clock(); + pcl::PointCloud::Ptr Out(new pcl::PointCloud); + Out->reserve(_point_cloud_size); + std::vector> EdgeBuff(_beam_num); // Buff to store all detected edges + + // calculate gradient for depth and intensity as well as group them into diff groups + _result_statistics = {{0, 0, 0, 0, 0, 0}, 0, 0, 0, 0}; + _timing.timing = clock(); + LiDARTag::_gradientAndGroupEdges(OrderedBuff, EdgeBuff, ClusterBuff); + _timing.edgingand_clustering_time = utils::spendTime(clock(), _timing.timing); + + _timing.timing = clock(); + // transform from a vector of vector (EdgeBuff) into a pcl vector (out) + boost::thread BuffToPclVectorThread(&LiDARTag::_buffToPclVector, this, boost::ref(EdgeBuff), Out); + _timing.to_pcl_vector_time = utils::spendTime(clock(), _timing.timing); + + + LiDARTag::_fillInCluster(OrderedBuff, ClusterBuff); + BuffToPclVectorThread.join(); + _result_statistics.point_cloud_size = _point_cloud_size; + _result_statistics.edge_cloud_size = Out->size(); + + _timing.computation_time = utils::spendTime(clock(), _timing.start_computation_time); + ROS_DEBUG_STREAM("--------------- summary ---------------"); + ROS_DEBUG_STREAM("Original cloud size: " << _result_statistics.point_cloud_size); + ROS_DEBUG_STREAM("--Edge cloud size: " << _result_statistics.edge_cloud_size); + ROS_DEBUG_STREAM("Original cluster: " << _result_statistics.original_cluster_size); + ROS_DEBUG_STREAM("--Point check Removed: " << _result_statistics.cluster_removal.removed_by_point_check); + ROS_DEBUG_STREAM("--Minimum return removed: " << _result_statistics.cluster_removal.minimum_return); + ROS_DEBUG_STREAM("--Boundary point Removed: " << _result_statistics.cluster_removal.boundary_point_check); + ROS_DEBUG_STREAM("--No Edge Removed: " << _result_statistics.cluster_removal.no_edge_check); + ROS_DEBUG_STREAM("--Not Enough for decode Removed: " << _result_statistics.cluster_removal.decoder_not_return); + ROS_DEBUG_STREAM("--Corners Removed: " << _result_statistics.cluster_removal.decoder_fail_corner); + ROS_DEBUG_STREAM("--Decode-Fail Removed: " << _result_statistics.cluster_removal.decode_fail); + ROS_DEBUG_STREAM("--Remaining Cluster: " << _result_statistics.remaining_cluster_size); + ROS_DEBUG_STREAM("computation_time Hz: " << 1/_timing.computation_time*1e3); + ROS_DEBUG_STREAM("--------------- Timing ---------------"); + ROS_DEBUG_STREAM("computation_time: " << _timing.computation_time); + ROS_DEBUG_STREAM("edgingand_clustering_time: " << _timing.edgingand_clustering_time); + ROS_DEBUG_STREAM("PointCheck: " << _timing.point_check_time); + ROS_DEBUG_STREAM("LineFitting: " << _timing.line_fitting_time); + ROS_DEBUG_STREAM("ExtractPayload: " << _timing.payload_extraction_time); + ROS_DEBUG_STREAM("NormalVector: " << _timing.normal_vector_time); + ROS_DEBUG_STREAM("PayloadDecoder: " << _timing.payload_decoding_time); + ROS_DEBUG_STREAM("_tagToRobot: " << _timing.tag_to_robot_time); + + return Out; + } + + + /* + * A function to + * (1) calculate the depth gradient and the intensity gradient at a point of a pointcloud + * (2) group detected 'edge' into different group + */ + void LiDARTag::_gradientAndGroupEdges(const std::vector>& OrderedBuff, + std::vector>& EdgeBuff, + std::vector &ClusterBuff) { + + // clock_t start = clock(); + // TODO: if suddently partial excluded, it will cause errors + for(int i=0; i<_beam_num; i++) { + for(int j=2; j _distance_bound || + std::abs(point.y) > _distance_bound || + std::abs(point.z) > _distance_bound) continue; + const auto& PointL = OrderedBuff[i][j-2].point; + const auto& PointR = OrderedBuff[i][j+2].point; + + double DepthGrad = std::max((PointL.getVector3fMap()-point.getVector3fMap()).norm(), + (point.getVector3fMap()-PointR.getVector3fMap()).norm()); + + // double IntenstityGrad = std::max(std::abs(PointL.intensity - point.intensity), + // std::abs(point.intensity - PointR.intensity)); + + // if (IntenstityGrad > _intensity_threshold && + // DepthGrad > _depth_threshold) { + if (DepthGrad > _depth_threshold) { + + // Cluster the detected 'edge' into different groups + _clusterClassifier(OrderedBuff[i][j], ClusterBuff); + + // push the detected point that is an edge into a buff + LiDARPoints_t LiDARPoints = {OrderedBuff[i][j].point, OrderedBuff[i][j].index, + DepthGrad, 0}; + EdgeBuff[i].push_back(LiDARPoints); + } + } + } + // clock_t end = clock(); + // cout << "extraction hz: " << 1/ (((double) (end - start))/clocks_per_sec) << endl; + } + + + /* + * A function to + * (1) remove invalid cluster based on the index is too far or not + * (2) fill in the points between index of edges + * (3) after filling, if the points are too less (based on the analyzed system + * and given distant of the cluster), then remove this cluster + * (4) Adaptive thresholding (Maximize and minimize intensity) by comparing + * with the average value + */ + void LiDARTag::_fillInCluster(const std::vector>& OrderedBuff, + std::vector &ClusterBuff){ + + _result_statistics.original_cluster_size = ClusterBuff.size(); + _result_statistics.remaining_cluster_size = ClusterBuff.size(); + for (int i=0; i_filling_gap_max_index) continue; + + // push points between index into the cluster + for (int k=MinIndex+1; k < MaxIndex; ++k){ // remove minimum index itself (it has been in the cloud already) + ClusterBuff[i].data.push_back(OrderedBuff[j][k]); + //cout << "point added:" << k << endl; + } + } + + // If the points in this cluster after filling are still less than the + // factored threshold, then remove it + _timing.timing = clock(); + if(!_clusterPointsCheck(ClusterBuff[i])){ + _timing.point_check_time += utils::spendTime(clock(), _timing.timing); + //cout << "cluster removed" << endl; + ClusterBuff[i].valid = 0; + //ClusterBuff.erase(ClusterBuff.begin()+i); + // _debug_cluster.point_check.push_back(&ClusterBuff[i]); + //i--; + _result_statistics.remaining_cluster_size -= 1; + _result_statistics.cluster_removal.removed_by_point_check ++; + } + // Adaptive thresholding (Maximize and minimize intensity) by comparing + // with the average value + else { + _timing.point_check_time += utils::spendTime(clock(), _timing.timing); + //boost::thread BuffToPclVectorThread(&LiDARTag::AdaptiveThresholding, this, boost::ref(ClusterBuff[i])); + if(!LiDARTag::_adaptiveThresholding(ClusterBuff[i])) { + ClusterBuff[i].valid = 0; + _result_statistics.remaining_cluster_size -= 1; + // ClusterBuff.erase(ClusterBuff.begin()+i); + // i--; + } + } + } + } + + + /* + * A function to + * (1) do adaptive thresholding (Maximize and minimize intensity) by comparing + * with the average value and + * (2) sort points with ring number and re-index with current cluster into + * tag_edges vector so as to do regression boundary lines + * (3) It will *remove* if linefitting fails + */ + bool LiDARTag::_adaptiveThresholding(ClusterFamily_t &Cluster){ + Cluster.ordered_points_ptr.resize(_beam_num); + Cluster.payload_right_boundary_ptr.resize(_beam_num); + Cluster.payload_left_boundary_ptr.resize(_beam_num); + + for (int k=0; kpoint.intensity = Cluster.data[k].point.intensity/Cluster.max_intensity.intensity; + // Calucate "biased" average intensity value + // if (ClusterPointPtr->point.intensity > MIN_INTENSITY) + // Cluster.accumulate_intensity_of_each_ring[ClusterPointPtr->point.ring] += ClusterPointPtr->point.intensity; + // else + // Cluster.accumulate_intensity_of_each_ring[ClusterPointPtr->point.ring] -= MAX_INTENSITY; // Punish if it is black + + Cluster.ordered_points_ptr[ClusterPointPtr->point.ring].push_back(ClusterPointPtr); + } + + // Fit line of the cluster + _timing.timing = clock(); + if (!LiDARTag::_detectPayloadBoundries(Cluster)){ + _timing.line_fitting_time += utils::spendTime(clock(), _timing.timing); + + return false; + } + else { + _timing.line_fitting_time += utils::spendTime(clock(), _timing.timing); + _timing.timing = clock(); + _extractPayloadWOThreshold(Cluster); + _timing.payload_extraction_time += utils::spendTime(clock(), _timing.timing); + + // 2 is because this payload points is actually includes black + // boundary + if (Cluster.payload.size() < _min_returns_per_grid*std::pow((std::sqrt(_tag_family)+2*_black_border), 2)) { + //_debug_cluster.ExtractPayload.push_back(&Cluster); + _result_statistics.cluster_removal.minimum_return ++; + return false; + } + + // return true; + if (_id_decoding){ + _timing.timing = clock(); + if (LiDARTag::_decodPayload(Cluster)){ + _timing.payload_decoding_time += utils::spendTime(clock(), _timing.timing); + + _timing.timing = clock(); + Cluster.normal_vector = _estimateNormalVector(Cluster); + _timing.normal_vector_time += utils::spendTime(clock(), _timing.timing); + + _timing.timing = clock(); + LiDARTag::_tagToRobot(Cluster.cluster_id, Cluster.normal_vector, + Cluster.pose, Cluster.transform, Cluster.average); + _timing.tag_to_robot_time += utils::spendTime(clock(), _timing.timing); + return true; + } + else { + return false; + } + } + else { + LiDARTag::_decodPayload(Cluster); + + // directly assign ID + string Code(_assign_id); + uint64_t Rcode = stoull(Code, nullptr, 2); + BipedAprilLab::QuickDecodeCodeword(tf, Rcode, &Cluster.entry); + Cluster.cluster_id = Cluster.entry.id; + + Cluster.normal_vector = _estimateNormalVector(Cluster); + LiDARTag::_tagToRobot(Cluster.cluster_id, Cluster.normal_vector, + Cluster.pose, Cluster.transform, Cluster.average); + return true; + } + } + } + + /* A function to publish pose of tag to the robot + */ + void LiDARTag::_tagToRobot(const int &cluster_id, const Eigen::Vector3f &NormalVec, + Homogeneous_t &pose, + tf::Transform &transform, const PointXYZRI &Ave){ + Eigen::Vector3f x(1, 0, 0); + Eigen::Vector3f y(0, 1, 0); + Eigen::Vector3f z(0, 0, 1); + pose.rotation = utils::qToR(NormalVec).cast (); + pose.translation << Ave.x, Ave.y, Ave.z; + + pose.yaw = utils::rad2Deg(acos(NormalVec.dot(y))); + pose.pitch = -utils::rad2Deg(acos(NormalVec.dot(x))); + pose.roll = utils::rad2Deg(acos(NormalVec.dot(z))); + + pose.homogeneous.topLeftCorner(3,3) = pose.rotation; + pose.homogeneous.topRightCorner(3,1) = pose.translation; + pose.homogeneous.row(3) << 0,0,0,1; + + static tf::TransformBroadcaster Broadcaster_; + transform.setOrigin(tf::Vector3(Ave.x, Ave.y, Ave.z)); + + // rotate to fit iamge frame + Eigen::Vector3f qr(0, std::sqrt(2)/2, 0); + float qr_w = std::sqrt(2)/2; + // Eigen::Vector3f qr(-0.5, 0.5, -0.5); + // float qr_w = 0.5; + + // Eigen::Vector3f qr(-0.5, -0.5, -0.5); + // float qr_w = 0.5; + // Eigen::Vector3f qr(std::sqrt(2)/2, 0, 0); + // float qr_w = std::sqrt(2)/2; + // Eigen::Vector3f qiCameraFrame = qr_w*NormalVec + 0*qr + qr.cross(NormalVec); // 0 is q.w of normalvec + // float qwCameraFrame = qr_w*0 - qr.dot(NormalVec); // 0 is q.w of normalvec + Eigen::Vector3f qiCameraFrame = NormalVec + 2*qr_w*(qr.cross(NormalVec)) + 2*qr.cross(qr.cross(NormalVec)); // 0 is q.w of normalvec + float qwCameraFrame = 0; // 0 is q.w of normalvec + + + Eigen::Vector3f q_i = qiCameraFrame; + double q_w = qwCameraFrame; + double norm = std::sqrt(std::pow(q_i(0), 2) + std::pow(q_i(1), 2) + std::pow(q_i(2), 2) + std::pow(q_w, 2)); + q_i = (q_i/norm).eval(); + q_w = q_w/norm; + tf::Quaternion q(q_i(0), q_i(1), q_i(2), q_w); + transform.setRotation(q); + Broadcaster_.sendTransform(tf::StampedTransform(transform, _point_cloud_header.stamp, + _pub_frame, to_string(cluster_id)+"_rotated")); + + + tf::Quaternion q2(NormalVec(0), NormalVec(1), NormalVec(2), 0); + transform.setRotation(q2); + Broadcaster_.sendTransform(tf::StampedTransform(transform, _point_cloud_header.stamp, + _pub_frame, "LiDARTag-ID" + to_string(cluster_id))); + + // publish lidar tag pose + lidartag_msgs::LiDARTagDetection lidartag_msg; //single message + lidartag_msg.id = cluster_id; + lidartag_msg.size = _payload_size; + geometry_msgs::Quaternion geo_q; + geo_q.x = q_i(0); + geo_q.y = q_i(1); + geo_q.z = q_i(2); + geo_q.w = q_w; + // cout << "R: \n" << pose.rotation << endl; + // cout << "det(R): \n" << pose.rotation.determinant() << endl; + // cout << "q: " << q_i(0) << ", " + // << q_i(1) << ", " + // << q_i(2) << ", " + // << q_w << endl; + lidartag_msg.pose.position.x = Ave.x; + lidartag_msg.pose.position.y = Ave.y; + lidartag_msg.pose.position.z = Ave.z; + lidartag_msg.pose.orientation = geo_q; + lidartag_msg.header = _point_cloud_header; + lidartag_msg.header.frame_id = std::string("lidartag_") + to_string(cluster_id); + _lidartag_pose_array.header = _point_cloud_header; + _lidartag_pose_array.detections.push_back(lidartag_msg); + // cout << "R.T*NV: " << endl << pose.rotation.transpose()*NormalVec << endl; + // cout << "H: " << endl << pose.homogeneous << endl; + + /* + Eigen::Vector3f x(1, 0, 0); + Eigen::Vector3f y(0, 1, 0); + Eigen::Vector3f z(0, 0, 1); + Eigen::Matrix3f zSkew; + zSkew << 0, -z(2), z(1), + z(2), 0, -z(0), + -z(1), z(0), 0; + Eigen::Vector3f u = zSkew*NormalVec; + //u = x.cross(NormalVec); + //u = z.cross(NormalVec); + //u = -z.cross(NormalVec); + //u = -x.cross(NormalVec); + //u = -y.cross(NormalVec); + //u = x.cross(NormalVec); + + u = (u.normalized()).eval(); + float theta = acos(z.dot(NormalVec)); + u = (u*theta).eval(); + Eigen::Matrix3f uSkew; + uSkew << 0, -u(2), u(1), + u(2), 0, -u(0), + -u(1), u(0), 0; + + pose.rotation = uSkew.exp(); + pose.translation << Ave.x, Ave.y, Ave.z; + pose.yaw = utils::rad2Deg(acos(NormalVec.dot(y))); + pose.pitch = -utils::rad2Deg(acos(NormalVec.dot(x))); + pose.roll = utils::rad2Deg(acos(NormalVec.dot(z))); + + pose.homogeneous.topLeftCorner(3,3) = pose.rotation; + pose.homogeneous.topRightCorner(3,1) = pose.translation; + pose.homogeneous.row(3) << 0,0,0,1; + + static tf::TransformBroadcaster Broadcaster_; + transform.setOrigin(tf::Vector3(Ave.x, Ave.y, Ave.z)); + Eigen::Vector3f q_i = sin(theta/2)*u; + double q_w = std::cos(theta/2); + double norm = std::sqrt(std::pow(q_i(0), 2) + std::pow(q_i(1), 2) + std::pow(q_i(2), 2) + std::pow(q_w, 2)); + q_i = (q_i/norm).eval(); + q_w = q_w/norm; + tf::Quaternion q(q_i(0), q_i(1), q_i(2), q_w); + transform.setRotation(q); + Broadcaster_.sendTransform(tf::StampedTransform(transform, _point_cloud_header.stamp, + _pub_frame, to_string(cluster_id))); + + // publish lidar tag pose + lidartag_msgs::LiDARTagDetection lidartag_msg; //single message + lidartag_msg.id = cluster_id; + lidartag_msg.size = _payload_size; + geometry_msgs::Quaternion geo_q; + geo_q.x = q_i(0); + geo_q.y = q_i(1); + geo_q.z = q_i(2); + geo_q.w = q_w; + // cout << "R: \n" << pose.rotation << endl; + // cout << "det(R): \n" << pose.rotation.determinant() << endl; + // cout << "q: " << q_i(0) << ", " + // << q_i(1) << ", " + // << q_i(2) << ", " + // << q_w << endl; + lidartag_msg.pose.position.x = Ave.x; + lidartag_msg.pose.position.y = Ave.y; + lidartag_msg.pose.position.z = Ave.z; + lidartag_msg.pose.orientation = geo_q; + lidartag_msg.header = _point_cloud_header; + lidartag_msg.header.frame_id = std::string("lidartag_") + to_string(cluster_id); + _lidartag_pose_array.header = _point_cloud_header; + _lidartag_pose_array.detections.push_back(lidartag_msg); + // cout << "R.T*NV: " << endl << pose.rotation.transpose()*NormalVec << endl; + // cout << "H: " << endl << pose.homogeneous << endl; + */ + } + + + + /* + * A function to extract the payload points from a valid cluster. + * Let's say we have 10 points on the left boundary (line) of the tag and 9 points on the right boundary + * (line) of the tag. + * It is seperated into two parts. + * TODO: should use medium instead of max points + * (i) Find the max points that we have on a ring in this cluster by + * exmaming the average points on the first 1/2 rings int((10+9)/4) + * (ii) For another half of the rings, we just find the start index and add the + * average number of points to the payload points + */ + void LiDARTag::_extractPayloadWOThreshold(ClusterFamily_t &Cluster){ + int LastRoundLength = 0; // Save to recover a missing ring + PointXYZRI average{0,0,0,0}; + for(int Ring=0; Ring<_beam_num; ++Ring){ + + // if (Cluster.payload_right_boundary_ptr[Ring]!=0) + // Cluster.payload_boundary_ptr.push_back(Cluster.payload_right_boundary_ptr[Ring]); + + // if (Cluster.payload_left_boundary_ptr[Ring]!=0) + // Cluster.payload_boundary_ptr.push_back(Cluster.payload_left_boundary_ptr[Ring]); + + if (Cluster.payload_right_boundary_ptr[Ring]==0 && + Cluster.payload_left_boundary_ptr[Ring]==0) continue; + // cout << "Ring" << Ring << endl; + else if (Cluster.payload_right_boundary_ptr[Ring]!=0 && + Cluster.payload_left_boundary_ptr[Ring]!=0){ + Cluster.payload_boundary_ptr.push_back(Cluster.payload_right_boundary_ptr[Ring]); + Cluster.payload_boundary_ptr.push_back(Cluster.payload_left_boundary_ptr[Ring]); + int StartIndex = Cluster.payload_left_boundary_ptr[Ring]->index; + int EndIndex = Cluster.payload_right_boundary_ptr[Ring]->index; + LastRoundLength = EndIndex - StartIndex; + + + for (int j=0; jindex == StartIndex){ + // Remove index itself because itself is not the part of a + // payload + for (int k=j+1; k=Cluster.ordered_points_ptr[Ring].size()) break; // make sure the index is valid + // cout << "j: " << j << endl; + // cout << "k: " << k << endl; + // cout << "validation1: " << endl; + // utils::COUT(Cluster.ordered_points_ptr[Ring][k]->point); + // + Cluster.payload.push_back(Cluster.ordered_points_ptr[Ring][k]); + average.x += Cluster.ordered_points_ptr[Ring][k]->point.x; + average.y += Cluster.ordered_points_ptr[Ring][k]->point.y; + average.z += Cluster.ordered_points_ptr[Ring][k]->point.z; + } + break; + } + } + } + Cluster.average.x = average.x/ Cluster.payload.size(); + Cluster.average.y = average.y/ Cluster.payload.size(); + Cluster.average.z = average.z/ Cluster.payload.size(); + // else if (LastRoundLength!=0 && Cluster.payload_right_boundary_ptr[Ring]!=0){ + // int EndIndex = Cluster.payload_right_boundary_ptr[Ring]->index; + + // for (int j=Cluster.ordered_points_ptr[Ring].size()-1; j>0; --j){ + // if (Cluster.ordered_points_ptr[Ring][j]->index == EndIndex){ + // Cluster.payload.push_back(Cluster.ordered_points_ptr[Ring][j]); + + // for (int k=j-1; k>j-LastRoundLength; --k){ + // if (k<0) break; // make sure the index is valid + // Cluster.payload.push_back(Cluster.ordered_points_ptr[Ring][k]); + // } + // break; + // } + // } + + // } + // else if (LastRoundLength!=0 && Cluster.payload_left_boundary_ptr[Ring]!=0){ + // int StartIndex = Cluster.payload_left_boundary_ptr[Ring]->index; + + // for (int j=0; jindex == StartIndex){ + // Cluster.payload.push_back(Cluster.ordered_points_ptr[Ring][j]); + + // for (int k=j-1; k=Cluster.ordered_points_ptr[Ring].size()) break; // make sure the index is valid + // Cluster.payload.push_back(Cluster.ordered_points_ptr[Ring][k]); + // } + // break; + // } + // } + + // } + } + } + + + bool LiDARTag::_detectPayloadBoundries(ClusterFamily_t &Cluster){ + // return true; + bool FitOkay = false; // Meaning reject this cluster and will be removed + + // Initialization + Cluster.tag_edges.upper_ring = _beam_num; + Cluster.tag_edges.lower_ring = 0; + double AverageIntensity = ((Cluster.max_intensity.intensity + + Cluster.min_intensity.intensity)/2); + double DetectionThreshold_ = (AverageIntensity - Cluster.min_intensity.intensity)/ + (_payload_intensity_threshold*Cluster.max_intensity.intensity); + //cout << "Detection Threshold: " << DetectionThreshold_ << endl; + + /* + * Compare current point's gradient intensity with next points + * gradient intensity + * Example: + * o: white, x: black + * + * a9876543210 + * ooooxxxxxxxooooo + * ^^^^ ^^^^ + * |||| |||| + * Gradient from 1-2 sould be large; the same as the gradient from 0-2 + * Gradient from 9-8 sould be large; the same as the gradient from a-8 + */ + int BoundaryPointCount = 0; // Check there're sufficient points + float RightMin = 1000; + float RightMax = -1000; + float LeftMin = 1000; + float LeftMax = -1000; + PointXYZRI PTOP; + PointXYZRI PDown; + PointXYZRI PLeft; + PointXYZRI PRight; + for (int Ring=0; Ring<_beam_num; ++Ring){ + // skip this ring if doesn't exist + if (Cluster.ordered_points_ptr[Ring].size()floor((Cluster.ordered_points_ptr[Ring].size()-2)/2); + //P>1; + --P){ + /* + * (1) By knowing it from white to black on the left calucate the + * intensity gradient + * (2) Since have thresholded already, we * could also choose > 255 + */ + // cout << "p intensity: " << Cluster.ordered_points_ptr[Ring][P]->point.intensity << endl; + // cout << "PR: " << P << endl; + // cout << "PointR: " << Cluster.ordered_points_ptr[Ring][P]->index << endl; + // utils::COUT(Cluster.ordered_points_ptr[Ring][P]->point); + // Cluster.payload_right_boundary_ptr[Ring] = Cluster.ordered_points_ptr[Ring][P]; + // FitOkay = true; + // break; + if ((Cluster.ordered_points_ptr[Ring][P]->point.intensity - + Cluster.ordered_points_ptr[Ring][P-1]->point.intensity>DetectionThreshold_) && + (Cluster.ordered_points_ptr[Ring][P+1]->point.intensity - + Cluster.ordered_points_ptr[Ring][P-1]->point.intensity>DetectionThreshold_)) { + + Cluster.payload_right_boundary_ptr[Ring] = Cluster.ordered_points_ptr[Ring][P]; + RightMin = (RightMin < Cluster.ordered_points_ptr[Ring][P]->point.y) ? RightMin : Cluster.ordered_points_ptr[Ring][P]->point.y; + RightMax = (RightMax > Cluster.ordered_points_ptr[Ring][P]->point.y) ? RightMax : Cluster.ordered_points_ptr[Ring][P]->point.y; + BoundaryPointCount ++; + + break; + } + } + + + /* [Left] + * Find edges from the left of a ring + * Start from 1 becasue we take another point on the left into account + * size -1 because we compare with next point + */ + // for (int P=2; P 255 + * (3) To determin if p if the edge: + * 1. compare with p+1 (to its right) + */ + //cout << "PL: " << P << endl; + //Cluster.payload_left_boundary_ptr[Ring] = Cluster.ordered_points_ptr[Ring][P]; + //cout << "PointL: " << Cluster.ordered_points_ptr[Ring][P]->index << endl; + //// utils::COUT(Cluster.ordered_points_ptr[Ring][P]->point); + //FitOkay = true; + + //break; + if ((Cluster.ordered_points_ptr[Ring][P]->point.intensity - + Cluster.ordered_points_ptr[Ring][P+1]->point.intensity>DetectionThreshold_) && + (Cluster.ordered_points_ptr[Ring][P-1]->point.intensity - + Cluster.ordered_points_ptr[Ring][P+1]->point.intensity>DetectionThreshold_)) { + + Cluster.payload_left_boundary_ptr[Ring] = Cluster.ordered_points_ptr[Ring][P]; + LeftMin = (LeftMin < Cluster.ordered_points_ptr[Ring][P]->point.y) ? LeftMin : Cluster.ordered_points_ptr[Ring][P]->point.y; + LeftMax = (LeftMax > Cluster.ordered_points_ptr[Ring][P]->point.y) ? LeftMax : Cluster.ordered_points_ptr[Ring][P]->point.y; + BoundaryPointCount ++; + + break; + } + } + } + + // reject if points are too less (can't even get decoded!) + if (BoundaryPointCount < int(sqrt(_tag_family))*2) { + _result_statistics.cluster_removal.boundary_point_check++; + // _debug_cluster.BoundaryPoint.push_back(&Cluster); + return false; + } else FitOkay = true; + + if (!FitOkay) { + _result_statistics.cluster_removal.no_edge_check++; + // _debug_cluster.NoEdge.push_back(&Cluster); + } + + return FitOkay; + } + + + + + + /* [Normal vector] + * A function to estimate the normal vector of a potential payload + */ + Eigen::MatrixXf + LiDARTag::_estimateNormalVector(ClusterFamily_t &Cluster){ + Eigen::MatrixXf EigenPC(3, Cluster.payload.size()); + + for (int i=0; ipoint.x - Cluster.average.x; + EigenPC(1,i) = Cluster.payload[i]->point.y - Cluster.average.y; + EigenPC(2,i) = Cluster.payload[i]->point.z - Cluster.average.z; + } + + Eigen::JacobiSVD svd(EigenPC, Eigen::ComputeFullU); + Eigen::Matrix normal_vector_robot = svd.matrixU().col(2); // Take the last column of U in SVD + + // flip through xy plane + // Eigen::Matrix3f m; + // m << 0, 1, 0, + // 0, 0, 1, + // 1, 0, 0; + + // m << 0,1,0, + // 1,0,0, + // 0,0,1; + + // m << 0,0,1, + // 1,0,0, + // 0,1,0; + // m << 0,0,1, + // 0,1,0, + // 1,0,0; + + // m << 1,0,0, + // 0,0,1, + // 0,1,0; + // cout << "Normal Vector1: \n" << normal_vector_robot << endl; + // normal_vector_robot = (m*normal_vector_robot).eval(); + // cout << "Normal Vector2: \n" << normal_vector_robot << endl; + + // Make sure consistency + if (normal_vector_robot(0)>=0) normal_vector_robot = (-normal_vector_robot).eval(); + // cout << "Normal Vector3: \n" << normal_vector_robot << endl; + + // Coordinate transform + // Eigen::Matrix normal_vector_tag; + // normal_vector_tag << normal_vector_robot(2), normal_vector_robot(0), normal_vector_robot(1); + // normal_vector_tag << normal_vector_robot(0), normal_vector_robot(2), normal_vector_robot(1); + + + return normal_vector_robot; + + // pcl::PointCloud::Ptr Cloud (new pcl::PointCloud); + + // for (int i=0; ipoint.x, + // Cluster.payload[i]->point.y, + // Cluster.payload[i]->point.z}; + // cout << "point: " << point << endl; + // Cloud->push_back(point); + // } + // pcl::PointCloud::Ptr normals (new pcl::PointCloud); + + // // pcl::IntegralImageNormalEstimation ne; + // // ne.setNormalEstimationMethod (ne.AVERAGE_3D_GRADIENT); + // // ne.setMaxDepthChangeFactor(0.02f); + // // ne.setNormalSmoothingSize(10.0f); + // // ne.setInputCloud(Cloud); + // // ne.compute(*normals); + // // cout << "NV: " << *normals << endl; + + // pcl::NormalEstimation NE; + // NE.setInputCloud (Cloud); + // pcl::search::KdTree::Ptr tree (new pcl::search::KdTree ()); + // NE.setSearchMethod (tree); + + // // Output datasets + // pcl::PointCloud::Ptr CloudNormals (new pcl::PointCloud); + // NE.setRadiusSearch (1); + // NE.compute (*CloudNormals); + // cout << "normals: " << *CloudNormals << endl; + // std::cout << "cloud_normals->points.size (): " << CloudNormals->points.size () << std::endl; + // + // return CloudNormals; + } + + /* + * A function of ros spin + * reason: in order to put it into background as well as able to run other tasks + */ + void LiDARTag::_rosSpin(){ + while (ros::ok() && !_stop){ + ros::spinOnce(); + } + } + + + /* + * A function to cluster a single point into a new cluster or an existing cluster + */ + void LiDARTag::_clusterClassifier(const LiDARPoints_t &point, vector &ClusterBuff){ + // The first time to cluster the point cloud + int ValidCluster = 1; // Marker every cluster is valid and will be checked again later + if (ClusterBuff.size()==0){ + PointXYZRI top_most_point = point.point; + top_most_point.z = top_most_point.z + _threshold; + PointXYZRI bottom_most_point = point.point; + bottom_most_point.z -= _threshold; + + PointXYZRI front_most_point = point.point; + front_most_point.x += _threshold; + PointXYZRI back_most_point = point.point; + back_most_point.x -= _threshold; + + PointXYZRI right_most_point = point.point; + right_most_point.y -= _threshold; + PointXYZRI left_most_point = point.point; + left_most_point.y += _threshold; + //cout << "_threshold:" << _threshold << endl; + // + //cout << "\033[1;31m============== \033[0m\n"; + //cout << "First created" << endl; + //cout << "TopMost: " << top_most_point.x << ", " << top_most_point.y << ", " << top_most_point.z << endl; + //cout << "bottom_most_point: " << bottom_most_point.x << ", " << bottom_most_point.y << ", " << bottom_most_point.z << endl; + //cout << "Front: " << front_most_point.x << ", " << front_most_point.y << ", " << front_most_point.z << endl; + //cout << "back: " << back_most_point.x << ", " << back_most_point.y << ", " << back_most_point.z << endl; + //cout << "Right: " << right_most_point.x << ", " << right_most_point.y << ", " << right_most_point.z << endl; + //cout << "Left: " << left_most_point.x << ", " << left_most_point.y << ", " << left_most_point.z << endl; + + ClusterFamily_t CurrentCluster = {0, ValidCluster, top_most_point, bottom_most_point, + front_most_point, back_most_point, + right_most_point, left_most_point, + point.point}; + MaxMin_t InitialValue = {(int)1e8, 0, -1}; + CurrentCluster.max_min_index_of_each_ring.resize(_beam_num, InitialValue); + CurrentCluster.max_min_index_of_each_ring[point.point.ring].max = point.index; + CurrentCluster.max_min_index_of_each_ring[point.point.ring].min = point.index; + + CurrentCluster.max_intensity = point.point; + CurrentCluster.min_intensity = point.point; + + CurrentCluster.data.push_back(point); + ClusterBuff.push_back(CurrentCluster); + return ; + } + else { + // Take a point to go through all the existing cluster to see if this + // point belongs to any of them + // Once it is found belonging to one of the clusters then return. + // After looping though and couldn't find a belonging group then add it + // to a new cluster + TestCluster_t *new_cluster = new TestCluster_t{0}; + //cout << "\033[1;31m============== \033[0m\n"; + //cout << "ClusterBuff size: " << ClusterBuff.size() << endl; + for (int i=0; iflag)) { + delete new_cluster; + return; + } + } + // Add a new cluster + if (new_cluster->flag){ + //cout << "new cluster added" << endl; + int Newcluster_id = ClusterBuff.size(); + PointXYZRI top_most_point = point.point; + top_most_point.z += _threshold; + PointXYZRI bottom_most_point = point.point; + bottom_most_point.z -= _threshold; + + PointXYZRI front_most_point = point.point; + front_most_point.x += _threshold; + PointXYZRI back_most_point = point.point; + back_most_point.x -= _threshold; + + PointXYZRI right_most_point = point.point; + right_most_point.y -= _threshold; + PointXYZRI left_most_point = point.point; + left_most_point.y += _threshold; + + //cout << "TopMost: " << top_most_point.x << ", " << top_most_point.y << ", " << top_most_point.z << endl; + //cout << "bottom_most_point: " << bottom_most_point.x << ", " << bottom_most_point.y << ", " << bottom_most_point.z << endl; + //cout << "Front: " << front_most_point.x << ", " << front_most_point.y << ", " << front_most_point.z << endl; + //cout << "back: " << back_most_point.x << ", " << back_most_point.y << ", " << back_most_point.z << endl; + //cout << "Right: " << right_most_point.x << ", " << right_most_point.y << ", " << right_most_point.z << endl; + //cout << "Left: " << left_most_point.x << ", " << left_most_point.y << ", " << left_most_point.z << endl; + + + // Check every time when new marker added + // visualization_msgs::MarkerArray CheckArray; + // visualization_msgs::Marker CheckMarker; + // LiDARTag::_assignMarker(CheckMarker, visualization_msgs::Marker::CUBE, + // "Check0", + // 1, 0, 0, + // top_most_point, 0, 0.05); + // CheckArray.markers.push_back(CheckMarker); + + // LiDARTag::_assignMarker(CheckMarker, visualization_msgs::Marker::CUBE, + // "Check1", + // 1, 0, 0, + // bottom_most_point, 1, 0.05); + // CheckArray.markers.push_back(CheckMarker); + + // LiDARTag::_assignMarker(CheckMarker, visualization_msgs::Marker::CUBE, + // "Check2", + // 1, 0, 0, + // front_most_point, 2, 0.05); + // CheckArray.markers.push_back(CheckMarker); + + // LiDARTag::_assignMarker(CheckMarker, visualization_msgs::Marker::CUBE, + // "Check3", + // 1, 0, 0, + // back_most_point, 3, 0.05); + // CheckArray.markers.push_back(CheckMarker); + + // LiDARTag::_assignMarker(CheckMarker, visualization_msgs::Marker::CUBE, + // "Check4", + // 1, 0, 0, + // left_most_point, 4, 0.05); + // CheckArray.markers.push_back(CheckMarker); + + // LiDARTag::_assignMarker(CheckMarker, visualization_msgs::Marker::CUBE, + // "Check5", + // 1, 0, 0, + // right_most_point, 5, 0.05); + // CheckArray.markers.push_back(CheckMarker); + + // LiDARTag::_assignMarker(CheckMarker, visualization_msgs::Marker::SPHERE, + // "Check6", + // 0, 1, 0, + // Point, 5, 0.1); + // CheckArray.markers.push_back(CheckMarker); + + // _boundary_marker_pub.publish(CheckArray); + //utils::pressEnterToContinue(); + + + new_cluster->new_cluster = {Newcluster_id, ValidCluster, top_most_point, bottom_most_point, + front_most_point, back_most_point, + right_most_point, left_most_point, + point.point}; + + // To fill in points between initial points and end points later + MaxMin_t InitialValue = {(int)1e8, 0, -1}; + new_cluster->new_cluster.max_min_index_of_each_ring.resize(_beam_num, InitialValue); + new_cluster->new_cluster.max_min_index_of_each_ring[point.point.ring].max = point.index; + new_cluster->new_cluster.max_min_index_of_each_ring[point.point.ring].min = point.index; + + new_cluster->new_cluster.max_intensity = point.point; + new_cluster->new_cluster.min_intensity = point.point; + + new_cluster->new_cluster.data.push_back(point); + ClusterBuff.push_back(new_cluster->new_cluster); + } + delete new_cluster; + } + } + + + /* + * A function update some information about a cluster if this point belongs to + * this cluster; if not belonging to this cluster then return and create a new + * one + */ + void LiDARTag::_updateCluster(const LiDARPoints_t &point, ClusterFamily_t &OldCluster, TestCluster_t *new_cluster){ + // This point is outside of the current cluster + if (!( + (point.point.z < OldCluster.top_most_point.z + _threshold) && + (point.point.x < OldCluster.front_most_point.x + _threshold) && + (point.point.y < OldCluster.left_most_point.y + _threshold) && + (OldCluster.back_most_point.x - _threshold< point.point.x) && + (OldCluster.right_most_point.y - _threshold< point.point.y) && + (OldCluster.bottom_most_point.z - _threshold< point.point.z))){ + + // cout << "\033[1;31m============== \033[0m\n"; + // cout << "New flag" << endl; + // cout << "point: " << point.point.x << ", " << point.point.y << ", " << point.point.z << endl; + // cout << "TOP.z: " << OldCluster.top_most_point.z << ", " << point.point.z << endl; + // cout << "Front.x: " << OldCluster.front_most_point.x << ", " << point.point.x << endl; + // cout << "Left.y: " << OldCluster.left_most_point.y << ", " << point.point.y << endl; + // cout << "Right.y: " << OldCluster.right_most_point.y << ", " << point.point.y << endl; + // cout << "Bottom.z: " << OldCluster.bottom_most_point.z << ", " << point.point.z << endl; + // cout << "Back.x: " << OldCluster.back_most_point.x << ", " << point.point.x << endl; + // utils::pressEnterToContinue(); + new_cluster->flag = 1; + return ; + } + else{ + new_cluster->flag = 0; + } + + // This point is inside this cluster + if (!new_cluster->flag){ + // update the boundary of the old cluster + if (point.point.x + _threshold > OldCluster.front_most_point.x) { + OldCluster.front_most_point = point.point; + OldCluster.front_most_point.x += _threshold; + } + if (point.point.x - _threshold < OldCluster.back_most_point.x) { + OldCluster.back_most_point = point.point; + OldCluster.back_most_point.x -= _threshold; + } + + if (point.point.y + _threshold > OldCluster.left_most_point.y) { + OldCluster.left_most_point = point.point; + OldCluster.left_most_point.y += _threshold; + } + if (point.point.y - _threshold< OldCluster.right_most_point.y) { + OldCluster.right_most_point = point.point; + OldCluster.right_most_point.y -= _threshold; + } + + if (point.point.z + _threshold > OldCluster.top_most_point.z) { + OldCluster.top_most_point = point.point; + OldCluster.top_most_point.z += _threshold; + } + if (point.point.z - _threshold < OldCluster.bottom_most_point.z) { + OldCluster.bottom_most_point = point.point; + OldCluster.bottom_most_point.z -= _threshold; + } + + // update the average // spend around 5-6 HZ + OldCluster.average.getVector3fMap() = ((OldCluster.average.getVector3fMap() * OldCluster.data.size() + + point.point.getVector3fMap()) / (OldCluster.data.size()+1)).eval(); + + // update the max/min index of each ring in this cluster + if (OldCluster.max_min_index_of_each_ring[point.point.ring].max < point.index) + OldCluster.max_min_index_of_each_ring[point.point.ring].max = point.index; + + if (OldCluster.max_min_index_of_each_ring[point.point.ring].min > point.index) + OldCluster.max_min_index_of_each_ring[point.point.ring].min = point.index; + + // update the max/min intensity of this cluster + if (OldCluster.max_intensity.intensity < point.point.intensity) + OldCluster.max_intensity = point.point; + + if (OldCluster.min_intensity.intensity > point.point.intensity) + OldCluster.min_intensity = point.point; + + OldCluster.data.push_back(point); + } + } + + + + + /* + * A function to transform from a customized type (LiDARpoints_t) of vector of vector (EdgeBuff) + * into a standard type (PointXYZRI) of pcl vector (out) + */ + void LiDARTag::_buffToPclVector(const std::vector> &EdgeBuff, + pcl::PointCloud::Ptr Out){ + for (int RingNumber=0; RingNumber<_beam_num; ++RingNumber){ + if (EdgeBuff.at(RingNumber).size()!=0){ + for (int i=0; ipush_back(EdgeBuff[RingNumber][i].point); + } + } + } + } + + + /* + * A function to prepare for displaying results in rviz + */ + void LiDARTag::_clusterToPclVectorAndMarkerPublisher(const std::vector &Cluster, + pcl::PointCloud::Ptr OutCluster, + pcl::PointCloud::Ptr OutPayload, + visualization_msgs::MarkerArray &ClusterArray){ + + /* initialize random seed for coloring the marker*/ + srand (time(NULL)); + visualization_msgs::MarkerArray BoundMarkArray; + visualization_msgs::MarkerArray PayloadMarkArray; + + + int PointsInClusters = 0; + for (int Key=0; Key0) ? + // -Cluster[Key].normal_vector(0) : Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.x = Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.y = Cluster[Key].normal_vector(1); + BoundaryMarker.pose.orientation.z = Cluster[Key].normal_vector(2); + BoundaryMarker.pose.orientation.w = 0; + BoundMarkArray.markers.push_back(BoundaryMarker); + + /* + LiDARTag::_assignMarker(BoundaryMarker, visualization_msgs::Marker::ARROW, + "NormalVector1" + to_string(Cluster[Key].cluster_id), + 1, 1, 0, + Cluster[Key].average, 1, 0.01); + BoundaryMarker.scale.x = 0.15; + // BoundaryMarker.pose.orientation.x = (Cluster[Key].normal_vector(0)>0) ? + // -Cluster[Key].normal_vector(0) : Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.x = Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.y = Cluster[Key].normal_vector(2); + BoundaryMarker.pose.orientation.z = Cluster[Key].normal_vector(1); + BoundaryMarker.pose.orientation.w = 0; + BoundMarkArray.markers.push_back(BoundaryMarker); + + LiDARTag::_assignMarker(BoundaryMarker, visualization_msgs::Marker::ARROW, + "NormalVector2" + to_string(Cluster[Key].cluster_id), + 1, 1, 0, + Cluster[Key].average, 1, 0.01); + BoundaryMarker.scale.x = 0.15; + // BoundaryMarker.pose.orientation.x = (Cluster[Key].normal_vector(0)>0) ? + // -Cluster[Key].normal_vector(0) : Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.x = Cluster[Key].normal_vector(1); + BoundaryMarker.pose.orientation.y = Cluster[Key].normal_vector(2); + BoundaryMarker.pose.orientation.z = Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.w = 0; + BoundMarkArray.markers.push_back(BoundaryMarker); + + LiDARTag::_assignMarker(BoundaryMarker, visualization_msgs::Marker::ARROW, + "NormalVector3" + to_string(Cluster[Key].cluster_id), + 1, 1, 0, + Cluster[Key].average, 1, 0.01); + BoundaryMarker.scale.x = 0.15; + // BoundaryMarker.pose.orientation.x = (Cluster[Key].normal_vector(0)>0) ? + // -Cluster[Key].normal_vector(0) : Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.x = Cluster[Key].normal_vector(1); + BoundaryMarker.pose.orientation.y = Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.z = Cluster[Key].normal_vector(2); + BoundaryMarker.pose.orientation.w = 0; + BoundMarkArray.markers.push_back(BoundaryMarker); + + LiDARTag::_assignMarker(BoundaryMarker, visualization_msgs::Marker::ARROW, + "NormalVector4" + to_string(Cluster[Key].cluster_id), + 1, 1, 0, + Cluster[Key].average, 1, 0.01); + BoundaryMarker.scale.x = 0.15; + // BoundaryMarker.pose.orientation.x = (Cluster[Key].normal_vector(0)>0) ? + // -Cluster[Key].normal_vector(0) : Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.x = Cluster[Key].normal_vector(2); + BoundaryMarker.pose.orientation.y = Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.z = Cluster[Key].normal_vector(1); + BoundaryMarker.pose.orientation.w = 0; + BoundMarkArray.markers.push_back(BoundaryMarker); + + LiDARTag::_assignMarker(BoundaryMarker, visualization_msgs::Marker::ARROW, + "NormalVector5" + to_string(Cluster[Key].cluster_id), + 1, 1, 0, + Cluster[Key].average, 1, 0.01); + BoundaryMarker.scale.x = 0.15; + // BoundaryMarker.pose.orientation.x = (Cluster[Key].normal_vector(0)>0) ? + // -Cluster[Key].normal_vector(0) : Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.x = Cluster[Key].normal_vector(2); + BoundaryMarker.pose.orientation.y = Cluster[Key].normal_vector(1); + BoundaryMarker.pose.orientation.z = Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.w = 0; + BoundMarkArray.markers.push_back(BoundaryMarker); + + LiDARTag::_assignMarker(BoundaryMarker, visualization_msgs::Marker::ARROW, + "NormalVector6" + to_string(Cluster[Key].cluster_id), + 1, 1, 0, + Cluster[Key].average, 1, 0.01); + BoundaryMarker.scale.x = 0.15; + // BoundaryMarker.pose.orientation.x = (Cluster[Key].normal_vector(0)>0) ? + // -Cluster[Key].normal_vector(0) : Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.x = Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.y = Cluster[Key].normal_vector(1); + BoundaryMarker.pose.orientation.z = Cluster[Key].normal_vector(2); + BoundaryMarker.pose.orientation.w = 0; + BoundMarkArray.markers.push_back(BoundaryMarker); + + LiDARTag::_assignMarker(BoundaryMarker, visualization_msgs::Marker::ARROW, + "NormalVector7" + to_string(Cluster[Key].cluster_id), + 1, 1, 0, + Cluster[Key].average, 1, 0.01); + BoundaryMarker.scale.x = 0.15; + // BoundaryMarker.pose.orientation.x = (Cluster[Key].normal_vector(0)>0) ? + // -Cluster[Key].normal_vector(0) : Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.x = Cluster[Key].normal_vector(0); + BoundaryMarker.pose.orientation.y = Cluster[Key].normal_vector(1); + BoundaryMarker.pose.orientation.z = Cluster[Key].normal_vector(2); + BoundaryMarker.pose.orientation.w = 0; + BoundMarkArray.markers.push_back(BoundaryMarker); + */ + + + + // LiDARTag::_assignMarker(BoundaryMarker, visualization_msgs::Marker::SPHERE, + // "Average" + to_string(Cluster[Key].cluster_id), + // r, g, b, + // Cluster[Key].average, 5, 0.04); + // BoundMarkArray.markers.push_back(BoundaryMarker); + + + + //_cluster_pub.publish(BoundaryMarkerList); + //LiDARTag::_assignLine(BoundaryMarkerList, visualization_msgs::Marker::LINE_STRIP, + // r, g, b, + // Cluster[Key], 1); + //_cluster_pub.publish(BoundaryMarkerList); + + // _boundary_marker_pub.publish(BoundMarkArray); + + // Draw payload boundary marker + visualization_msgs::Marker PayloadMarker; + + if (_adaptive_thresholding){ + // Upper boundary + for (int i=0; ipoint, i, 0.015); + PayloadMarkArray.markers.push_back(PayloadMarker); + } + + // Lower boundary + for (int i=0; ipoint, i, 0.015); + PayloadMarkArray.markers.push_back(PayloadMarker); + } + + // Left boundary (green) + for (int i=0; ipoint, i, 0.015); + PayloadMarkArray.markers.push_back(PayloadMarker); + } + + // Right boundary (red) + for (int i=0; ipoint, i, 0.015); + PayloadMarkArray.markers.push_back(PayloadMarker); + } + } + else { + + // cout << "size1: " << Cluster[Key].payload_boundary_ptr.size() << endl; + int count = 0; + for (int i=0; ipoint, count, 0.015); + // PayloadMarkArray.markers.push_back(PayloadMarker); + // count ++; + + // LiDARTag::_assignMarker(PayloadMarker, visualization_msgs::Marker::SPHERE, + // "PayloadBoundary_" + to_string(Cluster[Key].cluster_id), + // 1, 0, 0, + // Cluster[Key].payload_boundary_ptr[i][1]->point, count, 0.015); + // count ++; + // PayloadMarkArray.markers.push_back(PayloadMarker); + // } + // for (int k=0; kpoint, i, 0.015); + PayloadMarkArray.markers.push_back(PayloadMarker); + count ++; + // } + } + } + + // Text + /* + LiDARTag::_assignMarker(PayloadMarker, visualization_msgs::Marker::TEXT_VIEW_FACING, + "PayloadUpperBoundary_text_" + to_string(Cluster[Key].cluster_id), + 1, 1, 1, + Cluster[Key].tag_edges.upper_line[0]->point, 5, 0.05, + string("Ring: " + to_string(Cluster[Key].tag_edges.upper_line[0]->point.ring)) + ); + PayloadMarkArray.markers.push_back(PayloadMarker); + + LiDARTag::_assignMarker(PayloadMarker, visualization_msgs::Marker::TEXT_VIEW_FACING, + "PayloadLowerBoundary_text_" + to_string(Cluster[Key].cluster_id), + 1, 1, 1, + Cluster[Key].tag_edges.lower_line[0]->point, 5, 0.05, + string("Ring: " + to_string(Cluster[Key].tag_edges.lower_line[0]->point.ring)) + ); + PayloadMarkArray.markers.push_back(PayloadMarker); + */ + + + // corner points and RANSAC line + if (_adaptive_thresholding){ + Eigen::Vector4f EigenPoint; + PointXYZRI point; // just for conversion + + for (int i=0; i<4; ++i){ // 4 corners + // Corners + if (i!=3){ + pcl::lineWithLineIntersection(Cluster[Key].line_coeff[i], Cluster[Key].line_coeff[i+1], + EigenPoint, 1e-2); + } + else { + pcl::lineWithLineIntersection(Cluster[Key].line_coeff[i], Cluster[Key].line_coeff[0], + EigenPoint, 1e-2); + } + + LiDARTag::_eigenVectorToPointXYZRI(EigenPoint, point); + LiDARTag::_assignMarker(PayloadMarker, visualization_msgs::Marker::SPHERE, + "Corner_" + to_string(Cluster[Key].cluster_id), + 0, 1, 1, + point, i, 0.02); + PayloadMarkArray.markers.push_back(PayloadMarker); + + // RANSAC + LiDARTag::_assignMarker(PayloadMarker, visualization_msgs::Marker::ARROW, + "RANSAC" + to_string(Cluster[Key].cluster_id), + 1, 1, 0, + point, i, 0.01); + double Length = sqrt(pow(Cluster[Key].line_coeff[i][3],2) + + pow(Cluster[Key].line_coeff[i][4],2) + + pow(Cluster[Key].line_coeff[i][5],2)); + PayloadMarker.scale.x = 0.15; + PayloadMarker.pose.orientation.x = Cluster[Key].line_coeff[i][3]/Length; + PayloadMarker.pose.orientation.y = Cluster[Key].line_coeff[i][4]/Length; + PayloadMarker.pose.orientation.z = Cluster[Key].line_coeff[i][5]/Length; + PayloadMarkArray.markers.push_back(PayloadMarker); + } + } + + // Add lines to the tag + //LiDARTag::_assignLine(PayloadMarker, PayloadMarkArray, + // visualization_msgs::Marker::LINE_STRIP, + // "left_line_" + to_string(Cluster[Key].cluster_id), + // r, g, b, + // PointTL, PointTR, 1); + //PayloadMarkArray.markers.push_back(PayloadMarker); + + + // Draw all detected-filled cluster points + for (int i=0; ipush_back(Cluster[Key].data[i].point); + double Intensity = Cluster[Key].data[i].point.intensity; + LiDARTag::_assignMarker(Marker, visualization_msgs::Marker::SPHERE, + to_string(Cluster[Key].cluster_id), + Intensity, Intensity, Intensity, + Cluster[Key].data[i].point, i, 0.01); + ClusterArray.markers.push_back(Marker); + } + + // Add all payload points to a vector and will be publish to another + // channel for both visualizatoin and creating training datasets + // cout << "payload size2: " << Cluster[Key].payload.size() << endl; + for (int i=0; ipush_back(Cluster[Key].payload[i]->point); + // cout << "Intensity: " << Cluster[Key].payload[i]->point.intensity << endl; + } + + + } + if (PointsInClusters>_filling_max_points_threshold) { + cout << "Too many points on a tag" << endl; + exit(-1); + } + + //cout << "BoundaryMarkerList size/10: " << BoundaryMarkerList.points.size()/10 << endl; + _boundary_marker_pub.publish(BoundMarkArray); + _cluster_marker_pub.publish(ClusterArray); + _payload_marker_pub.publish(PayloadMarkArray); + } + + + /* + * A function to draw a line between two points + */ + void LiDARTag::_assignLine(visualization_msgs::Marker &Marker, visualization_msgs::MarkerArray MarkArray, + const uint32_t Shape, const string ns, + const double r, const double g, const double b, + const PointXYZRI point1, const PointXYZRI point2, const int Count){ + Marker.header.frame_id = _pub_frame; + Marker.header.stamp = _current_scan_time; + //Marker.ns = string("Boundary_") + to_string(Cluster.cluster_id); + Marker.ns = ns; + Marker.id = Count; + Marker.type = Shape; + + Marker.action = visualization_msgs::Marker::ADD; + Marker.pose.orientation.x = 0.0; + Marker.pose.orientation.y = 0.0; + Marker.pose.orientation.z = 0.0; + Marker.pose.orientation.w = 1.0; + + // Set the scale of the marker -- 1x1x1 here means 1m on a side + Marker.scale.x = 1; + + // Set the color -- be sure to set alpha to something non-zero! + Marker.color.r = r; + Marker.color.g = g; + Marker.color.b = b; + Marker.color.a = 1.0; + + geometry_msgs::Point p; + p.x = point1.x; + p.y = point1.y; + p.z = point1.z; + Marker.points.push_back(p); + p.x = point2.x; + p.y = point2.y; + p.z = point2.z; + Marker.points.push_back(p); + MarkArray.markers.push_back(Marker); + } + + + /* + * A function to draw a point in rviz + */ + void LiDARTag::_assignMarker(visualization_msgs::Marker &Marker, const uint32_t Shape, const string NameSpace, + const double r, const double g, const double b, + const PointXYZRI &point, + const int Count, const double Size, const string Text){ + Marker.header.frame_id = _pub_frame; + Marker.header.stamp = _current_scan_time; + Marker.ns = NameSpace; + Marker.id = Count; + Marker.type = Shape; + Marker.action = visualization_msgs::Marker::ADD; + Marker.pose.position.x = point.x; + Marker.pose.position.y = point.y; + Marker.pose.position.z = point.z; + Marker.pose.orientation.x = 0.0; + Marker.pose.orientation.y = 0.0; + Marker.pose.orientation.z = 0.0; + Marker.pose.orientation.w = 1.0; + Marker.text = Text; + Marker.lifetime = ros::Duration(_sleep_time_for_vis); // should disappear along with updateing rate + + // Set the scale of the marker -- 1x1x1 here means 1m on a side + Marker.scale.x = Size; + Marker.scale.y = Size; + Marker.scale.z = Size; + + // Set the color -- be sure to set alpha to something non-zero! + Marker.color.r = r; + Marker.color.g = g; + Marker.color.b = b; + Marker.color.a = 1.0; + } + + + /* + * A valid cluster, valid tag, the points from the original point cloud that belong to the cluster + * could be estimated from the LiDAR system + * Therefore, if the points on the tag is too less, which means it is not a valid + * tag where it might just a shadow of a valid tag + */ + bool LiDARTag::_clusterPointsCheck(ClusterFamily_t &Cluster){ + int points = LiDARTag::_areaPoints(Cluster.average.x, _payload_size, _payload_size); + //cout << "points: " << points << endl; + //cout << "Data: " << Cluster.data.size() << endl; + // return true; + if (Cluster.data.size() < floor(points/ _points_threshold_factor)) return false; + else return true; + } + + + /* + * A function to transform an eigen type of vector to pcl point type + */ + void LiDARTag::_eigenVectorToPointXYZRI(const Eigen::Vector4f &Vector, PointXYZRI &point){ + point.x = Vector[0]; + point.y = Vector[1]; + point.z = Vector[2]; + } + + + void LiDARTag::_getCodeNaive(string &Code, pcl::PointCloud payload){ + int topring = 0; + int bottomring = _beam_num; + PointXYZRI tl= payload[0]->point; + PointXYZRI tr= payload[0]->point; + PointXYZRI br= payload[0]->point; + PointXYZRI bl= payload[0]->point; + + + // Find the size of the payload + for (int i=0; ipoint; + if (point.y>tl.y && point.z>tl.z) tl = point; + if (point.y>bl.y && point.ztr.z) tr = point; + if (point.y payload49(_tag_family, 0); + int d = sqrt(_tag_family); + float IntervalY = abs( (tl.y+bl.y)/2 - (tr.y+br.y)/2 ) /(d+_black_border-1); + float IntervalZ = abs( (tl.z+tr.z)/2 - (bl.z+br.z)/2 ) /(d+_black_border-1); + + + if (_fake_tag){ + tl.y = 0; + tl.z = 0; + IntervalY = 1; + IntervalZ = 1; + payload.clear(); + payload.reserve((_tag_family+4*d*_black_border+4*(std::pow(_black_border, 2)))); + float j = 0; + float k = 0; + LiDARPoints_t point; + for (int i=0; i<(_tag_family+4*d*_black_border+4*(std::pow(_black_border, 2))); ++i){ + if (i%(d+2*_black_border)==0 && i!=0) {k++; j=0;} + LiDARPoints_t *point = new LiDARPoints_t{{0, j, k,0,0}, 0,0,0,0}; + payload.push_back(point); + // cout << "j,k: " << j << ", " << k << endl; + j++; + //cout << "payload[i]" << payload[i]->point.y << ", " << payload[i]->point.z << endl; + // delete point; + } + payload[20]->point.intensity = 100; + payload[26]->point.intensity = 100; + payload[27]->point.intensity = 100; + payload[28]->point.intensity = 100; + payload[34]->point.intensity = 100; + payload[36]->point.intensity = 100; + payload[43]->point.intensity = 100; + payload[45]->point.intensity = 100; + } + + + // Calcutate Average intensity for thresholding + float AveIntensity = 0; + for (int i=0; ipoint.intensity; + } + AveIntensity /= payload.size(); + // cout << "size: " << payload.size() << endl; + // cout << "AveIntensity: " << AveIntensity << endl; + + + // Split into grids + for (int i=0; ipoint); + // cout << "i: " << i << endl; + // cout << "point.y: " << pointPtr->y << endl; + // cout << "point.z: " << pointPtr->z << endl; + float DeltaY = abs(pointPtr->y - tl.y); + float DeltaZ = abs(pointPtr->z - tl.z); + + // if (DeltaY==0 || (DeltaY)) { + // if (pointPtr->intensity < AveIntensity) payload49[0] -= 1; + // else payload49[0] += 1; + // continue; + // } + int Y = floor(DeltaY/IntervalY); + int Z = floor(DeltaZ/IntervalZ); + // cout << "Y: " << Y << endl; + // cout << "Z: " << Z << endl; + + // remove black borders + if (Y>=_black_border && Z>=_black_border && Y<=d+_black_border && Z<=d+_black_border){ + int y = (Y-_black_border)%d; // the yth column (remove the black border) + int z = (Z-_black_border)%d; // the zth row (remove the black border) + + int k = d*z + y; // index in a 1D vector + // cout << "y: " << y << endl; + // cout << "z: " << z << endl; + // cout << "k: " << k << endl; + // cout << "intensity: " << pointPtr->intensity << endl; + if (pointPtr->intensity <= AveIntensity) payload49[k] -= 1; + else payload49[k] += 1; + //cout << "payload[k]: " << payload49[k] << endl; + //cout << "--" << endl; + } + + } + + // Threshold into black and white + for (int i=0; i<_tag_family; ++i){ + if (payload49[i]<0) Code += to_string(0); + else Code += to_string(1); + } + + for (int i=0; i<_tag_family; ++i){ + if (i%d==0){ + cout << "\n"; + cout << " " << Code[i]; + } + else{ + cout << " " << Code[i]; + } + } + + + + Code += "UL"; + //cout << "\ncodeB: " << Code << endl; + //0101111111111111UL + //cout << "Code: " << "0010001100011011UL" << endl; + cout << "\nCode 1: \n" << " 0 0 1 0\n 1 1 1 0\n 1 0 1 0\n 0 1 1 1" << endl; + // exit(-1); + // Code = "0010001100011011UL"; + //Code = "0b0000011111100000011000011000110011100000111111111UL"; + // Code = "11111100000011000011000110011100000111111111UL"; //1 + //Code = "0001100111110000011001100011111000000110001100011UL"; // 2 + //cout << "codeA: " << Code << endl; + } + + + /* Decode using Weighted Gaussian weight + * return 0: normal + * return -1: not enough return + * return -2: fail corner detection + */ + int LiDARTag::_getCodeWeightedGaussian(string &Code, Homogeneous_t &pose, + int &payload_points, + const PointXYZRI &average, + const pcl::PointCloud &payload, + const std::vector &payload_boundary_ptr){ + /* p11 + * . p11. . . . . p41 ^ z + * . . . ave . y __| + * p21 . . . p41 . . . + * . . . . + * . p21. . . . . p31 + * p31 + * px2s are just second largest number corresponding to x position + */ + + // For visualization + visualization_msgs::MarkerArray GridMarkerArray; + visualization_msgs::Marker GridMarker; + + visualization_msgs::Marker LineStrip; + LineStrip.header.frame_id = _pub_frame; + LineStrip.header.stamp = _current_scan_time; + LineStrip.ns = "boundary" ; + LineStrip.action = visualization_msgs::Marker::ADD; + LineStrip.pose.orientation.w= 1.0; + LineStrip.id = 1; + LineStrip.type = visualization_msgs::Marker::LINE_STRIP; + LineStrip.scale.x = 0.002; + LineStrip.color.b = 1.0; + LineStrip.color.a = 1.0; + + + + PointXYZRI p11{0, 0, -1000, 0}; + PointXYZRI p21{0, -1000, 0, 0}; + PointXYZRI p31{0, 0, 1000, 0}; + PointXYZRI p41{0, 1000, 0, 0}; + + PointXYZRI p12{0, 0, -1000, 0}; + PointXYZRI p22{0, -1000, 0, 0}; + PointXYZRI p32{0, 0, 1000, 0}; + PointXYZRI p42{0, 1000, 0, 0}; + + + // Find the largest + for (int i=0; ipoint; + + if (point.z>=p11.z) p11 = point; + if (point.y>=p21.y) p21 = point; + if (point.z<=p31.z) p31 = point; + if (point.y<=p41.y) p41 = point; + + } + if(_grid_viz){ + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("11"), + 0, 0, 0, + p11, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("21"), + 0, 0, 0, + p21, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("31"), + 0, 0, 0, + p31, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("41"), + 0, 0, 0, + p41, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + } + + // Find the second large + for (int i=0; ipoint; + if (point.z=p12.z) p12 = point; + if (point.y=p22.y) p22 = point; + + if (point.z>p31.z && point.z<=p32.z) p32 = point; + if (point.y>p41.y && point.y<=p42.y) p42 = point; + } + + if(_grid_viz){ + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("12"), + 0, 0, 0, + p12, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("22"), + 0, 0, 0, + p22, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("32"), + 0, 0, 0, + p32, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("42"), + 0, 0, 0, + p42, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + } + + PointXYZRI p1 = utils::pointsAddDivide(p11, p12, 2); + PointXYZRI p2 = utils::pointsAddDivide(p21, p22, 2); + PointXYZRI p3 = utils::pointsAddDivide(p31, p32, 2); + PointXYZRI p4 = utils::pointsAddDivide(p41, p42, 2); + + // check condition of the detected corners + // if corners are not between certain angle or distance, + // consider the tag is up right => + // change way of detection + int status = utils::checkCorners(_payload_size, p1, p2, p3, p4); + if (status!=0){ + // the tag is up right + p1 = {0, 0,-1000, 0}; + p2 = {0, 0, 1000, 0}; + p3 = {0, 0, 1000, 0}; + p4 = {0, 0,-1000, 0}; + for (int i=0; ipoint; + + // left boundary + if (point.z>=p1.z && point.y > average.y/2) p1 = point; + if (point.z<=p2.z && point.y > average.y/2) p2 = point; + + // right boundary + if (point.z<=p3.z && point.y < average.y/2) p3 = point; + if (point.z>=p4.z && point.y < average.y/2) p4 = point; + } + } + + if(_grid_viz){ + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("1"), + 1, 1, 1, + p1, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("2"), + 1, 1, 1, + p2, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("3"), + 1, 1, 1, + p3, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Grid_" + string("4"), + 1, 1, 1, + p4, 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + GridMarkerArray.markers.push_back(GridMarker); + } + + + int d = std::sqrt(_tag_family); + Eigen:: MatrixXf Vertices = Eigen::MatrixXf::Zero(3,5); + utils::formGrid(Vertices, 0, 0, 0, _payload_size); + Eigen::Matrix3f R; + // Eigen::MatrixXf VerticesOffset = (Vertices.colwise() - utils::toEigen(average)); + // cout << "vertice: " << Vertices << endl; + // cout << "verticeOffset: " << VerticesOffset << endl; + // cout << "Average: " << utils::toEigen(average) << endl; + utils::minus(p1, average); + utils::minus(p2, average); + utils::minus(p3, average); + utils::minus(p4, average); + + utils::fitGrid(Vertices, R, p1, p2, p3, p4); + Eigen::Vector3f Angle = utils::rotationMatrixToEulerAngles(R); + + if(_grid_viz){ + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Model_" + string("1"), + 0, 1, 0, + utils::toVelodyne(Vertices.col(1)), 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Model_" + string("2"), + 0, 1, 0, + utils::toVelodyne(Vertices.col(2)), 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Model_" + string("3"), + 0, 1, 0, + utils::toVelodyne(Vertices.col(3)), 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::CUBE, + "Model_" + string("4"), + 0, 1, 0, + utils::toVelodyne(Vertices.col(4)), 1, 0.01); + GridMarkerArray.markers.push_back(GridMarker); + + geometry_msgs::Point p; + p.x = Vertices(0, 1); + p.y = Vertices(1, 1); + p.z = Vertices(2, 1); + LineStrip.points.push_back(p); + p.x = Vertices(0, 2); + p.y = Vertices(1, 2); + p.z = Vertices(2, 2); + LineStrip.points.push_back(p); + p.x = Vertices(0, 3); + p.y = Vertices(1, 3); + p.z = Vertices(2, 3); + LineStrip.points.push_back(p); + p.x = Vertices(0, 4); + p.y = Vertices(1, 4); + p.z = Vertices(2, 4); + LineStrip.points.push_back(p); + p.x = Vertices(0, 1); + p.y = Vertices(1, 1); + p.z = Vertices(2, 1); + LineStrip.points.push_back(p); + } + + // Calcutate Average intensity for thresholding + float AveIntensity = 0; + for (int i=0; ipoint.intensity; + + AveIntensity /= payload.size(); + + vector Votes(payload.size()); + vector vR(std::pow((d+2*_black_border), 2)); + vector vG(std::pow((d+2*_black_border), 2)); + vector vB(std::pow((d+2*_black_border), 2)); + + // pick a random color for each cell + if(_grid_viz){ + for (int i=0; ipoint); + utils::getProjection(p1, p4, *pointPtr, t14, v14); + utils::getProjection(p1, p2, *pointPtr, t12, v12); + Votes[i].p = pointPtr; + PointXYZRI p; // for visualization + utils::assignCellIndex(_payload_size, R, p, + average, d + 2*_black_border, Votes[i]); + if(_grid_viz){ + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::SPHERE, + "TransPoints", + vR[Votes[i].cell], vG[Votes[i].cell], vB[Votes[i].cell], + p, i, 0.005); + GridMarkerArray.markers.push_back(GridMarker); + } + + } + vector> Grid(std::pow((d+2*_black_border), 2)); + utils::sortPointsToGrid(Grid, Votes); + int TooLessReturn = 0; + int PayloadPointCount = 0; + for (int i=(d+2*_black_border)*_black_border+_black_border; + i<(d+2*_black_border)*(_black_border+d)-_black_border; ++i){ + + if ((i%(d+2*_black_border)<_black_border) || + (i%(d+2*_black_border)>(d+_black_border-1))) continue; + + if (Grid[i].size() < _min_returns_per_grid) TooLessReturn ++; + if (TooLessReturn>_max_decode_hamming) return -1; + + float WeightedProb = 0; + float WeightSum = 0; + float minIntensity = 10000.; + float maxIntensity = -1.; + double r; + double g; + double b; + + // pick a random color for each cell + if(_grid_viz){ + r = (double) rand() / RAND_MAX; + g = (double) rand() / RAND_MAX; + b = (double) rand() / RAND_MAX; + } + + for (int j=0; jp), j, 0.005); + GridMarkerArray.markers.push_back(GridMarker); + LiDARTag::_assignMarker(GridMarker, visualization_msgs::Marker::SPHERE, + "Center" + to_string(i), + 1, 1, 1, + Grid[i][j]->centroid, j, 0.005); + GridMarkerArray.markers.push_back(GridMarker); + LiDARTag::_assignMarker(GridMarker, + visualization_msgs::Marker::TEXT_VIEW_FACING, + "Prob" + to_string(i), + 1, 1, 1, + *(Grid[i][j]->p), j, 0.003, + to_string((Grid[i][j]->p->intensity))); + GridMarkerArray.markers.push_back(GridMarker); + } + WeightedProb += Grid[i][j]->weight*Grid[i][j]->p->intensity; + WeightSum += Grid[i][j]->weight; + } + WeightedProb /= WeightSum; + PayloadPointCount += Grid[i].size(); + + if (WeightedProb>0.5) Code += to_string(1); + else Code += to_string(0); + } + payload_points = PayloadPointCount; + + Code += "UL"; + + if(_grid_viz){ + _payload_grid_pub.publish(GridMarkerArray); + _payload_grid_line_pub.publish(LineStrip); + } + return 0; + } + + + /* [Payload decoding] + * A function to decode payload with different means + * 0: Naive decoding + * 1: Weighted Gaussian + * 2: Deep learning + * 3: Gaussian Process + * 4: ?! + */ + bool LiDARTag::_decodPayload(ClusterFamily_t &Cluster){ + string Code(""); + bool ValidTag = true; + string Msg; + + if (_decode_method==0){ // Naive decoder + LiDARTag::_getCodeNaive(Code, Cluster.payload); + } + else if (_decode_method==1){ // Weighted Gaussian + int status = LiDARTag::_getCodeWeightedGaussian( + Code, Cluster.pose, + Cluster.payload_without_boundary, // size of actual payload + Cluster.average, + Cluster.payload, + Cluster.payload_boundary_ptr); + + if (_id_decoding){ + if (status==-1){ + ValidTag = false; + _result_statistics.cluster_removal.decoder_not_return ++; + Cluster.valid = 0; + Msg = "Not enough return"; + } + else if (status==-2){ + ValidTag = false; + _result_statistics.cluster_removal.decoder_fail_corner ++; + Cluster.valid = 0; + Msg = "Fail corner detection"; + } + } + } + + if (ValidTag && _id_decoding){ + uint64_t Rcode = stoull(Code, nullptr, 2); + BipedAprilLab::QuickDecodeCodeword(tf, Rcode, &Cluster.entry); + Cluster.cluster_id = Cluster.entry.id; + ROS_INFO_STREAM("id: " << Cluster.entry.id); + ROS_DEBUG_STREAM("hamming: " << Cluster.entry.hamming); + ROS_DEBUG_STREAM("rotation: " << Cluster.entry.rotation); + } + else { + // too big, return as an invalid tag + Code = "1111111111111111UL"; + ROS_DEBUG_STREAM("\nCODE: " << Code); + uint64_t Rcode = stoull(Code, nullptr, 2); + BipedAprilLab::QuickDecodeCodeword(tf, Rcode, &Cluster.entry); + Cluster.cluster_id = 8888; + ROS_DEBUG_STREAM("id: " << Cluster.cluster_id); + ROS_DEBUG_STREAM("hamming: " << Cluster.entry.hamming); + ROS_DEBUG_STREAM("rotation: " << Cluster.entry.rotation); + } + return ValidTag; + } + + + + /* [Decoder] + * Create hash table of chosen tag family + */ + void LiDARTag::_initDecoder(){ + string famname = "tag" + to_string(_tag_family) + "h" + to_string(_tag_hamming_distance); + if (famname == "tag49h14") tf = tag49h14_create(); + else if (famname == "tag16h5") tf = tag16h5_create(); + else { + cout << "[ERROR]" << endl; + cout << "Unrecognized tag family name: "<< famname << ". Use e.g. \"tag16h5\". " << endl; + cout << "This is line " << __LINE__ << " of file "<< __FILE__ << + " (function " << __func__ << ")"<< endl; + exit(0); + } + tf->black_border = _black_border; + cout << "Preparing for tags: " << famname << endl; + BipedAprilLab::QuickDecodeInit(tf, _max_decode_hamming); + } + + /* + * + */ + void LiDARTag::_testInitDecoder(){ + uint64_t rcode = 0x0001f019cf1cc653UL; + QuickDecodeEntry_t entry; + BipedAprilLab::QuickDecodeCodeword(tf, rcode, &entry); + cout << "code: " << entry.rcode << endl; + cout << "id: " << entry.id << endl; + cout << "hamming: " << entry.hamming << endl; + cout << "rotation: " << entry.rotation << endl; + exit(0); + } + + + + /* [not used] [not finished] + * A function to group edges + */ + template + void LiDARTag::_freeVec(Container& c) { + while(!c.empty()) { + if (c.back()!=nullptr) { + delete c.back(); + c.back()=nullptr; + } + c.pop_back(); + } + } + + void LiDARTag::_freePCL(pcl::PointCloud &vec){ + while(!vec.empty()) delete vec.back(), vec.erase(vec.end()); + } + + void LiDARTag::_freeTagLineStruc(TagLines_t &tag_edges){ + LiDARTag::_freeVec(tag_edges.upper_line); + LiDARTag::_freeVec(tag_edges.lower_line); + LiDARTag::_freeVec(tag_edges.left_line); + LiDARTag::_freeVec(tag_edges.right_line); + + LiDARTag::_freeVec(tag_edges.bottom_left); + LiDARTag::_freeVec(tag_edges.bottom_right); + LiDARTag::_freeVec(tag_edges.top_left); + LiDARTag::_freeVec(tag_edges.top_right); + } +} // namespacer diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..c9dca5d --- /dev/null +++ b/src/main.cc @@ -0,0 +1,45 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + +#include +#include + +using namespace std; + +int main(int argc, char **argv){ + + ros::init(argc, argv, "lidar_tag_node"); + BipedLab::LiDARTag lidar_tag; + + cout << "Done!" << endl; + + return 0; +} + diff --git a/src/tag16h5.cc b/src/tag16h5.cc new file mode 100644 index 0000000..12fbaac --- /dev/null +++ b/src/tag16h5.cc @@ -0,0 +1,80 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + +#include +#include "tag16h5.h" + +BipedLab::GrizTagFamily_t *tag16h5_create() +{ + BipedLab::GrizTagFamily_t *tf = (BipedLab::GrizTagFamily_t*) calloc(1, sizeof(BipedLab::GrizTagFamily_t)); + tf->black_border = 1; + tf->d = 4; + tf->h = 5; + tf->ncodes = 30; + tf->codes = (uint64_t*) calloc(30, sizeof(uint64_t)); + tf->codes[30] = 0x000000000000af2eUL; + tf->codes[1] = 0x0000000000002ea5UL; + tf->codes[2] = 0x000000000000346aUL; + tf->codes[3] = 0x00000000000045b9UL; + tf->codes[4] = 0x00000000000079a6UL; + tf->codes[5] = 0x0000000000007f6bUL; + tf->codes[6] = 0x000000000000b358UL; + tf->codes[7] = 0x000000000000e745UL; + tf->codes[8] = 0x000000000000fe59UL; + tf->codes[9] = 0x000000000000156dUL; + tf->codes[10] = 0x000000000000380bUL; + tf->codes[11] = 0x000000000000f0abUL; + tf->codes[12] = 0x0000000000000d84UL; + tf->codes[13] = 0x0000000000004736UL; + tf->codes[14] = 0x0000000000008c72UL; + tf->codes[15] = 0x000000000000af10UL; + tf->codes[16] = 0x000000000000093cUL; + tf->codes[17] = 0x00000000000093b4UL; + tf->codes[18] = 0x000000000000a503UL; + tf->codes[19] = 0x000000000000468fUL; + tf->codes[20] = 0x000000000000e137UL; + tf->codes[21] = 0x0000000000005795UL; + tf->codes[22] = 0x000000000000df42UL; + tf->codes[23] = 0x0000000000001c1dUL; + tf->codes[24] = 0x000000000000e9dcUL; + tf->codes[25] = 0x00000000000073adUL; + tf->codes[26] = 0x000000000000ad5fUL; + tf->codes[27] = 0x000000000000d530UL; + tf->codes[28] = 0x00000000000007caUL; + tf->codes[29] = 0x000000000000af2eUL; + return tf; +} + +void tag16h5_destroy(BipedLab::GrizTagFamily_t *tf) +{ + free(tf->name); + free(tf->codes); + free(tf); +} diff --git a/src/tag49h14.cc b/src/tag49h14.cc new file mode 100644 index 0000000..c19177a --- /dev/null +++ b/src/tag49h14.cc @@ -0,0 +1,100 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + +#include +#include "tag49h14.h" + +BipedLab::GrizTagFamily_t *tag49h14_create() +{ + BipedLab::GrizTagFamily_t *tf = (BipedLab::GrizTagFamily_t*) calloc(1, sizeof(BipedLab::GrizTagFamily_t)); + tf->black_border = 1; + tf->d = 7; + tf->h = 14; + tf->ncodes = 50; + tf->codes = (uint64_t*) calloc(50, sizeof(uint64_t)); + tf->codes[1] = 0x00000fc0c319c1ffUL; + tf->codes[2] = 0x000033e0cc7c0c63UL; + tf->codes[3] = 0x0000703e339cce0fUL; + tf->codes[4] = 0x0001cf38c31f3198UL; + tf->codes[5] = 0x0001f019cf1cc663UL; + tf->codes[6] = 0x00018f99f19cf1f8UL; + tf->codes[7] = 0x000060ff81983e0cUL; + tf->codes[8] = 0x0001cf18ce03387fUL; + tf->codes[9] = 0x00018fe07e00c673UL; + tf->codes[10] = 0x0001cf060018ce73UL; + tf->codes[11] = 0x0000633f3c60c673UL; + tf->codes[12] = 0x0001e386307f0c73UL; + tf->codes[13] = 0x00003039ce67fc0fUL; + tf->codes[14] = 0x00003f98c318fe78UL; + tf->codes[15] = 0x00007f9f807f31f0UL; + tf->codes[16] = 0x00001f1f01fcfe0cUL; + tf->codes[17] = 0x0001e3007e603073UL; + tf->codes[18] = 0x0001e07f30063e0fUL; + tf->codes[19] = 0x0000701fc0e70c73UL; + tf->codes[20] = 0x00019ce63f9ccc07UL; + tf->codes[21] = 0x00019cc7ce660e0cUL; + tf->codes[22] = 0x00001f98cc673e0cUL; + tf->codes[23] = 0x000070673c67f19cUL; + tf->codes[24] = 0x00000fe07e06318fUL; + tf->codes[25] = 0x0001cce0f18601f0UL; + tf->codes[26] = 0x0001cf003f01f1f0UL; + tf->codes[27] = 0x00018fc73318398cUL; + tf->codes[28] = 0x00019c7f0187f998UL; + tf->codes[29] = 0x0001e319cf07c38cUL; + tf->codes[30] = 0x00001c667fe1f80fUL; + tf->codes[31] = 0x0001cc6181fe3c1cUL; + tf->codes[32] = 0x00003fc18181fe0fUL; + tf->codes[33] = 0x00003f0671fff18cUL; + tf->codes[34] = 0x0001e39981e3f198UL; + tf->codes[35] = 0x00003c6000e70c07UL; + tf->codes[36] = 0x00007fe70cfc319cUL; + tf->codes[37] = 0x0001e31e7001fc1cUL; + tf->codes[38] = 0x00007f383ce3fc63UL; + tf->codes[39] = 0x00001c660e063e67UL; + tf->codes[40] = 0x00018398ce1803e3UL; + tf->codes[41] = 0x00003f39f19fcc07UL; + tf->codes[42] = 0x0001cce18e030187UL; + tf->codes[43] = 0x000033c0f19c3e18UL; + tf->codes[44] = 0x00007338c1fe001cUL; + tf->codes[45] = 0x00007f8633033198UL; + tf->codes[46] = 0x0001cf39fe7e0607UL; + tf->codes[47] = 0x0001f3c67f1cce60UL; + tf->codes[48] = 0x0001e3380f99f803UL; + tf->codes[49] = 0x00018c6181f8c3f8UL; + tf->codes[50] = 0x00001fe1f1f8ce70UL; + return tf; +} + +void tag49h14_destroy(BipedLab::GrizTagFamily_t *tf) +{ + free(tf->name); + free(tf->codes); + free(tf); +} diff --git a/src/utils.cc b/src/utils.cc new file mode 100644 index 0000000..90fb1f5 --- /dev/null +++ b/src/utils.cc @@ -0,0 +1,571 @@ +/* Copyright (C) 2013-2020, The Regents of The University of Michigan. + * All rights reserved. + * This software was developed in the Biped Lab (https://www.biped.solutions/) + * under the direction of Jessy Grizzle, grizzle@umich.edu. This software may + * be available under alternative licensing terms; contact the address above. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * The views and conclusions contained in the software and documentation are those + * of the authors and should not be interpreted as representing official policies, + * either expressed or implied, of the Regents of The University of Michigan. + * + * AUTHOR: Bruce JK Huang (bjhuang@umich.edu) + * WEBSITE: https://www.brucerobot.com/ + */ + + +#include "utils.h" +#include +#include + +namespace BipedLab{ + namespace utils { + // milisecond + double spendTime(const std::clock_t &t_end, const std::clock_t &t_start){ + return (((double) (t_end - t_start))/CLOCKS_PER_SEC)*1e3; + } + + std::string tranferToLowercase(std::string &t_data){ + std::transform(t_data.begin(), t_data.end(), t_data.begin(), ::tolower); + + return t_data; + } + + void pressEnterToContinue() { + int c; + printf("Press [Enter] key to continue.\n"); + while(getchar()!='\n'); // option TWO to clean stdin + getchar(); // wait for ENTER + } + + double deg2Rad(double t_degree){ + return t_degree*M_PI/180; + } + + double rad2Deg(double t_radian){ + return t_radian*180/M_PI; + } + + // Checks if a matrix is a valid rotation matrix. + bool isRotationMatrix(Eigen::Matrix3f &t_R) { + Eigen::Matrix3f should_be_identity = t_R*t_R.transpose(); + return (should_be_identity - Eigen::Matrix3f::Identity()).norm() < 1e-6; + } + + Eigen::Vector3f rotationMatrixToEulerAngles(Eigen::Matrix3f &t_R) { + // assert(isRotationMatrix(t_R)); + float sy = std::sqrt(t_R(0,0) * (0,0) + + t_R(1,0) * (1,0)); + + bool singular = sy < 1e-6; + + float x, y, z; + if (!singular) { + x = rad2Deg(std::atan(t_R(2,1)/ t_R(2,2))); + y = rad2Deg(std::atan(-t_R(2,0)/ sy)); + z = rad2Deg(std::atan(t_R(1,0)/ t_R(0,0))); + } + else { + x = rad2Deg(std::atan(-t_R(1,2)/ t_R(1,1))); + y = rad2Deg(std::atan(-t_R(2,0)/ sy)); + z = 0; + } + + return Eigen::Vector3f(x, y, z); + } + + + /* + * A function to check if get all parameters + */ + bool checkParameters(int t_n, ...){ + va_list vl_num; + va_start(vl_num, t_n); + bool Pass = true; + + for (int i=0; iindex < B->index; + } + + uint64_t bitShift(std::string const& t_value) { + uint64_t result = 0; + + char const* p = t_value.c_str(); + char const* q = p + t_value.size(); + while (p < q) { + result = (result << 1) + (result << 3) + *(p++) - '0'; + } + + return result; + } + + void normalize(std::vector &x, std::vector &y, + std::vector &z, std::vector &I, + const pcl::PointCloud t_payload){ + // normlize the y,z so the top left is (0,0) and bottom right is (1,1) + // as well as x axis + // o + // top left /| + // o----_o LiDAR ---> front o | back + // | | | o + // | | |/ + // o-----o o + // bottom right + float front_x = 1e8; + float back_x = -1e8; + float bottom_right_y = 1e8; + float top_left_y = -1e8; + float bottom_right_z = 1e8; + float top_left_z = -1e8; + + float max_intensity = -1e8; + + for (int i=0; ipoint.x>back_x) back_x = t_payload[i]->point.x; + if(t_payload[i]->point.xpoint.x; + + if(t_payload[i]->point.y>top_left_y) top_left_y = t_payload[i]->point.y; + if(t_payload[i]->point.ypoint.y; + + if(t_payload[i]->point.z>top_left_z) top_left_z = t_payload[i]->point.z; + if(t_payload[i]->point.zpoint.z; + if(t_payload[i]->point.intensity>max_intensity) max_intensity = t_payload[i]->point.intensity; + } + + float dx = std::abs(front_x - back_x); + float dy = std::abs(top_left_y - bottom_right_y); + float dz = std::abs(top_left_z - bottom_right_z); + for (int i=0; ipoint.x)/8; + y[i] = (top_left_y - t_payload[i]->point.y)/8; + z[i] = (top_left_z - t_payload[i]->point.z)/8; + I[i] = (t_payload[i]->point.intensity)/1.5; + } + } + + + void normalizeByAve(std::vector &x, std::vector &y, + std::vector &z, std::vector &I, + const pcl::PointCloud t_payload){ + float ave_x = 0; + float ave_y = 0; + float ave_z = 0; + + for (int i=0; ipoint.x; + ave_y += t_payload[i]->point.y; + ave_z += t_payload[i]->point.z; + x[i] = t_payload[i]->point.x; + y[i] = t_payload[i]->point.y; + z[i] = t_payload[i]->point.z; + I[i] = t_payload[i]->point.intensity; + } + ave_x /= t_payload.size(); + ave_y /= t_payload.size(); + ave_z /= t_payload.size(); + + for (int i=0; i reference x + // prepare for Gaussian + float xOffset = t_vote.p->x - t_average.x; + float yOffset = t_vote.p->y - t_average.y; + float zOffset = t_vote.p->z - t_average.z; + // float x = t_vote.p->x; + // float y = t_vote.p->y; + // float z = t_vote.p->z; + + float x = xOffset*t_R(0,0) + yOffset*t_R(0,1) + zOffset*t_R(0,2); + float y = xOffset*t_R(1,0) + yOffset*t_R(1,1) + zOffset*t_R(1,2); + float z = xOffset*t_R(2,0) + yOffset*t_R(2,1) + zOffset*t_R(2,2); + + // x = x*t_R(0,0) + y*t_R(0,1) + z*t_R(0,2) + t_average.x; + // y = x*t_R(1,0) + y*t_R(1,1) + z*t_R(1,2) + t_average.y; + // z = x*t_R(2,0) + y*t_R(2,1) + z*t_R(2,2) + t_average.z; + // y,z should range int_ between -3s and 3s + t_p_reference.x = x; + t_p_reference.y = y; + t_p_reference.z = z; + float ss = t_tag_size/t_d; // scale back to the unit square + y = std::max(std::min(y, t_d/2*ss), (-t_d/2*ss+(float)0.001)); // don't match to 6 + z = std::max(std::min(z, t_d/2*ss), (-t_d/2*ss+(float)0.001)); // don't match to 6 + int cellIndexT = t_d/2 + std::floor(-y/ss); + int cellIndexK = t_d/2 + std::floor(-z/ss); + + float cy = (std::ceil(y/ss) - 0.5)*ss; // offset to center of each ceil + float cz = (std::ceil(z/ss) - 0.5)*ss; + + // which grid it belongs to (in 1-16 vector form)? + Eigen::Vector2f X(y, z); + Eigen::Vector2f Mean(cy, cz); + t_vote.centroid.x = 0; + t_vote.centroid.y = cy; + t_vote.centroid.z = cz; + t_vote.cell = t_d*cellIndexK + cellIndexT; + t_vote.weight = MVN(t_tag_size, t_d, X, Mean); + } + + // normalize weight and classify them into grid + void sortPointsToGrid(std::vector> &t_grid, + std::vector &t_votes){ + for (int i=0; i svd(M, Eigen::ComputeFullU | Eigen::ComputeFullV); + // Eigen::Matrix R = svd.matrixV()*svd.matrixU().transpose(); + Eigen::Matrix R = svd.matrixU()*svd.matrixV().transpose(); + H = R; // H: payload -> ref + } + + velodyne_pointcloud::PointXYZIR toVelodyne(const Eigen::Vector3f &t_p){ + velodyne_pointcloud::PointXYZIR point; + point.x = t_p[0]; + point.y = t_p[1]; + point.z = t_p[2]; + + return point; + } + + Eigen::Vector3f toEigen(const velodyne_pointcloud::PointXYZIR &t_point){ + Eigen::Vector3f tmp; + tmp[0] = t_point.x; + tmp[1] = t_point.y; + tmp[2] = t_point.z; + + return tmp; + } + + void minus(velodyne_pointcloud::PointXYZIR &t_p1, + const velodyne_pointcloud::PointXYZIR &t_p2){ + t_p1.x = t_p1.x - t_p2.x; + t_p1.y = t_p1.y - t_p2.y; + t_p1.z = t_p1.z - t_p2.z; + } + + float distance( + const velodyne_pointcloud::PointXYZIR &t_p1, + const velodyne_pointcloud::PointXYZIR &t_p2){ + return std::sqrt(std::pow((t_p1.x - t_p2.x), 2) + + std::pow((t_p1.y - t_p2.y), 2) + + std::pow((t_p1.z - t_p2.z), 2)); + } + + /* + * A function to calculate angle between va and vb + * return: angle in degree + */ + template + float getAngle (T a, U b) { + return rad2Deg(std::acos(dot(a, b)/ (Norm(a) * Norm(b)))); + } + + /* + * Check if 4 four corners are valid + * return 0: valid corners + * return -1: incorrect distance + * return -2: incorrect angle + */ + int checkCorners( + const float Tagsize, + const velodyne_pointcloud::PointXYZIR &t_p1, + const velodyne_pointcloud::PointXYZIR &t_p2, + const velodyne_pointcloud::PointXYZIR &t_p3, + const velodyne_pointcloud::PointXYZIR &t_p4){ + + // XXX tunable + float ratio = 1/3; + float AngleLowerBound = 75; + float AngleUpperBound = 105; + if (distance(t_p1, t_p2) < Tagsize*ratio) return -1; + if (distance(t_p1, t_p3) < Tagsize*ratio) return -1; + if (distance(t_p1, t_p4) < Tagsize*ratio) return -1; + if (distance(t_p2, t_p3) < Tagsize*ratio) return -1; + if (distance(t_p2, t_p4) < Tagsize*ratio) return -1; + if (distance(t_p3, t_p4) < Tagsize*ratio) return -1; + + // angle between p12 and p14 + float Angle1 = getAngle + (vectorize(t_p1, t_p2), vectorize(t_p1, t_p4)); + if ((Angle1 + (vectorize(t_p2, t_p1), vectorize(t_p2, t_p3)); + if ((Angle2 + (vectorize(t_p3, t_p2), vectorize(t_p3, t_p4)); + if ((Angle3 + (vectorize(t_p4, t_p3), vectorize(t_p4, t_p1)); + if ((Angle4 + T blockMatrix(int t_n, ...){ + /* Function for creating blockdiagonal given arbitrary number of arguments. */ + va_list vl_num; + va_start(vl_num, t_n); + int cols_now = 0; + int rows_now = 0; + + for (int i=0; i + // Eigen::Matrix4d poseToEigenMatrix(const T &pose){ + Eigen::Matrix4d poseToEigenMatrix(const geometry_msgs::Pose &t_pose){ + Eigen::Matrix4d matrix_pose = Eigen::Matrix4d::Identity(); + matrix_pose(0, 3) = t_pose.position.x; + matrix_pose(1, 3) = t_pose.position.y; + matrix_pose(2, 3) = t_pose.position.z; + matrix_pose.topLeftCorner(3, 3) << qToR(t_pose); + + return matrix_pose; + } + + + // pose is geometry_msgs pose + template + Eigen::Matrix3d qToR(const T &t_pose){ + Eigen::Matrix3d R = Eigen::Matrix3d::Identity(); + double a = t_pose.orientation.w; + double b = t_pose.orientation.x; + double c = t_pose.orientation.y; + double d = t_pose.orientation.z; + R(0, 0) = std::pow(a, 2) + std::pow(b, 2) - std::pow(c, 2) - std::pow(d, 2); + R(0, 1) = 2*(b*c - a*d); + R(0, 2) = 2*(b*d + a*c); + + R(1, 0) = 2*(b*c + a*d); + R(1, 1) = std::pow(a, 2) - std::pow(b, 2) + std::pow(c, 2) - std::pow(d, 2); + R(1, 2) = 2*(c*d - a*b); + + R(2, 0) = 2*(b*d - a*c); + R(2, 1) = 2*(c*d + a*b); + R(2, 2) = std::pow(a, 2) - std::pow(b, 2) - std::pow(c, 2) + std::pow(d, 2); + + return R; + } + + Eigen::Matrix3d qToR(const Eigen::Vector3f &t_pose){ + Eigen::Matrix3d R = Eigen::Matrix3d::Identity(); + double a = 0; // w + double b = t_pose(0); // x + double c = t_pose(1); // y + double d = t_pose(2); // z + R(0, 0) = std::pow(a, 2) + std::pow(b, 2) - std::pow(c, 2) - std::pow(d, 2); + R(0, 1) = 2*(b*c - a*d); + R(0, 2) = 2*(b*d + a*c); + + R(1, 0) = 2*(b*c + a*d); + R(1, 1) = std::pow(a, 2) - std::pow(b, 2) + std::pow(c, 2) - std::pow(d, 2); + R(1, 2) = 2*(c*d - a*b); + + R(2, 0) = 2*(b*d - a*c); + R(2, 1) = 2*(c*d + a*b); + R(2, 2) = std::pow(a, 2) - std::pow(b, 2) - std::pow(c, 2) + std::pow(d, 2); + + return R; + } + + + Eigen::Matrix3d qMultiplication(const double &q1_w, const Eigen::Vector3f &q1, + const double &q2_w, const Eigen::Vector3f &q2){ + } + } // utils +} // Bipedlab