-
Notifications
You must be signed in to change notification settings - Fork 35
Extending likelihood field to model unexplored spaces #430
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0e499ad
848ec94
98ef5e8
06dc8cc
be9c0d9
3ad7d35
287453b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| // Copyright 2024 Ekumen, Inc. | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| #ifndef BELUGA_ACTIONS_OVERLAY_HPP | ||
| #define BELUGA_ACTIONS_OVERLAY_HPP | ||
|
|
||
| #include <algorithm> | ||
| #include <execution> | ||
|
|
||
| #include <range/v3/action/action.hpp> | ||
| #include <range/v3/view/common.hpp> | ||
| #include <range/v3/view/transform.hpp> | ||
|
|
||
| /** | ||
| * \file | ||
| * \brief Implementation of the overlay range adaptor object | ||
| */ | ||
| namespace beluga::actions { | ||
|
|
||
| namespace detail { | ||
|
|
||
| /// Implementation detail for an overlay range adaptor object. | ||
| struct overlay_base_fn { | ||
| /// Overload that implements an overlay of a value in a range. | ||
| /** | ||
| * \tparam ExecutionPolicy An [execution policy](https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t). | ||
| * \tparam Range An [input range](https://en.cppreference.com/w/cpp/ranges/input_range). | ||
| * \tparam MaskRange An [input range](https://en.cppreference.com/w/cpp/ranges/input_range). | ||
| * \param policy The execution policy to use. | ||
| * \param range An existing range to apply this action to. | ||
| * \param mask The mask where the values will be overlaid. | ||
| * \param mask_value The value to be overlaid. | ||
| */ | ||
| template < | ||
| class ExecutionPolicy, | ||
| class Range, | ||
| class MaskRange, | ||
| class Mask, | ||
| std::enable_if_t<std::is_execution_policy_v<std::decay_t<ExecutionPolicy>>, int> = 0, | ||
| std::enable_if_t<ranges::range<Range>, int> = 0, | ||
| std::enable_if_t<ranges::range<MaskRange>, int> = 0> | ||
| constexpr auto operator()(ExecutionPolicy&& policy, Range& range, MaskRange&& mask, Mask&& mask_value) const | ||
| -> Range& { | ||
| auto map = range | ranges::views::common; | ||
| const auto converted_mask_value = static_cast<ranges::range_value_t<Range>>(mask_value); | ||
|
|
||
|
nahueespinosa marked this conversation as resolved.
|
||
| std::transform( | ||
| policy, // | ||
| std::begin(map), // | ||
| std::end(map), // | ||
| std::begin(mask), // | ||
| std::begin(map), // | ||
| [&converted_mask_value](const auto& base_value, bool flag) { | ||
| return flag ? converted_mask_value : base_value; | ||
| }); | ||
|
|
||
| return range; | ||
| } | ||
|
|
||
| /// Overload that re-orders arguments from an action closure. | ||
| template < | ||
| class ExecutionPolicy, | ||
| class Range, | ||
| class MaskRange, | ||
| class Mask, | ||
| std::enable_if_t<std::is_execution_policy_v<ExecutionPolicy>, int> = 0, | ||
| std::enable_if_t<ranges::range<Range>, int> = 0, | ||
| std::enable_if_t<ranges::range<MaskRange>, int> = 0> | ||
| constexpr auto operator()(Range&& range, MaskRange&& mask, Mask&& mask_value, ExecutionPolicy policy) const | ||
| -> Range& { | ||
| return (*this)( | ||
| std::move(policy), std::forward<Range>(range), std::forward<MaskRange>(mask), std::forward<Mask>(mask_value)); | ||
| } | ||
|
|
||
| /// Overload that returns an action closure to compose with other actions. | ||
| template < | ||
| class ExecutionPolicy, | ||
| class MaskRange, | ||
| class Mask, | ||
| std::enable_if_t<std::is_execution_policy_v<ExecutionPolicy>, int> = 0, | ||
| std::enable_if_t<ranges::range<MaskRange>, int> = 0> | ||
| constexpr auto operator()(ExecutionPolicy policy, MaskRange&& mask, Mask&& mask_value) const { | ||
| return ranges::make_action_closure(ranges::bind_back( | ||
| overlay_base_fn{}, std::forward<MaskRange>(mask), std::forward<Mask>(mask_value), std::move(policy))); | ||
| } | ||
| }; | ||
|
|
||
| /// Implementation detail for an overlay range adaptor object with a default execution policy. | ||
| struct overlay_fn : public overlay_base_fn { | ||
| using overlay_base_fn::operator(); | ||
|
|
||
| /// Overload that defines a default execution policy. | ||
| template < | ||
| class Range, | ||
| class MaskRange, | ||
| class Mask, | ||
| std::enable_if_t<ranges::range<Range>, int> = 0, | ||
| std::enable_if_t<ranges::range<MaskRange>, int> = 0> | ||
| constexpr auto operator()(Range&& range, MaskRange&& mask, Mask&& mask_value) const -> Range& { | ||
| return (*this)( | ||
| std::execution::seq, std::forward<Range>(range), std::forward<MaskRange>(mask), std::forward<Mask>(mask_value)); | ||
| } | ||
|
|
||
| /// Overload that returns an action closure to compose with other actions. | ||
| template <class MaskRange, class Mask, std::enable_if_t<ranges::range<MaskRange>, int> = 0> | ||
| constexpr auto operator()(MaskRange&& mask, Mask&& mask_value) const { | ||
| return ranges::make_action_closure( | ||
| ranges::bind_back(overlay_fn{}, std::forward<MaskRange>(mask), std::forward<Mask>(mask_value))); | ||
| } | ||
| }; | ||
|
|
||
| } // namespace detail | ||
|
|
||
| /// [Range adaptor object](https://en.cppreference.com/w/cpp/named_req/RangeAdaptorObject) that | ||
| /// can overlay a range of values (or a range of particles). | ||
| /** | ||
| * The `overlay` range adaptor allows to overlay the values of the range that match a mask. | ||
| * All the values are overlaid for a given value. | ||
| */ | ||
| inline constexpr ranges::actions::action_closure<detail::overlay_fn> overlay; | ||
| } // namespace beluga::actions | ||
|
|
||
| #endif | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,9 +20,11 @@ | |
| #include <random> | ||
| #include <vector> | ||
|
|
||
| #include <beluga/actions/overlay.hpp> | ||
| #include <beluga/algorithm/distance_map.hpp> | ||
| #include <beluga/sensor/data/occupancy_grid.hpp> | ||
| #include <beluga/sensor/data/value_grid.hpp> | ||
| #include <range/v3/action/transform.hpp> | ||
| #include <range/v3/range/conversion.hpp> | ||
| #include <range/v3/view/all.hpp> | ||
| #include <range/v3/view/transform.hpp> | ||
|
|
@@ -58,6 +60,8 @@ struct LikelihoodFieldModelParam { | |
| * Used to calculate the probability of the obstacle being hit. | ||
| */ | ||
| double sigma_hit = 0.2; | ||
| /// Whether to model unknown space or assume it free. | ||
| bool model_unknown_space = false; | ||
| }; | ||
|
|
||
| /// Likelihood field sensor model for range finders. | ||
|
|
@@ -161,23 +165,35 @@ class LikelihoodFieldModel { | |
| return std::min(squared_distance, squared_max_distance); | ||
| }; | ||
|
|
||
| const auto to_likelihood = [amplitude = | ||
| params.z_hit / (params.sigma_hit * std::sqrt(2 * Sophus::Constants<double>::pi())), | ||
| two_squared_sigma = 2 * params.sigma_hit * params.sigma_hit, | ||
| offset = params.z_random / params.max_laser_distance](double squared_distance) { | ||
| assert(two_squared_sigma > 0.0); | ||
| assert(amplitude > 0.0); | ||
| /// Pre-computed variables | ||
| const double two_squared_sigma = 2 * params.sigma_hit * params.sigma_hit; | ||
| assert(two_squared_sigma > 0.0); | ||
|
|
||
| const double amplitude = params.z_hit / (params.sigma_hit * std::sqrt(2 * Sophus::Constants<double>::pi())); | ||
| assert(amplitude > 0.0); | ||
|
|
||
| const double offset = params.z_random / params.max_laser_distance; | ||
|
|
||
| const auto to_likelihood = [amplitude, two_squared_sigma, offset](double squared_distance) { | ||
| return amplitude * std::exp(-squared_distance / two_squared_sigma) + offset; | ||
| }; | ||
|
|
||
| const auto neighborhood = [&grid](std::size_t index) { return grid.neighborhood4(index); }; | ||
|
|
||
| // determine distances to obstacles and calculate likelihood values in-place | ||
| // to minimize memory usage when dealing with large maps | ||
| auto likelihood_values = nearest_obstacle_distance_map(grid.obstacle_data(), squared_distance, neighborhood); | ||
| std::transform( | ||
| likelihood_values.begin(), likelihood_values.end(), likelihood_values.begin(), truncate_to_max_distance); | ||
| std::transform(likelihood_values.begin(), likelihood_values.end(), likelihood_values.begin(), to_likelihood); | ||
| auto distance_map = nearest_obstacle_distance_map(grid.obstacle_mask(), squared_distance, neighborhood); | ||
|
|
||
| if (params.model_unknown_space) { | ||
| const double inverse_max_distance = 1 / params.max_laser_distance; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @DPR00 @hidmic I'm going through this code to help rodrigo with #577, but I can't fully make sense of this. After a while of thinking this go a long way around assigning So my question is: why
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That. IIUC the idea is that the probability of a hit for any given measurement is uniform: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that's right, and I explain why. The problem is that that causes the likelihood map to implement different likelihood functions depending on the lidar happens to hit, causing non-uniform weight scales. Depending on the lidar range the likelihood might be higher through an open window than 20cm away from an actually mapped obstacle.
I think that not mixing assumes a knowledge we don't have. It asummes we know with 100% certainty that the measurement IS a random hit, which we can't know. Where a hit falls does not depend as much on the measurement as much as on the hyphotesis/particle you are tracing it from, so a hit on unknown area behind a wall surface or through a window most likely just means a hyphotesis which is 5 cm closer to the wall than the actual pose of the lidar. That hypothesis is not wrong, it's just part of the internal representation of the distribution that the filter uses for the pose. If we assign If we assign a weight Notice that while I think that using That's because the likelihood model only really models known obstacles; more than a probabilistic model, LF is a "scan correlator". In fact, the In ignoring the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hmm, but random hit != unknown hit. Random hits have lower probability because it is implied they are transient. Thrun mentions cross-talk and echoes as potential sources. That said...
Yeah, I see the problem now 馃 (stream of consciousness ahead) IIUC we are folding both unobservable and unmapped as unknown, and we are overpowering the base likelihood model when we shouldn't. Assigning a higher likelihood to hits within internal walls or past external walls just gives us bias. Assigning a higher likelihood to hits within an unmapped but otherwise visible region does prevent us from excessively penalizing hypothesis close to such regions when those regions happen to be occupied. If the unmapped space is within a room that had its door closed during mapping, we do want hits within that unexplored space not be ruled out as highly unlikely. Hmm, what if we let the user configure a distance threshold? Unexplored space that is within that distance from occupied cells decays normally, while unexplored space past that distance gets the truncated likelihood to signal we don't really know if a hit in that region is likely or not. We cover for the variance in the estimate while not (excessively) penalizing hitting a car parked out the window.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I created #582 to further discuss this. |
||
| const double background_distance = -two_squared_sigma * std::log((inverse_max_distance - offset) / amplitude); | ||
|
|
||
| distance_map |= beluga::actions::overlay(grid.unknown_mask(), background_distance); | ||
|
DPR00 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| auto likelihood_values = std::move(distance_map) | // | ||
| ranges::actions::transform(truncate_to_max_distance) | // | ||
| ranges::actions::transform(to_likelihood); | ||
|
|
||
| return ValueGrid2<float>{std::move(likelihood_values), grid.width(), grid.resolution()}; | ||
| } | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.