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