otimal
This commit is contained in:
81
include/robot_depth_image_proc/processing_rate_limiter.h
Normal file
81
include/robot_depth_image_proc/processing_rate_limiter.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace depth_image_proc
|
||||||
|
{
|
||||||
|
|
||||||
|
// Wall-clock limiter for CPU-bound sensor callbacks. It deliberately does not
|
||||||
|
// catch up after a delayed callback: processing a burst of old frames would
|
||||||
|
// increase latency and CPU without helping a latest-sample costmap consumer.
|
||||||
|
class ProcessingRateLimiter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
using TimePoint = Clock::time_point;
|
||||||
|
|
||||||
|
explicit ProcessingRateLimiter(double rate_hz = 0.0)
|
||||||
|
{
|
||||||
|
setRate(rate_hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRate(double rate_hz)
|
||||||
|
{
|
||||||
|
enabled_ = std::isfinite(rate_hz) && rate_hz > 0.0;
|
||||||
|
if (enabled_)
|
||||||
|
{
|
||||||
|
period_ = std::chrono::duration_cast<Clock::duration>(
|
||||||
|
std::chrono::duration<double>(1.0 / rate_hz));
|
||||||
|
early_tolerance_ = period_ / 20;
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldProcess()
|
||||||
|
{
|
||||||
|
return shouldProcessAt(Clock::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldProcessAt(const TimePoint now)
|
||||||
|
{
|
||||||
|
if (!enabled_)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!initialized_)
|
||||||
|
{
|
||||||
|
initialized_ = true;
|
||||||
|
next_process_time_ = now + period_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now + early_tolerance_ >= next_process_time_)
|
||||||
|
{
|
||||||
|
next_process_time_ += period_;
|
||||||
|
if (now + early_tolerance_ >= next_process_time_)
|
||||||
|
next_process_time_ = now + period_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
initialized_ = false;
|
||||||
|
next_process_time_ = TimePoint{};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enabled() const
|
||||||
|
{
|
||||||
|
return enabled_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool enabled_{false};
|
||||||
|
bool initialized_{false};
|
||||||
|
Clock::duration period_{Clock::duration::zero()};
|
||||||
|
Clock::duration early_tolerance_{Clock::duration::zero()};
|
||||||
|
TimePoint next_process_time_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace depth_image_proc
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<launch>
|
<launch>
|
||||||
<!-- Use with Gazebo T800: depth topics come from URDF depth camera plugin -->
|
<!-- Use with Gazebo T800: depth topics come from URDF depth camera plugin -->
|
||||||
<arg name="multi_cam" default="true"/>
|
<arg name="multi_cam" default="false"/>
|
||||||
<arg name="depth_topic" default="/camera/depth/image_raw"/>
|
<arg name="depth_topic" default="/camera/depth/image_raw"/>
|
||||||
<arg name="camera_info_topic" default="/camera/depth/camera_info"/>
|
<arg name="camera_info_topic" default="/camera/depth/camera_info"/>
|
||||||
<arg name="cloud_topic" default="/camera/depth/points_proc"/>
|
<arg name="cloud_topic" default="/camera/depth/points_proc"/>
|
||||||
<arg name="fixed_frame" default="odom"/>
|
<arg name="fixed_frame" default="odom"/>
|
||||||
|
<!-- Limit CPU-heavy filter/projection work while preserving 30 Hz camera input. -->
|
||||||
|
<arg name="processing_rate" default="15.0"/>
|
||||||
|
<arg name="performance_metrics_enabled" default="true"/>
|
||||||
|
<arg name="performance_metrics_period" default="5.0"/>
|
||||||
|
|
||||||
<!-- Noise filter + downsampling (applied before publishing the cloud) -->
|
<!-- Noise filter + downsampling (applied before publishing the cloud) -->
|
||||||
<arg name="filter_decimation" default="4"/> <!-- keep 1 of NxN pixels -->
|
<arg name="filter_decimation" default="4"/> <!-- keep 1 of NxN pixels -->
|
||||||
@@ -19,7 +23,7 @@
|
|||||||
<arg name="filter_edge_invalid_border" default="true"/> <!-- drop pixels hugging no-data holes at occlusion edges -->
|
<arg name="filter_edge_invalid_border" default="true"/> <!-- drop pixels hugging no-data holes at occlusion edges -->
|
||||||
<arg name="filter_temporal_max_delta" default="0.06"/> <!-- [m] per-frame stability tolerance -->
|
<arg name="filter_temporal_max_delta" default="0.06"/> <!-- [m] per-frame stability tolerance -->
|
||||||
<arg name="filter_temporal_min_frames" default="2"/> <!-- stable frames required; 0/1 disables -->
|
<arg name="filter_temporal_min_frames" default="2"/> <!-- stable frames required; 0/1 disables -->
|
||||||
<arg name="rviz" default="true"/>
|
<arg name="rviz" default="false"/>
|
||||||
|
|
||||||
<node pkg="robot_depth_image_proc"
|
<node pkg="robot_depth_image_proc"
|
||||||
type="depth_image_proc_node"
|
type="depth_image_proc_node"
|
||||||
@@ -31,6 +35,9 @@
|
|||||||
<param name="cloud_topic" value="$(arg cloud_topic)"/>
|
<param name="cloud_topic" value="$(arg cloud_topic)"/>
|
||||||
<param name="fixed_frame" value="$(arg fixed_frame)"/>
|
<param name="fixed_frame" value="$(arg fixed_frame)"/>
|
||||||
<param name="publish_tf" value="false"/>
|
<param name="publish_tf" value="false"/>
|
||||||
|
<param name="processing_rate" value="$(arg processing_rate)"/>
|
||||||
|
<param name="performance_metrics_enabled" value="$(arg performance_metrics_enabled)"/>
|
||||||
|
<param name="performance_metrics_period" value="$(arg performance_metrics_period)"/>
|
||||||
<param name="filter/decimation" value="$(arg filter_decimation)"/>
|
<param name="filter/decimation" value="$(arg filter_decimation)"/>
|
||||||
<param name="filter/range_min" value="$(arg filter_range_min)"/>
|
<param name="filter/range_min" value="$(arg filter_range_min)"/>
|
||||||
<param name="filter/range_max" value="$(arg filter_range_max)"/>
|
<param name="filter/range_max" value="$(arg filter_range_max)"/>
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -12,6 +15,7 @@
|
|||||||
|
|
||||||
#include <robot_depth_image_proc/depth_frame_filter.h>
|
#include <robot_depth_image_proc/depth_frame_filter.h>
|
||||||
#include <robot_depth_image_proc/point_cloud_xyz.h>
|
#include <robot_depth_image_proc/point_cloud_xyz.h>
|
||||||
|
#include <robot_depth_image_proc/processing_rate_limiter.h>
|
||||||
#include <robot_depth_image_proc/ros_message_conversions.h>
|
#include <robot_depth_image_proc/ros_message_conversions.h>
|
||||||
|
|
||||||
struct CameraConfig
|
struct CameraConfig
|
||||||
@@ -30,12 +34,20 @@ public:
|
|||||||
const CameraConfig& config,
|
const CameraConfig& config,
|
||||||
const depth_image_proc::DepthFilterConfig& filter_config,
|
const depth_image_proc::DepthFilterConfig& filter_config,
|
||||||
const std::string& fixed_frame,
|
const std::string& fixed_frame,
|
||||||
bool publish_tf)
|
bool publish_tf,
|
||||||
|
double processing_rate_hz,
|
||||||
|
bool performance_metrics_enabled,
|
||||||
|
double performance_metrics_period)
|
||||||
: config_(config),
|
: config_(config),
|
||||||
filter_config_(filter_config),
|
filter_config_(filter_config),
|
||||||
frame_filter_(filter_config),
|
frame_filter_(filter_config),
|
||||||
fixed_frame_(fixed_frame),
|
fixed_frame_(fixed_frame),
|
||||||
publish_tf_(publish_tf)
|
publish_tf_(publish_tf),
|
||||||
|
processing_rate_hz_(processing_rate_hz),
|
||||||
|
processing_rate_limiter_(processing_rate_hz),
|
||||||
|
performance_metrics_enabled_(performance_metrics_enabled),
|
||||||
|
performance_metrics_period_(std::max(1.0, performance_metrics_period)),
|
||||||
|
metrics_window_start_(ros::WallTime::now())
|
||||||
{
|
{
|
||||||
cloud_pub_ = nh.advertise<sensor_msgs::PointCloud2>(config_.cloud_topic, 1);
|
cloud_pub_ = nh.advertise<sensor_msgs::PointCloud2>(config_.cloud_topic, 1);
|
||||||
|
|
||||||
@@ -55,11 +67,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
ROS_INFO(
|
ROS_INFO(
|
||||||
"[%s] depth_image_proc listening on [%s] + [%s], publishing [%s]",
|
"[%s] depth_image_proc listening on [%s] + [%s], publishing [%s] at <= %.1f Hz",
|
||||||
config_.name.c_str(),
|
config_.name.c_str(),
|
||||||
config_.depth_topic.c_str(),
|
config_.depth_topic.c_str(),
|
||||||
config_.camera_info_topic.c_str(),
|
config_.camera_info_topic.c_str(),
|
||||||
config_.cloud_topic.c_str());
|
config_.cloud_topic.c_str(),
|
||||||
|
processing_rate_hz_ > 0.0 ? processing_rate_hz_ : 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -106,11 +119,21 @@ private:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
++received_frames_;
|
||||||
|
if (!processing_rate_limiter_.shouldProcess())
|
||||||
|
{
|
||||||
|
++skipped_frames_;
|
||||||
|
reportPerformanceIfDue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto processing_start = std::chrono::steady_clock::now();
|
||||||
robot_sensor_msgs::Image depth = depth_image_proc::toRobotImage(*msg);
|
robot_sensor_msgs::Image depth = depth_image_proc::toRobotImage(*msg);
|
||||||
frame_filter_.apply(depth);
|
frame_filter_.apply(depth);
|
||||||
const robot_sensor_msgs::PointCloud2 cloud =
|
const robot_sensor_msgs::PointCloud2 cloud =
|
||||||
depth_image_proc::convertDepthToPointCloudFiltered(
|
depth_image_proc::convertDepthToPointCloudFiltered(
|
||||||
depth, camera_info, filter_config_);
|
depth, camera_info, filter_config_);
|
||||||
|
recordProcessedFrame(processing_start);
|
||||||
|
|
||||||
// A fully filtered-out frame (nothing in range) is valid: publish the
|
// A fully filtered-out frame (nothing in range) is valid: publish the
|
||||||
// empty cloud so costmap_2d observation buffers do not go stale.
|
// empty cloud so costmap_2d observation buffers do not go stale.
|
||||||
@@ -120,15 +143,57 @@ private:
|
|||||||
5.0,
|
5.0,
|
||||||
"[%s] depth_image_proc conversion failed (bad encoding or filter config)",
|
"[%s] depth_image_proc conversion failed (bad encoding or filter config)",
|
||||||
config_.name.c_str());
|
config_.name.c_str());
|
||||||
|
reportPerformanceIfDue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ROS_WARN("cloud: %d", (int)cloud.data.size());
|
|
||||||
|
|
||||||
sensor_msgs::PointCloud2 ros_cloud = depth_image_proc::toRosPointCloud(cloud);
|
sensor_msgs::PointCloud2 ros_cloud = depth_image_proc::toRosPointCloud(cloud);
|
||||||
ros_cloud.header.stamp = msg->header.stamp;
|
ros_cloud.header.stamp = msg->header.stamp;
|
||||||
ros_cloud.header.frame_id = msg->header.frame_id;
|
ros_cloud.header.frame_id = msg->header.frame_id;
|
||||||
cloud_pub_.publish(ros_cloud);
|
cloud_pub_.publish(ros_cloud);
|
||||||
|
reportPerformanceIfDue();
|
||||||
|
}
|
||||||
|
|
||||||
|
void recordProcessedFrame(const std::chrono::steady_clock::time_point processing_start)
|
||||||
|
{
|
||||||
|
const double elapsed_ms =
|
||||||
|
std::chrono::duration<double, std::milli>(
|
||||||
|
std::chrono::steady_clock::now() - processing_start).count();
|
||||||
|
++processed_frames_;
|
||||||
|
processing_time_ms_ += elapsed_ms;
|
||||||
|
max_processing_time_ms_ = std::max(max_processing_time_ms_, elapsed_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportPerformanceIfDue()
|
||||||
|
{
|
||||||
|
if (!performance_metrics_enabled_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const ros::WallTime now = ros::WallTime::now();
|
||||||
|
const double window_seconds = (now - metrics_window_start_).toSec();
|
||||||
|
if (window_seconds < performance_metrics_period_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const double input_hz = static_cast<double>(received_frames_) / window_seconds;
|
||||||
|
const double processed_hz = static_cast<double>(processed_frames_) / window_seconds;
|
||||||
|
const double average_ms =
|
||||||
|
processed_frames_ > 0 ? processing_time_ms_ / static_cast<double>(processed_frames_) : 0.0;
|
||||||
|
ROS_INFO(
|
||||||
|
"[%s] depth performance: input=%.1f Hz processed=%.1f Hz "
|
||||||
|
"skipped=%llu avg=%.2f ms max=%.2f ms",
|
||||||
|
config_.name.c_str(),
|
||||||
|
input_hz,
|
||||||
|
processed_hz,
|
||||||
|
static_cast<unsigned long long>(skipped_frames_),
|
||||||
|
average_ms,
|
||||||
|
max_processing_time_ms_);
|
||||||
|
|
||||||
|
metrics_window_start_ = now;
|
||||||
|
received_frames_ = 0;
|
||||||
|
processed_frames_ = 0;
|
||||||
|
skipped_frames_ = 0;
|
||||||
|
processing_time_ms_ = 0.0;
|
||||||
|
max_processing_time_ms_ = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void publishStaticTransform()
|
void publishStaticTransform()
|
||||||
@@ -151,6 +216,16 @@ private:
|
|||||||
depth_image_proc::DepthFrameFilter frame_filter_;
|
depth_image_proc::DepthFrameFilter frame_filter_;
|
||||||
const std::string fixed_frame_;
|
const std::string fixed_frame_;
|
||||||
const bool publish_tf_;
|
const bool publish_tf_;
|
||||||
|
const double processing_rate_hz_;
|
||||||
|
depth_image_proc::ProcessingRateLimiter processing_rate_limiter_;
|
||||||
|
const bool performance_metrics_enabled_;
|
||||||
|
const double performance_metrics_period_;
|
||||||
|
ros::WallTime metrics_window_start_;
|
||||||
|
std::uint64_t received_frames_{0};
|
||||||
|
std::uint64_t processed_frames_{0};
|
||||||
|
std::uint64_t skipped_frames_{0};
|
||||||
|
double processing_time_ms_{0.0};
|
||||||
|
double max_processing_time_ms_{0.0};
|
||||||
|
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
robot_sensor_msgs::CameraInfo camera_info_;
|
robot_sensor_msgs::CameraInfo camera_info_;
|
||||||
@@ -170,6 +245,9 @@ public:
|
|||||||
{
|
{
|
||||||
pnh.param("fixed_frame", fixed_frame_, std::string("map"));
|
pnh.param("fixed_frame", fixed_frame_, std::string("map"));
|
||||||
pnh.param("publish_tf", publish_tf_, true);
|
pnh.param("publish_tf", publish_tf_, true);
|
||||||
|
pnh.param("processing_rate", processing_rate_hz_, 15.0);
|
||||||
|
pnh.param("performance_metrics_enabled", performance_metrics_enabled_, true);
|
||||||
|
pnh.param("performance_metrics_period", performance_metrics_period_, 5.0);
|
||||||
|
|
||||||
const depth_image_proc::DepthFilterConfig filter_config = loadFilterConfig(pnh);
|
const depth_image_proc::DepthFilterConfig filter_config = loadFilterConfig(pnh);
|
||||||
|
|
||||||
@@ -184,7 +262,14 @@ public:
|
|||||||
for (const CameraConfig& config : configs)
|
for (const CameraConfig& config : configs)
|
||||||
{
|
{
|
||||||
pipelines_.push_back(std::make_unique<DepthCameraPipeline>(
|
pipelines_.push_back(std::make_unique<DepthCameraPipeline>(
|
||||||
nh, config, filter_config, fixed_frame_, publish_tf_));
|
nh,
|
||||||
|
config,
|
||||||
|
filter_config,
|
||||||
|
fixed_frame_,
|
||||||
|
publish_tf_,
|
||||||
|
processing_rate_hz_,
|
||||||
|
performance_metrics_enabled_,
|
||||||
|
performance_metrics_period_));
|
||||||
}
|
}
|
||||||
|
|
||||||
ROS_INFO("depth_image_proc_node started with %zu camera(s)", pipelines_.size());
|
ROS_INFO("depth_image_proc_node started with %zu camera(s)", pipelines_.size());
|
||||||
@@ -336,6 +421,9 @@ private:
|
|||||||
|
|
||||||
std::string fixed_frame_;
|
std::string fixed_frame_;
|
||||||
bool publish_tf_{true};
|
bool publish_tf_{true};
|
||||||
|
double processing_rate_hz_{15.0};
|
||||||
|
bool performance_metrics_enabled_{true};
|
||||||
|
double performance_metrics_period_{5.0};
|
||||||
std::vector<std::unique_ptr<DepthCameraPipeline>> pipelines_;
|
std::vector<std::unique_ptr<DepthCameraPipeline>> pipelines_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <robot_depth_image_proc/depth_frame_filter.h>
|
#include <robot_depth_image_proc/depth_frame_filter.h>
|
||||||
#include <robot_depth_image_proc/point_cloud_xyz.h>
|
#include <robot_depth_image_proc/point_cloud_xyz.h>
|
||||||
|
#include <robot_depth_image_proc/processing_rate_limiter.h>
|
||||||
#include <robot_sensor_msgs/image_encodings.h>
|
#include <robot_sensor_msgs/image_encodings.h>
|
||||||
#include <robot_sensor_msgs/point_cloud2_iterator.h>
|
#include <robot_sensor_msgs/point_cloud2_iterator.h>
|
||||||
|
|
||||||
@@ -59,6 +60,50 @@ robot_sensor_msgs::Image makeFlatDepthImage(
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
TEST(ProcessingRateLimiter, LimitsWorkWithoutCatchUpBursts)
|
||||||
|
{
|
||||||
|
using Clock = depth_image_proc::ProcessingRateLimiter::Clock;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
depth_image_proc::ProcessingRateLimiter limiter(10.0);
|
||||||
|
const Clock::time_point start{};
|
||||||
|
|
||||||
|
EXPECT_TRUE(limiter.shouldProcessAt(start));
|
||||||
|
EXPECT_FALSE(limiter.shouldProcessAt(start + 50ms));
|
||||||
|
EXPECT_TRUE(limiter.shouldProcessAt(start + 100ms));
|
||||||
|
|
||||||
|
// A late callback schedules from "now"; it does not create a catch-up burst.
|
||||||
|
EXPECT_TRUE(limiter.shouldProcessAt(start + 350ms));
|
||||||
|
EXPECT_FALSE(limiter.shouldProcessAt(start + 351ms));
|
||||||
|
EXPECT_TRUE(limiter.shouldProcessAt(start + 450ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProcessingRateLimiter, NonPositiveRateDisablesLimiting)
|
||||||
|
{
|
||||||
|
using Clock = depth_image_proc::ProcessingRateLimiter::Clock;
|
||||||
|
|
||||||
|
depth_image_proc::ProcessingRateLimiter limiter(0.0);
|
||||||
|
const Clock::time_point now{};
|
||||||
|
EXPECT_TRUE(limiter.shouldProcessAt(now));
|
||||||
|
EXPECT_TRUE(limiter.shouldProcessAt(now));
|
||||||
|
|
||||||
|
limiter.setRate(-1.0);
|
||||||
|
EXPECT_TRUE(limiter.shouldProcessAt(now));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ProcessingRateLimiter, AcceptsNominalFramesWithSmallClockJitter)
|
||||||
|
{
|
||||||
|
using Clock = depth_image_proc::ProcessingRateLimiter::Clock;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
depth_image_proc::ProcessingRateLimiter limiter(15.0);
|
||||||
|
const Clock::time_point start{};
|
||||||
|
|
||||||
|
EXPECT_TRUE(limiter.shouldProcessAt(start));
|
||||||
|
EXPECT_TRUE(limiter.shouldProcessAt(start + 65ms));
|
||||||
|
EXPECT_TRUE(limiter.shouldProcessAt(start + 130ms));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(PointCloudXyz, ConvertsFlatDepthImage)
|
TEST(PointCloudXyz, ConvertsFlatDepthImage)
|
||||||
{
|
{
|
||||||
const uint32_t width = 3;
|
const uint32_t width = 3;
|
||||||
|
|||||||
Reference in New Issue
Block a user