Skip to content

Commit

Permalink
Merge pull request #4311 from kunaltyagi/pr/4275
Browse files Browse the repository at this point in the history
  • Loading branch information
kunaltyagi authored Aug 7, 2020
2 parents 9f980d7 + 3bd2de2 commit c440618
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ pcl::OrganizedEdgeBase<PointT, PointLT>::extractEdges (pcl::PointCloud<PointLT>&
std::tie (min_itr, max_itr) = std::minmax_element (nghr_dist.cbegin (), nghr_dist.cend ());
float nghr_dist_min = *min_itr;
float nghr_dist_max = *max_itr;
float dist_dominant = std::max(std::abs (nghr_dist_min),std::abs (nghr_dist_max));
float dist_dominant = std::abs (nghr_dist_min) > std::abs (nghr_dist_max) ? nghr_dist_min : nghr_dist_max;
if (std::abs (dist_dominant) > th_depth_discon_*std::abs (curr_depth))
{
// Found a depth discontinuity
Expand Down
57 changes: 29 additions & 28 deletions features/include/pcl/features/organized_edge_detection.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@

namespace pcl
{
/** \brief OrganizedEdgeBase, OrganizedEdgeFromRGB, OrganizedEdgeFromNormals,
* and OrganizedEdgeFromRGBNormals find 3D edges from an organized point
* cloud data. Given an organized point cloud, they will output a PointCloud
/** \brief OrganizedEdgeBase, OrganizedEdgeFromRGB, OrganizedEdgeFromNormals,
* and OrganizedEdgeFromRGBNormals find 3D edges from an organized point
* cloud data. Given an organized point cloud, they will output a PointCloud
* of edge labels and a vector of PointIndices.
* OrganizedEdgeBase accepts PCL_XYZ_POINT_TYPES and returns EDGELABEL_NAN_BOUNDARY, EDGELABEL_OCCLUDING, and EDGELABEL_OCCLUDED.
* OrganizedEdgeFromRGB accepts PCL_RGB_POINT_TYPES and returns EDGELABEL_NAN_BOUNDARY, EDGELABEL_OCCLUDING, EDGELABEL_OCCLUDED, and EDGELABEL_RGB_CANNY.
Expand All @@ -59,7 +59,7 @@ namespace pcl
using PointCloud = pcl::PointCloud<PointT>;
using PointCloudPtr = typename PointCloud::Ptr;
using PointCloudConstPtr = typename PointCloud::ConstPtr;

using PointCloudL = pcl::PointCloud<PointLT>;
using PointCloudLPtr = typename PointCloudL::Ptr;
using PointCloudLConstPtr = typename PointCloudL::ConstPtr;
Expand All @@ -81,7 +81,7 @@ namespace pcl
}

/** \brief Destructor for OrganizedEdgeBase */

~OrganizedEdgeBase ()
{
}
Expand All @@ -92,15 +92,17 @@ namespace pcl
*/
void
compute (pcl::PointCloud<PointLT>& labels, std::vector<pcl::PointIndices>& label_indices) const;

/** \brief Set the tolerance in meters for difference in depth values between neighboring points. */

/** \brief Set the tolerance in meters for the relative difference in depth values between neighboring points.
* e.g. If a point has a depth (z) value of 2.0 meters, a neighboring point is discontinuous if its depth differs by > 2.0 * th. */
inline void
setDepthDisconThreshold (const float th)
{
th_depth_discon_ = th;
}

/** \brief Get the tolerance in meters for difference in depth values between neighboring points. */
/** \brief Get the tolerance in meters for the relative difference in depth values between neighboring points.
* e.g. If a point has a depth (z) value of 2.0 meters, a neighboring point is discontinuous if its depth differs by > 2.0 * th. */
inline float
getDepthDisconThreshold () const
{
Expand Down Expand Up @@ -134,7 +136,7 @@ namespace pcl
{
return detecting_edge_types_;
}

enum {EDGELABEL_NAN_BOUNDARY=1, EDGELABEL_OCCLUDING=2, EDGELABEL_OCCLUDED=4, EDGELABEL_HIGH_CURVATURE=8, EDGELABEL_RGB_CANNY=16};
static const int num_of_edgetype_ = 5;

Expand All @@ -144,31 +146,30 @@ namespace pcl
*/
void
extractEdges (pcl::PointCloud<PointLT>& labels) const;

/** \brief Assign point indices for each edge label
* \param[out] labels a PointCloud of edge labels
* \param[out] label_indices a vector of PointIndices corresponding to each edge label
*/
void
assignLabelIndices (pcl::PointCloud<PointLT>& labels, std::vector<pcl::PointIndices>& label_indices) const;

struct Neighbor
{
Neighbor (int dx, int dy, int didx)
: d_x (dx)
, d_y (dy)
, d_index (didx)
{}

int d_x;
int d_y;
int d_index; // = dy * width + dx: pre-calculated
};

/** \brief The tolerance in meters for difference in depth values between neighboring points
* (The value is set for 1 meter and is adapted with respect to depth value linearly.
* (e.g. 2.0*th_depth_discon_ in 2 meter depth))
*/
/** \brief The tolerance in meters for the relative difference in depth values between neighboring points
* (The default value is set for .02 meters and is adapted with respect to depth value linearly.
* e.g. If a point has a depth (z) value of 2.0 meters, a neighboring point is discontinuous if its depth differs by > 2.0 * th. */
float th_depth_discon_;

/** \brief The max search distance for deciding occluding and occluded edges */
Expand All @@ -184,7 +185,7 @@ namespace pcl
using PointCloud = pcl::PointCloud<PointT>;
using PointCloudPtr = typename PointCloud::Ptr;
using PointCloudConstPtr = typename PointCloud::ConstPtr;

using PointCloudL = pcl::PointCloud<PointLT>;
using PointCloudLPtr = typename PointCloudL::Ptr;
using PointCloudLConstPtr = typename PointCloudL::ConstPtr;
Expand All @@ -210,7 +211,7 @@ namespace pcl
}

/** \brief Destructor for OrganizedEdgeFromRGB */

~OrganizedEdgeFromRGB ()
{
}
Expand All @@ -221,7 +222,7 @@ namespace pcl
*/
void
compute (pcl::PointCloud<PointLT>& labels, std::vector<pcl::PointIndices>& label_indices) const;

/** \brief Set the low threshold value for RGB Canny edge detection */
inline void
setRGBCannyLowThreshold (const float th)
Expand Down Expand Up @@ -270,7 +271,7 @@ namespace pcl
using PointCloud = pcl::PointCloud<PointT>;
using PointCloudPtr = typename PointCloud::Ptr;
using PointCloudConstPtr = typename PointCloud::ConstPtr;

using PointCloudN = pcl::PointCloud<PointNT>;
using PointCloudNPtr = typename PointCloudN::Ptr;
using PointCloudNConstPtr = typename PointCloudN::ConstPtr;
Expand All @@ -291,7 +292,7 @@ namespace pcl
using OrganizedEdgeBase<PointT, PointLT>::EDGELABEL_HIGH_CURVATURE;

/** \brief Constructor for OrganizedEdgeFromNormals */
OrganizedEdgeFromNormals ()
OrganizedEdgeFromNormals ()
: OrganizedEdgeBase<PointT, PointLT> ()
, normals_ ()
, th_hc_canny_low_ (0.4f)
Expand All @@ -301,7 +302,7 @@ namespace pcl
}

/** \brief Destructor for OrganizedEdgeFromNormals */

~OrganizedEdgeFromNormals ()
{
}
Expand All @@ -317,7 +318,7 @@ namespace pcl
* \param[in] normals the input normal cloud
*/
inline void
setInputNormals (const PointCloudNConstPtr &normals)
setInputNormals (const PointCloudNConstPtr &normals)
{
normals_ = normals;
}
Expand Down Expand Up @@ -356,7 +357,7 @@ namespace pcl
{
return (th_hc_canny_high_);
}

protected:
/** \brief Perform the 3D edge detection (edges from depth discontinuities and high curvature regions)
* \param[out] labels a PointCloud of edge labels
Expand All @@ -380,7 +381,7 @@ namespace pcl
using PointCloud = pcl::PointCloud<PointT>;
using PointCloudPtr = typename PointCloud::Ptr;
using PointCloudConstPtr = typename PointCloud::ConstPtr;

using PointCloudN = pcl::PointCloud<PointNT>;
using PointCloudNPtr = typename PointCloudN::Ptr;
using PointCloudNConstPtr = typename PointCloudN::ConstPtr;
Expand All @@ -400,17 +401,17 @@ namespace pcl
using OrganizedEdgeBase<PointT, PointLT>::EDGELABEL_OCCLUDED;
using OrganizedEdgeBase<PointT, PointLT>::EDGELABEL_HIGH_CURVATURE;
using OrganizedEdgeBase<PointT, PointLT>::EDGELABEL_RGB_CANNY;

/** \brief Constructor for OrganizedEdgeFromRGBNormals */
OrganizedEdgeFromRGBNormals ()
OrganizedEdgeFromRGBNormals ()
: OrganizedEdgeFromRGB<PointT, PointLT> ()
, OrganizedEdgeFromNormals<PointT, PointNT, PointLT> ()
{
this->setEdgeType (EDGELABEL_NAN_BOUNDARY | EDGELABEL_OCCLUDING | EDGELABEL_OCCLUDED | EDGELABEL_RGB_CANNY | EDGELABEL_HIGH_CURVATURE);
}

/** \brief Destructor for OrganizedEdgeFromRGBNormals */

~OrganizedEdgeFromRGBNormals ()
{
}
Expand Down
3 changes: 3 additions & 0 deletions test/features/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ if(BUILD_io)
FILES test_gasd_estimation.cpp
LINK_WITH pcl_gtest pcl_io pcl_features
ARGUMENTS "${PCL_SOURCE_DIR}/test/bun0.pcd")
PCL_ADD_TEST(feature_organized_edge_detection test_organized_edge_detection
FILES test_organized_edge_detection.cpp
LINK_WITH pcl_gtest pcl_features pcl_io)
if(BUILD_keypoints)
PCL_ADD_TEST(feature_brisk test_brisk
FILES test_brisk.cpp
Expand Down
147 changes: 147 additions & 0 deletions test/features/test_organized_edge_detection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Point Cloud Library (PCL) - www.pointclouds.org
* Copyright (c) 2020-, Open Perception
*
* All rights reserved
*/

#include <pcl/features/organized_edge_detection.h>
#include <pcl/test/gtest.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>

namespace {
class OrganizedPlaneDetectionTestFixture : public ::testing::Test {
protected:
const int INNER_SQUARE_EDGE_LENGTH = 50;
const int OUTER_SQUARE_EDGE_LENGTH = INNER_SQUARE_EDGE_LENGTH * 2;
const float SYNTHETIC_CLOUD_BASE_DEPTH = 2.0;
const float SYNTHETIC_CLOUD_DEPTH_DISCONTINUITY = .02f;
const float SYNTHETIC_CLOUD_RESOLUTION = 0.01f;

pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_;
std::set<pcl::index_t> outer_perimeter_;
std::set<pcl::index_t> inner_perimeter_;

void
SetUp() override
{
cloud_ = generateSyntheticEdgeDetectionCloud();
}

private:
pcl::PointCloud<pcl::PointXYZ>::Ptr
generateSyntheticEdgeDetectionCloud()
{
auto organized_test_cloud = pcl::make_shared<pcl::PointCloud<pcl::PointXYZ>>(
OUTER_SQUARE_EDGE_LENGTH, OUTER_SQUARE_EDGE_LENGTH);

// Draw a smaller square in front of a larger square both centered on the
// view axis to generate synthetic occluding and occluded edges based on depth
// discontinuity between neighboring pixels. The base depth and resolution are
// arbitrary and useful for visualizing the cloud. The discontinuity of the
// generated cloud must be greater than the threshold set when running the
// organized edge detection algorithm.
const auto outer_square_ctr = OUTER_SQUARE_EDGE_LENGTH / 2;
const auto inner_square_ctr = INNER_SQUARE_EDGE_LENGTH / 2;
const auto left_col = outer_square_ctr - inner_square_ctr;
const auto right_col = outer_square_ctr + inner_square_ctr;
const auto top_row = outer_square_ctr - inner_square_ctr;
const auto bottom_row = outer_square_ctr + inner_square_ctr;

for (auto row = 0; row < OUTER_SQUARE_EDGE_LENGTH; ++row) {
for (auto col = 0; col < OUTER_SQUARE_EDGE_LENGTH; ++col) {
const float x = col - outer_square_ctr;
const float y = row - inner_square_ctr;

auto depth = SYNTHETIC_CLOUD_BASE_DEPTH;

// If pixels correspond to smaller box, then set depth and color appropriately
if (col >= left_col && col < right_col) {
if (row >= top_row && row < bottom_row) {

depth = SYNTHETIC_CLOUD_BASE_DEPTH - SYNTHETIC_CLOUD_DEPTH_DISCONTINUITY;

// Record indices of the outer perimeter points of small inner square that
// correspond to the occluding edge points
if ((col == left_col || col == right_col - 1) ||
(row == top_row || row == bottom_row - 1)) {
outer_perimeter_.insert(row * organized_test_cloud->width + col);
}
}
}

// Record indices of the inner perimeter points of large outer square that
// correspond to the occluded edge points
if (((row == top_row - 1 || row == bottom_row) &&
(col >= left_col - 1 && col <= right_col)) ||
((row >= top_row && row < bottom_row) &&
(col == left_col - 1 || col == right_col))) {
inner_perimeter_.insert(row * organized_test_cloud->width + col);
}

organized_test_cloud->at(col, row).x = x * SYNTHETIC_CLOUD_RESOLUTION;
organized_test_cloud->at(col, row).y = y * SYNTHETIC_CLOUD_RESOLUTION;
organized_test_cloud->at(col, row).z = depth;
}
}

return organized_test_cloud;
}
};
} // namespace

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
This test is designed to ensure that the organized edge detection appropriately
classifies occluding and occluding edges and to specifically detect the type of
regression detailed in PR 4275 (https://github.com/PointCloudLibrary/pcl/pull/4275).
This test works by generating a synthetic cloud of one square slightly in front of
another square, so that occluding edges and occluded edges are generated. The
regression introduced in PCL 1.10.1 was a logic bug that caused both occluding and
occluded edges to be miscategorized as occluding edges. This test should catch
this and similar bugs.
*/
TEST_F(OrganizedPlaneDetectionTestFixture, OccludedAndOccludingEdges)
{
constexpr auto MAX_SEARCH_NEIGHBORS = 8;

// The depth discontinuity check to determine whether an edge exists is linearly
// dependent on the depth of the points in the cloud (not a fixed distance). The
// algorithm iterates through each point in the cloud and finding the neighboring
// point with largest discontinuity value. That value is compared against a threshold
// multiplied by the actual depth value of the point. Therefore:
// abs(SYNTHETIC_CLOUD_DEPTH_DISCONTINUITY) must be greater than
// DEPTH_DISCONTINUITY_THRESHOLD * abs(SYNTHETIC_CLOUD_BASE_DEPTH)
const auto DEPTH_DISCONTINUITY_THRESHOLD =
SYNTHETIC_CLOUD_DEPTH_DISCONTINUITY / (SYNTHETIC_CLOUD_BASE_DEPTH * 1.1f);

auto oed = pcl::OrganizedEdgeBase<pcl::PointXYZ, pcl::Label>();
auto labels = pcl::PointCloud<pcl::Label>();
auto label_indices = std::vector<pcl::PointIndices>();

oed.setEdgeType(oed.EDGELABEL_OCCLUDING | oed.EDGELABEL_OCCLUDED);
oed.setInputCloud(cloud_);
oed.setDepthDisconThreshold(DEPTH_DISCONTINUITY_THRESHOLD);
oed.setMaxSearchNeighbors(MAX_SEARCH_NEIGHBORS);
oed.compute(labels, label_indices);

const auto occluding_indices = std::set<pcl::index_t>(
label_indices[1].indices.begin(), label_indices[1].indices.end());
EXPECT_EQ(occluding_indices, outer_perimeter_);

const auto occluded_indices = std::set<pcl::index_t>(label_indices[2].indices.begin(),
label_indices[2].indices.end());
EXPECT_EQ(occluded_indices, inner_perimeter_);
}

/* ---[ */
int
main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
return (RUN_ALL_TESTS());
}
/* ]--- */

0 comments on commit c440618

Please sign in to comment.