mirror of
https://github.com/orbbec/OrbbecSDK_ROS2.git
synced 2026-10-11 15:09:48 +08:00
feat: enhance frame timestamp logging with expected interval and dropped frame tracking
This commit is contained in:
@@ -60,6 +60,7 @@ class FrameTimestampCsvLogger {
|
|||||||
int64_t sdk_system_ts_us = 0;
|
int64_t sdk_system_ts_us = 0;
|
||||||
int64_t arrival_system_us = 0;
|
int64_t arrival_system_us = 0;
|
||||||
int64_t arrival_steady_us = 0;
|
int64_t arrival_steady_us = 0;
|
||||||
|
int64_t expected_interval_us = 0;
|
||||||
std::optional<int64_t> publish_system_us;
|
std::optional<int64_t> publish_system_us;
|
||||||
std::optional<int64_t> publish_steady_us;
|
std::optional<int64_t> publish_steady_us;
|
||||||
|
|
||||||
@@ -85,6 +86,10 @@ class FrameTimestampCsvLogger {
|
|||||||
|
|
||||||
struct PreviousStreamTimestamps {
|
struct PreviousStreamTimestamps {
|
||||||
std::optional<int64_t> device_ts_us;
|
std::optional<int64_t> device_ts_us;
|
||||||
|
std::optional<int64_t> publish_device_ts_us;
|
||||||
|
int64_t expected_interval_us = 0;
|
||||||
|
int64_t dropped_frames = 0;
|
||||||
|
int64_t publish_dropped_frames = 0;
|
||||||
std::optional<int64_t> sensor_ts_us;
|
std::optional<int64_t> sensor_ts_us;
|
||||||
std::optional<int64_t> global_ts_us;
|
std::optional<int64_t> global_ts_us;
|
||||||
std::optional<int64_t> sdk_system_ts_us;
|
std::optional<int64_t> sdk_system_ts_us;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "orbbec_camera/frame_timestamp_csv_logger.h"
|
#include "orbbec_camera/frame_timestamp_csv_logger.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
@@ -13,6 +14,21 @@ constexpr size_t kCompletedQueueSoftLimit = 1000;
|
|||||||
constexpr size_t kFlushBatchSize = 100;
|
constexpr size_t kFlushBatchSize = 100;
|
||||||
constexpr auto kFlushInterval = std::chrono::seconds(1);
|
constexpr auto kFlushInterval = std::chrono::seconds(1);
|
||||||
|
|
||||||
|
int64_t getExpectedIntervalUs(const std::shared_ptr<ob::Frame> &frame) {
|
||||||
|
if (!frame || !frame->is<ob::VideoFrame>()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto stream_profile = frame->getStreamProfile();
|
||||||
|
if (!stream_profile || !stream_profile->is<ob::VideoStreamProfile>()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const auto fps = stream_profile->as<ob::VideoStreamProfile>()->fps();
|
||||||
|
if (fps == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<int64_t>(1000000.0 / static_cast<double>(fps));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
FrameTimestampCsvLogger::FrameTimestampCsvLogger(bool enabled, const std::string &csv_file_path,
|
FrameTimestampCsvLogger::FrameTimestampCsvLogger(bool enabled, const std::string &csv_file_path,
|
||||||
@@ -22,6 +38,19 @@ FrameTimestampCsvLogger::FrameTimestampCsvLogger(bool enabled, const std::string
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RCLCPP_INFO_STREAM(
|
||||||
|
logger_,
|
||||||
|
"Frame drop log enabled. Format: <SDK|PUB> drop <color|depth>: idx=<frame_index> "
|
||||||
|
"ts=<device_timestamp_us> gap=<actual_interval_ms> ideal=<expected_interval_ms> "
|
||||||
|
"total=<total_dropped_frames>. SDK means frame arrival from SDK; PUB means before ROS "
|
||||||
|
"image publish.");
|
||||||
|
|
||||||
|
if (csv_file_path_.empty()) {
|
||||||
|
RCLCPP_INFO_STREAM(logger_,
|
||||||
|
"Frame timestamp CSV file is empty; only frame lost logs are enabled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto path = std::filesystem::path(csv_file_path_);
|
auto path = std::filesystem::path(csv_file_path_);
|
||||||
if (path.has_parent_path() && !std::filesystem::exists(path.parent_path())) {
|
if (path.has_parent_path() && !std::filesystem::exists(path.parent_path())) {
|
||||||
@@ -284,6 +313,27 @@ void FrameTimestampCsvLogger::populateArrivalData(StreamState &state, TrackedStr
|
|||||||
state.has_frame = true;
|
state.has_frame = true;
|
||||||
state.publish_expected = publish_expected;
|
state.publish_expected = publish_expected;
|
||||||
state.frame_index = frame->index();
|
state.frame_index = frame->index();
|
||||||
|
state.device_ts_us = static_cast<int64_t>(frame->timeStampUs());
|
||||||
|
if (previous.expected_interval_us <= 0) {
|
||||||
|
previous.expected_interval_us = getExpectedIntervalUs(frame);
|
||||||
|
}
|
||||||
|
state.expected_interval_us = previous.expected_interval_us;
|
||||||
|
if (previous.device_ts_us.has_value() && state.expected_interval_us > 0) {
|
||||||
|
const auto device_ts_delta_us = state.device_ts_us - previous.device_ts_us.value();
|
||||||
|
if (device_ts_delta_us > state.expected_interval_us * 3 / 2) {
|
||||||
|
const auto lost_frames =
|
||||||
|
std::max<int64_t>(1, device_ts_delta_us / state.expected_interval_us - 1);
|
||||||
|
previous.dropped_frames += lost_frames;
|
||||||
|
RCLCPP_WARN_STREAM(
|
||||||
|
logger_, "SDK drop " << (stream == TrackedStream::COLOR ? "color" : "depth") << ": idx="
|
||||||
|
<< state.frame_index << " ts=" << state.device_ts_us << "us"
|
||||||
|
<< " gap=" << std::fixed << std::setprecision(1)
|
||||||
|
<< (static_cast<double>(device_ts_delta_us) / 1000.0) << "ms"
|
||||||
|
<< " ideal="
|
||||||
|
<< (static_cast<double>(state.expected_interval_us) / 1000.0) << "ms"
|
||||||
|
<< " total=" << previous.dropped_frames);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (frame->hasMetadata(OB_FRAME_METADATA_TYPE_FRAME_NUMBER)) {
|
if (frame->hasMetadata(OB_FRAME_METADATA_TYPE_FRAME_NUMBER)) {
|
||||||
state.metadata_frame_number =
|
state.metadata_frame_number =
|
||||||
static_cast<int64_t>(frame->getMetadataValue(OB_FRAME_METADATA_TYPE_FRAME_NUMBER));
|
static_cast<int64_t>(frame->getMetadataValue(OB_FRAME_METADATA_TYPE_FRAME_NUMBER));
|
||||||
@@ -296,7 +346,6 @@ void FrameTimestampCsvLogger::populateArrivalData(StreamState &state, TrackedStr
|
|||||||
} else {
|
} else {
|
||||||
state.sensor_ts_us.reset();
|
state.sensor_ts_us.reset();
|
||||||
}
|
}
|
||||||
state.device_ts_us = static_cast<int64_t>(frame->timeStampUs());
|
|
||||||
state.global_ts_us = static_cast<int64_t>(frame->globalTimeStampUs());
|
state.global_ts_us = static_cast<int64_t>(frame->globalTimeStampUs());
|
||||||
state.sdk_system_ts_us = static_cast<int64_t>(frame->systemTimeStampUs());
|
state.sdk_system_ts_us = static_cast<int64_t>(frame->systemTimeStampUs());
|
||||||
state.arrival_system_us = arrival_system_us;
|
state.arrival_system_us = arrival_system_us;
|
||||||
@@ -310,7 +359,7 @@ void FrameTimestampCsvLogger::populateArrivalData(StreamState &state, TrackedStr
|
|||||||
}
|
}
|
||||||
state.global_ts_delta_us = updateDelta(previous.global_ts_us, state.global_ts_us);
|
state.global_ts_delta_us = updateDelta(previous.global_ts_us, state.global_ts_us);
|
||||||
state.sdk_system_ts_delta_us = updateDelta(previous.sdk_system_ts_us, state.sdk_system_ts_us);
|
state.sdk_system_ts_delta_us = updateDelta(previous.sdk_system_ts_us, state.sdk_system_ts_us);
|
||||||
state.arrival_system_delta_us = updateDelta(previous.arrival_system_us, state.arrival_system_us);
|
previous.arrival_system_us = state.arrival_system_us;
|
||||||
state.arrival_steady_delta_us = updateDelta(previous.arrival_steady_us, state.arrival_steady_us);
|
state.arrival_steady_delta_us = updateDelta(previous.arrival_steady_us, state.arrival_steady_us);
|
||||||
state.sdk_delay_from_global_us = state.arrival_system_us - state.global_ts_us;
|
state.sdk_delay_from_global_us = state.arrival_system_us - state.global_ts_us;
|
||||||
state.sdk_delay_from_system_us = state.arrival_system_us - state.sdk_system_ts_us;
|
state.sdk_delay_from_system_us = state.arrival_system_us - state.sdk_system_ts_us;
|
||||||
@@ -321,13 +370,29 @@ void FrameTimestampCsvLogger::populatePublishData(StreamState &state, TrackedStr
|
|||||||
int64_t publish_steady_us) {
|
int64_t publish_steady_us) {
|
||||||
auto &previous = stream == TrackedStream::COLOR ? color_previous_ : depth_previous_;
|
auto &previous = stream == TrackedStream::COLOR ? color_previous_ : depth_previous_;
|
||||||
|
|
||||||
|
if (previous.publish_device_ts_us.has_value()) {
|
||||||
|
const auto device_ts_delta_us = state.device_ts_us - previous.publish_device_ts_us.value();
|
||||||
|
if (state.expected_interval_us > 0 && device_ts_delta_us > state.expected_interval_us * 3 / 2) {
|
||||||
|
const auto lost_frames =
|
||||||
|
std::max<int64_t>(1, device_ts_delta_us / state.expected_interval_us - 1);
|
||||||
|
previous.publish_dropped_frames += lost_frames;
|
||||||
|
RCLCPP_WARN_STREAM(
|
||||||
|
logger_, "PUB drop " << (stream == TrackedStream::COLOR ? "color" : "depth") << ": idx="
|
||||||
|
<< state.frame_index << " ts=" << state.device_ts_us << "us"
|
||||||
|
<< " gap=" << std::fixed << std::setprecision(1)
|
||||||
|
<< (static_cast<double>(device_ts_delta_us) / 1000.0) << "ms"
|
||||||
|
<< " ideal="
|
||||||
|
<< (static_cast<double>(state.expected_interval_us) / 1000.0) << "ms"
|
||||||
|
<< " total=" << previous.publish_dropped_frames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previous.publish_device_ts_us = state.device_ts_us;
|
||||||
|
|
||||||
state.publish_system_us = publish_system_us;
|
state.publish_system_us = publish_system_us;
|
||||||
state.publish_steady_us = publish_steady_us;
|
state.publish_steady_us = publish_steady_us;
|
||||||
state.publish_system_delta_us =
|
previous.publish_system_us = state.publish_system_us.value();
|
||||||
updateDelta(previous.publish_system_us, state.publish_system_us.value());
|
|
||||||
state.publish_steady_delta_us =
|
state.publish_steady_delta_us =
|
||||||
updateDelta(previous.publish_steady_us, state.publish_steady_us.value());
|
updateDelta(previous.publish_steady_us, state.publish_steady_us.value());
|
||||||
state.arrival_to_publish_system_us = state.publish_system_us.value() - state.arrival_system_us;
|
|
||||||
state.arrival_to_publish_steady_us = state.publish_steady_us.value() - state.arrival_steady_us;
|
state.arrival_to_publish_steady_us = state.publish_steady_us.value() - state.arrival_steady_us;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,6 +415,9 @@ bool FrameTimestampCsvLogger::isRowReady(const PendingRow &row) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FrameTimestampCsvLogger::enqueueCompletedRow(const PendingRow &row) {
|
void FrameTimestampCsvLogger::enqueueCompletedRow(const PendingRow &row) {
|
||||||
|
if (csv_file_path_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
std::lock_guard<std::mutex> queue_lock(completed_rows_mutex_);
|
std::lock_guard<std::mutex> queue_lock(completed_rows_mutex_);
|
||||||
completed_rows_.push_back(row);
|
completed_rows_.push_back(row);
|
||||||
if (completed_rows_.size() > kCompletedQueueSoftLimit) {
|
if (completed_rows_.size() > kCompletedQueueSoftLimit) {
|
||||||
@@ -393,7 +461,7 @@ std::string FrameTimestampCsvLogger::serializeRow(const PendingRow &row) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string FrameTimestampCsvLogger::serializeStreamColumns(const StreamState &state) const {
|
std::string FrameTimestampCsvLogger::serializeStreamColumns(const StreamState &state) const {
|
||||||
std::vector<std::string> fields(22, "");
|
std::vector<std::string> fields(15, "");
|
||||||
if (state.has_frame) {
|
if (state.has_frame) {
|
||||||
fields[0] = std::to_string(state.frame_index);
|
fields[0] = std::to_string(state.frame_index);
|
||||||
fields[1] = formatOptionalIntColumn(state.metadata_frame_number);
|
fields[1] = formatOptionalIntColumn(state.metadata_frame_number);
|
||||||
@@ -407,22 +475,11 @@ std::string FrameTimestampCsvLogger::serializeStreamColumns(const StreamState &s
|
|||||||
fields[7] = formatOptionalIntColumn(state.global_ts_delta_us);
|
fields[7] = formatOptionalIntColumn(state.global_ts_delta_us);
|
||||||
fields[8] = formatSecondsColumn(state.sdk_system_ts_us);
|
fields[8] = formatSecondsColumn(state.sdk_system_ts_us);
|
||||||
fields[9] = formatOptionalIntColumn(state.sdk_system_ts_delta_us);
|
fields[9] = formatOptionalIntColumn(state.sdk_system_ts_delta_us);
|
||||||
fields[10] = formatSecondsColumn(state.arrival_system_us);
|
fields[10] = formatOptionalIntColumn(state.arrival_steady_delta_us);
|
||||||
fields[11] = formatOptionalIntColumn(state.arrival_system_delta_us);
|
fields[11] = formatOptionalIntColumn(state.publish_steady_delta_us);
|
||||||
fields[12] = formatSecondsColumn(state.arrival_steady_us);
|
fields[12] = formatOptionalIntColumn(state.arrival_to_publish_steady_us);
|
||||||
fields[13] = formatOptionalIntColumn(state.arrival_steady_delta_us);
|
fields[13] = formatOptionalIntColumn(state.sdk_delay_from_global_us);
|
||||||
if (state.publish_system_us.has_value()) {
|
fields[14] = formatOptionalIntColumn(state.sdk_delay_from_system_us);
|
||||||
fields[14] = formatSecondsColumn(state.publish_system_us.value());
|
|
||||||
}
|
|
||||||
fields[15] = formatOptionalIntColumn(state.publish_system_delta_us);
|
|
||||||
if (state.publish_steady_us.has_value()) {
|
|
||||||
fields[16] = formatSecondsColumn(state.publish_steady_us.value());
|
|
||||||
}
|
|
||||||
fields[17] = formatOptionalIntColumn(state.publish_steady_delta_us);
|
|
||||||
fields[18] = formatOptionalIntColumn(state.arrival_to_publish_system_us);
|
|
||||||
fields[19] = formatOptionalIntColumn(state.arrival_to_publish_steady_us);
|
|
||||||
fields[20] = formatOptionalIntColumn(state.sdk_delay_from_global_us);
|
|
||||||
fields[21] = formatOptionalIntColumn(state.sdk_delay_from_system_us);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
@@ -461,15 +518,8 @@ std::string FrameTimestampCsvLogger::csvHeader() {
|
|||||||
ss << prefix << "_global_ts_delta_us,";
|
ss << prefix << "_global_ts_delta_us,";
|
||||||
ss << prefix << "_system_ts_sec,";
|
ss << prefix << "_system_ts_sec,";
|
||||||
ss << prefix << "_system_ts_delta_us,";
|
ss << prefix << "_system_ts_delta_us,";
|
||||||
ss << prefix << "_arrival_system_sec,";
|
|
||||||
ss << prefix << "_arrival_system_delta_us,";
|
|
||||||
ss << prefix << "_arrival_steady_sec,";
|
|
||||||
ss << prefix << "_arrival_steady_delta_us,";
|
ss << prefix << "_arrival_steady_delta_us,";
|
||||||
ss << prefix << "_publish_system_sec,";
|
|
||||||
ss << prefix << "_publish_system_delta_us,";
|
|
||||||
ss << prefix << "_publish_steady_sec,";
|
|
||||||
ss << prefix << "_publish_steady_delta_us,";
|
ss << prefix << "_publish_steady_delta_us,";
|
||||||
ss << prefix << "_arrival_to_publish_system_us,";
|
|
||||||
ss << prefix << "_arrival_to_publish_steady_us,";
|
ss << prefix << "_arrival_to_publish_steady_us,";
|
||||||
ss << prefix << "_sdk_delay_from_global_us,";
|
ss << prefix << "_sdk_delay_from_global_us,";
|
||||||
ss << prefix << "_sdk_delay_from_system_us";
|
ss << prefix << "_sdk_delay_from_system_us";
|
||||||
|
|||||||
@@ -75,11 +75,6 @@ OBCameraNode::OBCameraNode(rclcpp::Node *node, std::shared_ptr<ob::Device> devic
|
|||||||
setupDefaultImageFormat();
|
setupDefaultImageFormat();
|
||||||
setupTopics();
|
setupTopics();
|
||||||
if (enable_frame_timestamp_csv_) {
|
if (enable_frame_timestamp_csv_) {
|
||||||
if (frame_timestamp_csv_file_.empty()) {
|
|
||||||
frame_timestamp_csv_file_ =
|
|
||||||
(std::filesystem::current_path() / (camera_name_ + "_frame_timestamp_stats.csv"))
|
|
||||||
.string();
|
|
||||||
}
|
|
||||||
frame_timestamp_csv_logger_ =
|
frame_timestamp_csv_logger_ =
|
||||||
std::make_unique<FrameTimestampCsvLogger>(true, frame_timestamp_csv_file_, logger_);
|
std::make_unique<FrameTimestampCsvLogger>(true, frame_timestamp_csv_file_, logger_);
|
||||||
if (!frame_timestamp_csv_logger_->enabled()) {
|
if (!frame_timestamp_csv_logger_->enabled()) {
|
||||||
@@ -1854,8 +1849,22 @@ void OBCameraNode::onNewFrameSetCallback(std::shared_ptr<ob::FrameSet> frame_set
|
|||||||
if (frame_set == nullptr) {
|
if (frame_set == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto frame_set_arrival_system_us = getSystemNowUs();
|
if (frame_timestamp_csv_logger_ && frame_timestamp_csv_logger_->enabled()) {
|
||||||
const auto frame_set_arrival_steady_us = getSteadyNowUs();
|
const auto frame_set_arrival_system_us = getSystemNowUs();
|
||||||
|
const auto frame_set_arrival_steady_us = getSteadyNowUs();
|
||||||
|
auto final_color_frame = frame_set->getFrame(OB_FRAME_COLOR);
|
||||||
|
auto final_depth_frame = frame_set->getFrame(OB_FRAME_DEPTH);
|
||||||
|
const bool track_color = enable_stream_[COLOR] && static_cast<bool>(final_color_frame);
|
||||||
|
const bool track_depth = enable_stream_[DEPTH] && static_cast<bool>(final_depth_frame);
|
||||||
|
const bool color_publish_expected = track_color;
|
||||||
|
const bool depth_publish_expected = track_depth;
|
||||||
|
|
||||||
|
frame_timestamp_csv_logger_->recordFrameSet(
|
||||||
|
final_color_frame, final_depth_frame, frame_set_arrival_system_us,
|
||||||
|
frame_set_arrival_steady_us, track_color, track_depth, color_publish_expected,
|
||||||
|
depth_publish_expected);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!tf_published_) {
|
if (!tf_published_) {
|
||||||
publishStaticTransforms();
|
publishStaticTransforms();
|
||||||
@@ -1884,18 +1893,6 @@ void OBCameraNode::onNewFrameSetCallback(std::shared_ptr<ob::FrameSet> frame_set
|
|||||||
"null or color frame is null");
|
"null or color frame is null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto final_color_frame = frame_set->getFrame(OB_FRAME_COLOR);
|
|
||||||
auto final_depth_frame = frame_set->getFrame(OB_FRAME_DEPTH);
|
|
||||||
if (frame_timestamp_csv_logger_ && frame_timestamp_csv_logger_->enabled()) {
|
|
||||||
const bool track_color = enable_stream_[COLOR] && static_cast<bool>(final_color_frame);
|
|
||||||
const bool track_depth = enable_stream_[DEPTH] && static_cast<bool>(final_depth_frame);
|
|
||||||
const bool color_publish_expected = track_color;
|
|
||||||
const bool depth_publish_expected = track_depth;
|
|
||||||
frame_timestamp_csv_logger_->recordFrameSet(
|
|
||||||
final_color_frame, final_depth_frame, frame_set_arrival_system_us,
|
|
||||||
frame_set_arrival_steady_us, track_color, track_depth, color_publish_expected,
|
|
||||||
depth_publish_expected);
|
|
||||||
}
|
|
||||||
if (enable_stream_[COLOR] && color_frame) {
|
if (enable_stream_[COLOR] && color_frame) {
|
||||||
std::unique_lock<std::mutex> lock(color_frame_queue_lock_);
|
std::unique_lock<std::mutex> lock(color_frame_queue_lock_);
|
||||||
// if (color_frame_queue_.size() > 2) {
|
// if (color_frame_queue_.size() > 2) {
|
||||||
|
|||||||
Reference in New Issue
Block a user