pcod-common v1.0.0
Shared preprocessing and postprocessing for point-cloud object detection
Loading...
Searching...
No Matches
pbod_postprocess.cpp
Go to the documentation of this file.
1// Copyright Institute for Automotive Engineering (ika), RWTH Aachen University
2// SPDX-License-Identifier: Apache-2.0
3
5
6#include <algorithm>
7#include <cmath>
8#include <stdexcept>
9
10#include "pcod_common/math.hpp"
11
12namespace pcod_common {
13
14namespace {
15inline float sigmoid(float x) { return 1.0F / (1.0F + std::exp(-x)); }
16
17std::vector<float> CopyTensorView(const float* values, std::size_t count) {
18 std::vector<float> copy(count);
19 std::copy_n(values, count, copy.begin());
20 return copy;
21}
22} // namespace
23
24std::vector<BoundingBox> DecodePbod(const PbodOutputsView& outputs, const PillarGrid& grid, const PbodPostprocessConfig& config) {
25 std::vector<BoundingBox> objects;
26 if (outputs.focal_logits == nullptr || outputs.size_posterior == nullptr || outputs.class_logits == nullptr ||
27 outputs.reg_logits == nullptr) {
28 throw std::invalid_argument("DecodePbod requires non-null output tensor pointers.");
29 }
30 if (outputs.num_pillars <= 0 || outputs.num_classes <= 0) {
31 return objects;
32 }
33 if (outputs.reg_dim > 0 && outputs.reg_dim < 7) {
34 throw std::invalid_argument("DecodePbod requires reg_dim >= 7.");
35 }
36 if (grid.centers.size() < static_cast<std::size_t>(outputs.num_pillars) * 3) {
37 throw std::invalid_argument("DecodePbod received fewer pillar centers than num_pillars.");
38 }
39
40 const int reg_dim = outputs.reg_dim > 0 ? outputs.reg_dim : 7;
41 const std::size_t num_pillars = static_cast<std::size_t>(outputs.num_pillars);
42 const std::size_t num_classes = static_cast<std::size_t>(outputs.num_classes);
43 const std::size_t reg_dim_size = static_cast<std::size_t>(reg_dim);
44
45 const std::vector<float> focal_logits = CopyTensorView(outputs.focal_logits, num_pillars);
46 const std::vector<float> size_posterior = CopyTensorView(outputs.size_posterior, num_pillars * num_classes * 3U);
47 const std::vector<float> class_logits = CopyTensorView(outputs.class_logits, num_pillars * num_classes);
48 const std::vector<float> reg_logits = CopyTensorView(outputs.reg_logits, num_pillars * num_classes * reg_dim_size);
49
50 objects.reserve(static_cast<std::size_t>(outputs.num_pillars));
51 for (int idx = 0; idx < outputs.num_pillars; ++idx) {
52 const std::size_t pillar_idx = static_cast<std::size_t>(idx);
53 const float score = sigmoid(focal_logits[pillar_idx]);
54
55 int best_class = 0;
56 const std::size_t class_base = pillar_idx * num_classes;
57 float best_logit = class_logits[class_base];
58 for (int c = 1; c < outputs.num_classes; ++c) {
59 float logit = class_logits[class_base + static_cast<std::size_t>(c)];
60 if (logit > best_logit) {
61 best_logit = logit;
62 best_class = c;
63 }
64 }
65
66 float score_thresh = 0.0F;
67 if (!config.score_thresholds.empty()) {
68 const std::size_t class_idx = static_cast<std::size_t>(best_class);
69 score_thresh =
70 class_idx < config.score_thresholds.size() ? config.score_thresholds[class_idx] : config.score_thresholds.front();
71 }
72
73 if (score < score_thresh) {
74 continue;
75 }
76
77 BoundingBox box;
78 const std::size_t class_idx = static_cast<std::size_t>(best_class);
79 const std::size_t size_offset = (pillar_idx * num_classes + class_idx) * 3U;
80 const std::size_t reg_offset = (pillar_idx * num_classes + class_idx) * reg_dim_size;
81 const std::size_t center_offset = pillar_idx * 3U;
82
83 box.length = std::exp(reg_logits[reg_offset + 3U]) * size_posterior[size_offset + 0U];
84 box.width = std::exp(reg_logits[reg_offset + 4U]) * size_posterior[size_offset + 1U];
85 box.height = std::exp(reg_logits[reg_offset + 5U]) * size_posterior[size_offset + 2U];
86 box.center[0] = reg_logits[reg_offset + 0U] * size_posterior[size_offset + 0U] + grid.centers[center_offset + 0U];
87 box.center[1] = reg_logits[reg_offset + 1U] * size_posterior[size_offset + 1U] + grid.centers[center_offset + 1U];
88 box.z = reg_logits[reg_offset + 2U] * size_posterior[size_offset + 2U] + grid.centers[center_offset + 2U];
89 box.yaw = wrap_to_range(reg_logits[reg_offset + 6U], -static_cast<float>(M_PI), static_cast<float>(M_PI));
90 if (std::isnan(box.length) || std::isnan(box.width) || std::isnan(box.height) || std::isnan(box.yaw)) {
91 continue;
92 }
93
94 for (int c = 0; c < outputs.num_classes; ++c) {
95 const std::size_t current_class = static_cast<std::size_t>(c);
96 box.classification.push_back({current_class, class_logits[class_base + current_class]});
97 }
98 box.existence_probability = score;
99
100 objects.push_back(std::move(box));
101 }
102
103 return objects;
104}
105
106} // namespace pcod_common
std::vector< BoundingBox > DecodePbod(const PbodOutputsView &outputs, const PillarGrid &grid, const PbodPostprocessConfig &config)
float wrap_to_range(float val, float min_val, float max_val)
Definition math.hpp:32
float length
Length along the local X axis.
std::array< float, 2 > center
XY center in metres.
float height
Height along the Z axis.
float z
Z center in metres.
float width
Width along the local Y axis.
std::vector< ClassificationEntry > classification
Ranked semantic predictions.
float existence_probability
Detection confidence.
float yaw
Heading in radians.
const float * focal_logits
Shape [num_pillars].
int num_classes
Number of semantic classes.
int reg_dim
Regression values per pillar and class.
int num_pillars
Number of spatial pillars.
const float * class_logits
Shape [num_pillars, num_classes].
const float * size_posterior
Shape [num_pillars, num_classes * 3].
const float * reg_logits
Shape [num_pillars, num_classes * reg_dim].
std::vector< float > score_thresholds
Per-class thresholds, or one shared threshold.
std::vector< float > centers
Flat center array with shape [num_pillars, 3].