LCOV - code coverage report
Current view: top level - src/common/autoware_auto_geometry/include/geometry/bounding_box - lfit.hpp (source / functions) Hit Total Coverage
Test: coverage.filtered Lines: 92 94 97.9 %
Date: 2022-02-08 20:36:52 Functions: 16 16 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 19 34 55.9 %

           Branch data     Line data    Source code
       1                 :            : // Copyright 2017-2019 the Autoware Foundation
       2                 :            : //
       3                 :            : // Licensed under the Apache License, Version 2.0 (the "License");
       4                 :            : // you may not use this file except in compliance with the License.
       5                 :            : // You may obtain a copy of the License at
       6                 :            : //
       7                 :            : //     http://www.apache.org/licenses/LICENSE-2.0
       8                 :            : //
       9                 :            : // Unless required by applicable law or agreed to in writing, software
      10                 :            : // distributed under the License is distributed on an "AS IS" BASIS,
      11                 :            : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12                 :            : // See the License for the specific language governing permissions and
      13                 :            : // limitations under the License.
      14                 :            : //
      15                 :            : // Co-developed by Tier IV, Inc. and Apex.AI, Inc.
      16                 :            : 
      17                 :            : /// \file
      18                 :            : /// \brief This file implements 2D pca on a linked list of points to estimate an oriented
      19                 :            : ///        bounding box
      20                 :            : 
      21                 :            : #ifndef GEOMETRY__BOUNDING_BOX__LFIT_HPP_
      22                 :            : #define GEOMETRY__BOUNDING_BOX__LFIT_HPP_
      23                 :            : 
      24                 :            : #include <geometry/bounding_box/eigenbox_2d.hpp>
      25                 :            : #include <limits>
      26                 :            : #include <utility>
      27                 :            : 
      28                 :            : namespace autoware
      29                 :            : {
      30                 :            : namespace common
      31                 :            : {
      32                 :            : namespace geometry
      33                 :            : {
      34                 :            : namespace bounding_box
      35                 :            : {
      36                 :            : namespace details
      37                 :            : {
      38                 :            : 
      39                 :            : /// \brief A representation of the M matrix for the L-fit algorithm
      40                 :            : struct LFitWs
      41                 :            : {
      42                 :            :   /// \brief Number of points in the first partition
      43                 :            :   std::size_t p;
      44                 :            :   /// \brief Number of points in the second partition
      45                 :            :   std::size_t q;
      46                 :            :   // assume matrix of form: [a b; c d]
      47                 :            :   /// \brief Sum of x values in first partition
      48                 :            :   float32_t m12a;
      49                 :            :   /// \brief Sum of y values in first partition
      50                 :            :   float32_t m12b;
      51                 :            :   /// \brief Sum of y values in second partition
      52                 :            :   float32_t m12c;
      53                 :            :   /// \brief Negative sum of x values in second partition
      54                 :            :   float32_t m12d;
      55                 :            :   // m22 is a symmetric matrix
      56                 :            :   /// \brief Sum_p x_2 + sum_q y_2
      57                 :            :   float32_t m22a;
      58                 :            :   /// \brief Sum_p x*y - sum_q x*y
      59                 :            :   float32_t m22b;
      60                 :            :   /// \brief Sum_p y_2 + sum_x y_2
      61                 :            :   float32_t m22d;
      62                 :            : };  // struct LFitWs
      63                 :            : 
      64                 :            : /// \brief Initialize M matrix for L-fit algorithm
      65                 :            : /// \param[in] begin An iterator pointing to the first point in a point list
      66                 :            : /// \param[in] end An iterator one past the last point in the point list
      67                 :            : /// \param[in] size The number of points in the point list
      68                 :            : /// \param[out] ws A representation of the M matrix to get initialized
      69                 :            : /// \tparam IT The iterator type, should dereference to a point type with float members x and y
      70                 :            : template<typename IT>
      71                 :         12 : void init_lfit_ws(const IT begin, const IT end, const std::size_t size, LFitWs & ws)
      72                 :            : {
      73                 :         12 :   ws.p = 1UL;
      74                 :         12 :   ws.q = size - 1UL;
      75                 :            :   // init P terms (first partition)
      76                 :            :   using point_adapter::x_;
      77                 :            :   using point_adapter::y_;
      78                 :         12 :   const auto & pt = *begin;
      79                 :         12 :   const float32_t px = x_(pt);
      80                 :         12 :   const float32_t py = y_(pt);
      81                 :            :   // assume matrix of form: [a b; c d]
      82                 :         12 :   ws.m12a = px;
      83                 :         12 :   ws.m12b = py;
      84                 :         12 :   ws.m12c = 0.0F;
      85                 :         12 :   ws.m12d = 0.0F;
      86                 :            :   // m22 is a symmetric matrix
      87                 :         12 :   ws.m22a = px * px;
      88                 :         12 :   ws.m22b = px * py;
      89                 :         12 :   ws.m22d = py * py;
      90                 :         12 :   auto it = begin;
      91                 :         12 :   ++it;
      92         [ +  + ]:        116 :   for (; it != end; ++it) {
      93                 :        104 :     const auto & qt = *it;
      94                 :        104 :     const float32_t qx = x_(qt);
      95                 :        104 :     const float32_t qy = y_(qt);
      96                 :        104 :     ws.m12c += qy;
      97                 :        104 :     ws.m12d -= qx;
      98                 :        104 :     ws.m22a += qy * qy;
      99                 :        104 :     ws.m22b -= qx * qy;
     100                 :        104 :     ws.m22d += qx * qx;
     101                 :            :   }
     102                 :         12 : }
     103                 :            : 
     104                 :            : /// \brief Solves the L fit problem for a given M matrix
     105                 :            : /// \tparam PointT The point type of the cluster being L-fitted
     106                 :            : /// \param[in] ws A representation of the M Matrix
     107                 :            : /// \param[out] dir The best fit direction for this partition/M matrix
     108                 :            : /// \return The L2 residual for this fit (the score, lower is better)
     109                 :            : template<typename PointT>
     110                 :        104 : float32_t solve_lfit(const LFitWs & ws, PointT & dir)
     111                 :            : {
     112                 :        104 :   const float32_t pi = 1.0F / static_cast<float32_t>(ws.p);
     113                 :        104 :   const float32_t qi = 1.0F / static_cast<float32_t>(ws.q);
     114                 :        104 :   const Covariance2d M{  // matrix of form [x, z; z y]
     115                 :        104 :     ws.m22a - (((ws.m12a * ws.m12a) * pi) + ((ws.m12c * ws.m12c) * qi)),
     116                 :        104 :     ws.m22d - (((ws.m12b * ws.m12b) * pi) + ((ws.m12d * ws.m12d) * qi)),
     117                 :        104 :     ws.m22b - (((ws.m12a * ws.m12b) * pi) + ((ws.m12c * ws.m12d) * qi)),
     118                 :            :     0UL
     119                 :            :   };
     120                 :        104 :   PointT eig1;
     121         [ +  - ]:        104 :   const auto eigv = eig_2d(M, eig1, dir);
     122                 :        104 :   return eigv.second;
     123                 :            : }
     124                 :            : 
     125                 :            : /// \brief Increments L fit M matrix with information in the point
     126                 :            : /// \tparam PointT The point type
     127                 :            : /// \param[in] pt The point to increment the M matrix with
     128                 :            : /// \param[inout] ws A representation of the M matrix
     129                 :            : template<typename PointT>
     130                 :         92 : void increment_lfit_ws(const PointT & pt, LFitWs & ws)
     131                 :            : {
     132                 :         92 :   const float32_t px = point_adapter::x_(pt);
     133                 :         92 :   const float32_t py = point_adapter::y_(pt);
     134                 :         92 :   ws.m12a += px;
     135                 :         92 :   ws.m12b += py;
     136                 :         92 :   ws.m12c -= py;
     137                 :         92 :   ws.m12d += px;
     138                 :         92 :   ws.m22b += 2.0F * px * py;
     139                 :         92 :   const float32_t px2y2 = (px - py) * (px + py);
     140                 :         92 :   ws.m22a += px2y2;
     141                 :         92 :   ws.m22d -= px2y2;
     142                 :         92 : }
     143                 :            : 
     144                 :            : /// \tparam IT An iterator type that should dereference into a point type with float members x and y
     145                 :            : template<typename PointT>
     146                 :            : class LFitCompare
     147                 :            : {
     148                 :            : public:
     149                 :            :   /// \brief Constructor, initializes normal direction
     150                 :            :   /// \param[in] hint A 2d vector with the direction that will be used to order the
     151                 :            :   ///                 point list
     152                 :         12 :   explicit LFitCompare(const PointT & hint)
     153                 :         12 :   : m_nx(point_adapter::x_(hint)),
     154                 :         12 :     m_ny(point_adapter::y_(hint))
     155                 :            :   {
     156                 :         12 :   }
     157                 :            : 
     158                 :            :   /// \brief Comparator operation, returns true if the projection of a the tangent line
     159                 :            :   ///        is smaller than the projection of b
     160                 :            :   /// \param[in] p The first point for comparison
     161                 :            :   /// \param[in] q The second point for comparison
     162                 :            :   /// \return True if a has a smaller projection than b on the tangent line
     163                 :        376 :   bool8_t operator()(const PointT & p, const PointT & q) const
     164                 :            :   {
     165                 :            :     using point_adapter::x_;
     166                 :            :     using point_adapter::y_;
     167                 :        376 :     return ((x_(p) * m_nx) + (y_(p) * m_ny)) < ((x_(q) * m_nx) + (y_(q) * m_ny));
     168                 :            :   }
     169                 :            : 
     170                 :            : private:
     171                 :            :   const float32_t m_nx;
     172                 :            :   const float32_t m_ny;
     173                 :            : };  // class LFitCompare
     174                 :            : 
     175                 :            : /// \brief The main implementation of L-fitting a bounding box to a list of points.
     176                 :            : /// Assumes sufficiently valid, large enough, and appropriately ordered point list
     177                 :            : /// \param[in] begin An iterator pointing to the first point in a point list
     178                 :            : /// \param[in] end An iterator pointing to one past the last point in the point list
     179                 :            : /// \param[in] size The number of points in the point list
     180                 :            : /// \return A bounding box that minimizes the LFit residual
     181                 :            : /// \tparam IT An iterator type dereferencable into a point with float members x and y
     182                 :            : template<typename IT>
     183                 :         12 : BoundingBox lfit_bounding_box_2d_impl(const IT begin, const IT end, const std::size_t size)
     184                 :            : {
     185                 :            :   // initialize M
     186                 :         12 :   LFitWs ws{};
     187                 :         12 :   init_lfit_ws(begin, end, size, ws);
     188                 :            :   // solve initial problem
     189                 :         12 :   details::base_type<decltype(*begin)> best_normal;
     190         [ +  - ]:         12 :   float32_t min_eig = solve_lfit(ws, best_normal);
     191                 :            :   // solve subsequent problems
     192                 :         12 :   auto it = begin;
     193                 :         12 :   ++it;
     194         [ +  - ]:        104 :   for (; it != end; ++it) {
     195                 :            :     // update M
     196                 :        104 :     ws.p += 1U;
     197                 :        104 :     ws.q -= 1U;
     198         [ +  + ]:        104 :     if (ws.q == 0U) {  // checks for q = 0 case
     199                 :         12 :       break;
     200                 :            :     }
     201                 :         92 :     increment_lfit_ws(*it, ws);
     202                 :            :     // solve incremented problem
     203                 :         92 :     decltype(best_normal) dir;
     204         [ +  - ]:         92 :     const float32_t score = solve_lfit(ws, dir);
     205                 :            :     // update optima
     206         [ +  + ]:         92 :     if (score < min_eig) {
     207                 :         22 :       min_eig = score;
     208                 :         22 :       best_normal = dir;
     209                 :            :     }
     210                 :            :   }
     211                 :            :   // can recover best corner point, but don't care, need to cover all points
     212                 :         12 :   const auto inorm = 1.0F / norm_2d(best_normal);
     213         [ -  + ]:         12 :   if (!std::isnormal(inorm)) {
     214         [ #  # ]:          0 :     throw std::runtime_error{"LFit: Abnormal norm"};
     215                 :            :   }
     216                 :         12 :   best_normal = times_2d(best_normal, inorm);
     217                 :         12 :   auto best_tangent = get_normal(best_normal);
     218                 :            :   // find extreme points
     219         [ +  + ]:         36 :   Point4<IT> supports;
     220                 :         12 :   const bool8_t is_ccw = details::compute_supports(begin, end, best_normal, best_tangent, supports);
     221         [ +  - ]:         12 :   if (is_ccw) {
     222                 :         12 :     std::swap(best_normal, best_tangent);
     223                 :            :   }
     224         [ +  - ]:         12 :   BoundingBox bbox = details::compute_bounding_box(best_normal, best_tangent, supports);
     225                 :         12 :   bbox.value = min_eig;
     226                 :            : 
     227                 :         24 :   return bbox;
     228                 :            : }
     229                 :            : }  // namespace details
     230                 :            : 
     231                 :            : /// \brief Compute bounding box which best fits an L-shaped cluster. Uses the method proposed
     232                 :            : ///        in "Efficient L-shape fitting of laser scanner data for vehicle pose estimation"
     233                 :            : /// \return An oriented bounding box in x-y. This bounding box has no height information
     234                 :            : /// \param[in] begin An iterator pointing to the first point in a point list
     235                 :            : /// \param[in] end An iterator pointing to one past the last point in the point list
     236                 :            : /// \param[in] size The number of points in the point list
     237                 :            : /// \param[in] hint An iterator pointing to the point whose normal will be used to sort the list
     238                 :            : /// \return A pair where the first element is an iterator pointing to the nearest point, and the
     239                 :            : ///         second element is the size of the list
     240                 :            : /// \tparam IT An iterator type dereferencable into a point with float members x and y
     241                 :            : /// \throw std::domain_error If the number of points is too few
     242                 :            : template<typename IT, typename PointT>
     243                 :         12 : BoundingBox lfit_bounding_box_2d(
     244                 :            :   const IT begin,
     245                 :            :   const IT end,
     246                 :            :   const PointT hint,
     247                 :            :   const std::size_t size)
     248                 :            : {
     249         [ -  + ]:         12 :   if (size <= 1U) {
     250         [ #  # ]:          0 :     throw std::domain_error("LFit requires >= 2 points!");
     251                 :            :   }
     252                 :            :   // NOTE: assumes points are in base_link/sensor frame!
     253                 :            :   // sort along tangent wrt sensor origin
     254                 :            :   //lint -e522 NOLINT Not a pure function: data structure iterators are pointing to is modified
     255         [ +  - ]:         12 :   std::partial_sort(begin, end, end, details::LFitCompare<PointT>{hint});
     256                 :            : 
     257                 :         12 :   return details::lfit_bounding_box_2d_impl(begin, end, size);
     258                 :            : }
     259                 :            : 
     260                 :            : /// \brief Compute bounding box which best fits an L-shaped cluster. Uses the method proposed
     261                 :            : ///        in "Efficient L-shape fitting of laser scanner data for vehicle pose estimation".
     262                 :            : /// This implementation sorts the list using std::sort
     263                 :            : /// \return An oriented bounding box in x-y. This bounding box has no height information
     264                 :            : /// \param[in] begin An iterator pointing to the first point in a point list
     265                 :            : /// \param[in] end An iterator pointing to one past the last point in the point list
     266                 :            : /// \tparam IT An iterator type dereferencable into a point with float members x and y
     267                 :            : template<typename IT>
     268                 :         12 : BoundingBox lfit_bounding_box_2d(const IT begin, const IT end)
     269                 :            : {
     270                 :            :   // use the principal component as the sorting direction
     271                 :         12 :   const auto cov = details::covariance_2d(begin, end);
     272                 :            :   using PointT = details::base_type<decltype(*begin)>;
     273                 :         12 :   PointT eig1;
     274                 :         12 :   PointT eig2;
     275         [ +  - ]:         12 :   (void)details::eig_2d(cov, eig1, eig2);
     276                 :            :   (void)eig2;
     277         [ +  - ]:         24 :   return lfit_bounding_box_2d(begin, end, eig1, cov.num_points);
     278                 :            : }
     279                 :            : }  // namespace bounding_box
     280                 :            : }  // namespace geometry
     281                 :            : }  // namespace common
     282                 :            : }  // namespace autoware
     283                 :            : 
     284                 :            : #endif  // GEOMETRY__BOUNDING_BOX__LFIT_HPP_

Generated by: LCOV version 1.14