GeneratorEdgesFromRangeImage.cpp
Go to the documentation of this file.
1 /* -------------------------------------------------------------------------
2  * A repertory of multi primitive-to-primitive (MP2P) ICP algorithms in C++
3  * Copyright (C) 2018-2024 Jose Luis Blanco, University of Almeria
4  * See LICENSE for license information.
5  * ------------------------------------------------------------------------- */
15 #include <mrpt/containers/yaml.h>
16 #include <mrpt/maps/CSimplePointsMap.h>
17 #include <mrpt/math/utils.h> // absDiff()
18 #include <mrpt/obs/CObservation3DRangeScan.h>
19 #include <mrpt/obs/CObservationRotatingScan.h>
20 #include <mrpt/version.h>
21 
22 #include <utility> // std::pair
23 
25 
26 using namespace mp2p_icp_filters;
27 
28 namespace
29 {
30 // Computes the mean and variance of a number of integer samples,
31 // using fixed-point integers for efficiency and avoiding float numbers.
32 auto calcStats(const int64_t* data, const size_t N)
33  -> std::pair<int64_t /*mean*/, int64_t /*stdDev*/>
34 {
35  ASSERT_(N > 1);
36 
37  int64_t sumMean = 0;
38  for (size_t i = 0; i < N; i++) sumMean += data[i];
39 
40  const int64_t mean = sumMean / (N - 1);
41 
42  int64_t sumVariance = 0;
43  for (size_t i = 0; i < N; i++)
44  sumVariance += mrpt::square(mrpt::math::absDiff<int64_t>(data[i], mean));
45 
46  const int64_t variance = sumVariance / (N - 1);
47 
48  return {mean, variance};
49 }
50 
51 } // namespace
52 
54 {
55  MCP_LOAD_REQ(c, planes_target_layer);
56  MCP_LOAD_REQ(c, score_threshold);
57 }
58 
59 void GeneratorEdgesFromRangeImage::initialize(const mrpt::containers::yaml& c)
60 {
61  // parent base method:
63 
65 }
66 
68  const mrpt::obs::CObservationRotatingScan& pc, mp2p_icp::metric_map_t& out,
69  const std::optional<mrpt::poses::CPose3D>& robotPose) const
70 {
71 #if MRPT_VERSION >= 0x020b04
72  constexpr int FIXED_POINT_BITS = 8;
73 
74  auto outPc = mrpt::maps::CSimplePointsMap::Create();
75 
76  ASSERT_(!pc.organizedPoints.empty());
77 
78  const auto nRows = pc.rowCount;
79  const auto nCols = pc.columnCount;
80 
81  ASSERT_EQUAL_(nRows, pc.organizedPoints.rows());
82  ASSERT_EQUAL_(nCols, pc.organizedPoints.cols());
83 
84  ASSERT_EQUAL_(nRows, pc.rangeImage.rows());
85  ASSERT_EQUAL_(nCols, pc.rangeImage.cols());
86 
87  constexpr unsigned int BLOCK_BITS = 3;
88  constexpr unsigned int W = 1 << BLOCK_BITS;
89 
90  std::vector<int64_t> rowRangeDiff;
91 
92  // for each row:
93  for (size_t r = 0; r < nRows; r++)
94  {
95  rowRangeDiff.assign(nCols, 0);
96 
97  // compute range diff:
98  for (size_t i = 1; i < nCols; i++)
99  {
100  rowRangeDiff[i] = (static_cast<int64_t>(pc.rangeImage(r, i)) -
101  static_cast<int64_t>(pc.rangeImage(r, i - 1)))
102  << FIXED_POINT_BITS;
103  }
104 
105  for (size_t i = 1 + W; i < nCols - W; i++)
106  {
107  // filtered range diff (in fixed-point arithmetic)
108  const auto [rdFiltered, rdVar] = calcStats(&rowRangeDiff[i - W], 1 + 2 * W);
109 
110  if (rdVar == 0) continue; // by no way this is an edge! avoid x/0
111 
112  // significance of each point (in fixed-point arithmetic)
113  const int64_t riFixPt = pc.rangeImage(r, i) << FIXED_POINT_BITS;
114  int64_t scoreSqrFixPt = mrpt::square(mrpt::math::absDiff(riFixPt, rdFiltered)) / rdVar;
115 
116  const int32_t scoreSqr = scoreSqrFixPt >> (2 * FIXED_POINT_BITS);
117 
118  if (scoreSqr > paramsEdges_.score_threshold)
119  {
120  // this point passes:
121  if (robotPose)
122  outPc->insertPoint(robotPose->composePoint(pc.organizedPoints(r, i)));
123  else
124  outPc->insertPoint(pc.organizedPoints(r, i));
125  }
126  }
127 
128  } // end for each row
129 
130  out.layers[params_.target_layer] = outPc;
131  return true; // Yes, it's implemented
132 #else
133  THROW_EXCEPTION("This class requires MRPT >=v2.11.4");
134 #endif
135 }
136 
138  const mrpt::obs::CObservation3DRangeScan& rgbd, mp2p_icp::metric_map_t& out,
139  const std::optional<mrpt::poses::CPose3D>& robotPose) const
140 {
141  constexpr int FIXED_POINT_BITS = 8;
142 
143  // Optional output layer for deleted points:
144  mrpt::maps::CPointsMap::Ptr outEdges = GetOrCreatePointLayer(
145  out, params_.target_layer, true /*allow empty for nullptr*/,
146  /* create cloud of the same type */
147  "mrpt::maps::CSimplePointsMap");
148 
149  mrpt::maps::CPointsMap::Ptr outPlanes = GetOrCreatePointLayer(
150  out, paramsEdges_.planes_target_layer, true /*allow empty for nullptr*/,
151  /* create cloud of the same type */
152  "mrpt::maps::CSimplePointsMap");
153 
154  if (outEdges) out.layers[params_.target_layer] = outEdges;
155  if (outPlanes) out.layers[paramsEdges_.planes_target_layer] = outEdges;
156  ASSERT_(outEdges || outPlanes);
157 
158  ASSERT_(rgbd.hasRangeImage);
159 
160  if (rgbd.rangeImage_isExternallyStored()) rgbd.load();
161 
162  // range is: CMatrix_u16. Zeros are invalid pixels.
163  const auto nRows = rgbd.rangeImage.rows();
164  const auto nCols = rgbd.rangeImage.cols();
165 
166  // Decimate range image, removing zeros:
167  constexpr unsigned int BLOCK_BITS = 3;
168  constexpr unsigned int BLOCKS = 1 << BLOCK_BITS;
169 
170  const mrpt::math::CMatrix_u16& ri = rgbd.rangeImage;
171  const auto nRowsDecim = nRows >> BLOCK_BITS;
172  const auto nColsDecim = nCols >> BLOCK_BITS;
173 
174  mrpt::math::CMatrix_u16 R(nRowsDecim, nColsDecim);
175  R.fill(0);
176  for (int rd = 0; rd < nRowsDecim; rd++)
177  {
178  for (int cd = 0; cd < nColsDecim; cd++)
179  {
180  size_t count = 0;
181  uint32_t sum = 0;
182  for (unsigned int i = 0; i < BLOCKS; i++)
183  {
184  for (unsigned int j = 0; j < BLOCKS; j++)
185  {
186  const auto val = ri((rd << BLOCK_BITS) + i, (cd << BLOCK_BITS) + j);
187  if (!val) continue;
188  count++;
189  sum += val;
190  }
191  }
192  if (count) R(rd, cd) = sum / count;
193  }
194  }
195 
196  std::vector<int64_t> rowRangeDiff, rowRangeDiff2;
197 
198  const size_t WH = nRows * nCols;
199  const auto& lut = rgbd.get_unproj_lut();
200 
201  // Select between coordinates wrt the robot/vehicle, or local wrt sensor:
202  const auto& Kxs = lut.Kxs_rot;
203  const auto& Kys = lut.Kys_rot;
204  const auto& Kzs = lut.Kzs_rot;
205 
206  ASSERT_EQUAL_(WH, size_t(Kxs.size()));
207  ASSERT_EQUAL_(WH, size_t(Kys.size()));
208  ASSERT_EQUAL_(WH, size_t(Kzs.size()));
209  const float* kxs = &Kxs[0];
210  const float* kys = &Kys[0];
211  const float* kzs = &Kzs[0];
212 
213  const auto sensorTranslation = rgbd.sensorPose.translation();
214 
215  const int MIN_SPACE_BETWEEN_PLANE_POINTS = nColsDecim / 16;
216 
217  auto lambdaUnprojectPoint = [&](const int rd, const int cd)
218  {
219  // unproject range -> 3D:
220  const float D = R(rd, cd) * rgbd.rangeUnits;
221  // LUT projection coefs:
222  const int r = rd * BLOCKS + BLOCKS / 2;
223  const int c = cd * BLOCKS + BLOCKS / 2;
224 
225  const auto kx = kxs[r * nCols + c], ky = kys[r * nCols + c], kz = kzs[r * nCols + c];
226 
227  // unproject range -> 3D (includes sensorPose rotation):
228  auto pt = mrpt::math::TPoint3Df(kx * D, ky * D /*y*/, kz * D /*z*/);
229  pt += sensorTranslation;
230 
231  if (robotPose) pt = robotPose->composePoint(pt);
232 
233  return pt;
234  };
235 
236  // analize each row:
237  for (int rd = 0; rd < nRowsDecim; rd++)
238  {
239  rowRangeDiff.assign(nColsDecim, 0);
240  rowRangeDiff2.assign(nColsDecim, 0);
241 
242  // compute range diff:
243  for (int cd = 1; cd < nColsDecim; cd++)
244  {
245  if (!R(rd, cd) || !R(rd, cd - 1)) continue; // ignore invalid pts
246 
247  rowRangeDiff[cd] =
248  (static_cast<int64_t>(R(rd, cd)) - static_cast<int64_t>(R(rd, cd - 1)))
249  << FIXED_POINT_BITS;
250  }
251  for (int cd = 1; cd < nColsDecim; cd++)
252  rowRangeDiff2[cd] = rowRangeDiff[cd] - rowRangeDiff[cd - 1];
253 
254  // filtered range diff (in fixed-point arithmetic)
255  const auto [rdMean, rdVar] = calcStats(rowRangeDiff2.data(), rowRangeDiff2.size());
256 
257  std::optional<int> currentPlaneStart;
258 
259  for (int cd = 1; cd < nColsDecim; cd++)
260  {
261  if (!R(rd, cd))
262  {
263  // invalid range here, stop.
264  currentPlaneStart.reset();
265  continue;
266  }
267 
268  int64_t scoreSqr = rdVar != 0 ? mrpt::square(rowRangeDiff2[cd]) / rdVar : 0;
269 
270  if (scoreSqr > paramsEdges_.score_threshold)
271  {
272  // it's an edge:
273  currentPlaneStart.reset();
274 
275  outEdges->insertPoint(lambdaUnprojectPoint(rd, cd));
276  }
277  else
278  {
279  // looks like a plane:
280  if (!currentPlaneStart) currentPlaneStart = cd;
281 
282  if (cd - *currentPlaneStart > MIN_SPACE_BETWEEN_PLANE_POINTS)
283  {
284  // create a plane point:
285  outPlanes->insertPoint(lambdaUnprojectPoint(rd, cd));
286  currentPlaneStart.reset();
287  }
288  }
289  }
290  } // end for each row
291 
292  return true;
293 }
mp2p_icp_filters::GeneratorEdgesFromRangeImage::paramsEdges_
ParametersEdges paramsEdges_
Definition: GeneratorEdgesFromRangeImage.h:49
mp2p_icp_filters::Generator::initialize
virtual void initialize(const mrpt::containers::yaml &cfg_block)
Definition: Generator.cpp:120
mp2p_icp_filters::GeneratorEdgesFromRangeImage::ParametersEdges::load_from_yaml
void load_from_yaml(const mrpt::containers::yaml &c)
Definition: GeneratorEdgesFromRangeImage.cpp:53
mp2p_icp_filters::Generator
Definition: Generator.h:71
mp2p_icp_filters::GeneratorEdgesFromRangeImage::filterScan3D
bool filterScan3D(const mrpt::obs::CObservation3DRangeScan &pc, mp2p_icp::metric_map_t &out, const std::optional< mrpt::poses::CPose3D > &robotPose) const override
Definition: GeneratorEdgesFromRangeImage.cpp:137
mp2p_icp_filters::GeneratorEdgesFromRangeImage::initialize
void initialize(const mrpt::containers::yaml &cfg_block) override
Definition: GeneratorEdgesFromRangeImage.cpp:59
GeneratorEdgesFromRangeImage.h
Generator of edge points from organized point clouds.
mp2p_icp_filters::Generator::params_
Parameters params_
Definition: Generator.h:146
mp2p_icp_filters::GeneratorEdgesFromRangeImage
Definition: GeneratorEdgesFromRangeImage.h:25
IMPLEMENTS_MRPT_OBJECT
IMPLEMENTS_MRPT_OBJECT(FilterDecimateVoxelsQuadratic, mp2p_icp_filters::FilterBase, mp2p_icp_filters) using namespace mp2p_icp_filters
mp2p_icp_filters::GeneratorEdgesFromRangeImage::ParametersEdges::planes_target_layer
std::string planes_target_layer
Definition: GeneratorEdgesFromRangeImage.h:44
mp2p_icp_filters::GeneratorEdgesFromRangeImage::filterRotatingScan
bool filterRotatingScan(const mrpt::obs::CObservationRotatingScan &pc, mp2p_icp::metric_map_t &out, const std::optional< mrpt::poses::CPose3D > &robotPose) const override
Definition: GeneratorEdgesFromRangeImage.cpp:67
kitti-batch-convert.out
string out
Definition: kitti-batch-convert.py:7
mp2p_icp_filters::Generator::Parameters::target_layer
std::string target_layer
Definition: Generator.h:104
mp2p_icp_filters::GetOrCreatePointLayer
mrpt::maps::CPointsMap::Ptr GetOrCreatePointLayer(mp2p_icp::metric_map_t &m, const std::string &layerName, bool allowEmptyName=true, const std::string &classForLayerCreation="mrpt::maps::CSimplePointsMap")
Definition: GetOrCreatePointLayer.cpp:15
boost::posix_time
GetOrCreatePointLayer.h
Auxiliary function GetOrCreatePointLayer.
mp2p_icp::metric_map_t
Generic container of pointcloud(s), extracted features and other maps.
Definition: metricmap.h:55
mp2p_icp_filters::GeneratorEdgesFromRangeImage::ParametersEdges::score_threshold
int32_t score_threshold
Definition: GeneratorEdgesFromRangeImage.h:46
mp2p_icp_filters
Definition: FilterAdjustTimestamps.h:19


mp2p_icp
Author(s):
autogenerated on Mon May 26 2025 02:45:48