Skip to content

Add Bird's-Eye-View rendering and tracker integration for ByteTrack#8

Open
salhiraid wants to merge 4 commits intomainfrom
codex/add-birds-eye-view-rendering-module
Open

Add Bird's-Eye-View rendering and tracker integration for ByteTrack#8
salhiraid wants to merge 4 commits intomainfrom
codex/add-birds-eye-view-rendering-module

Conversation

@salhiraid
Copy link
Copy Markdown
Owner

@salhiraid salhiraid commented Apr 15, 2026

Motivation

  • Add an optional bird's-eye-view (BEV) visualization that renders both tracked and lost tracks per frame without changing ByteTrack association logic.
  • Carry world ground-point (gp) and class information through detections and tracks so BEV rendering can position and label tracks in world coordinates.
  • Provide a ready-to-use integration and example usage for the TensorRT C++ demo.

Description

  • Added a new BirdEyeViewRenderer (deploy/TensorRT/cpp/include/BirdEyeViewRenderer.h, deploy/TensorRT/cpp/src/BirdEyeViewRenderer.cpp) which builds a BEV canvas, converts world coords to image pixels, draws grid/axes and track markers, and writes per-frame images.
  • Added BirdEyeViewTracker (deploy/TensorRT/cpp/include/BirdEyeViewTracker.h, deploy/TensorRT/cpp/src/BirdEyeViewTracker.cpp) as a BYTETracker subclass that calls the base update, collects both tracked and lost STrack entries, and passes them to the BirdEyeViewRenderer.
  • Extended Object (in BYTETracker.h) to include gp (cv::Point3f) and propagate label/gp into STrack via a new STrack constructor signature; updated STrack (include/STrack.h, src/STrack.cpp) to store class_id and gp and to copy them in update/re_activate.
  • Made BYTETracker::update virtual and destructor virtual, added get_tracked_stracks() and get_lost_stracks() accessors in BYTETracker, and updated the TensorRT demo (deploy/TensorRT/cpp/src/bytetrack.cpp) to instantiate BirdEyeViewTracker and write BEV outputs.
  • Added documentation example deploy/TensorRT/cpp/BEV_INTEGRATION.md that shows usage and notes on passing gp from detectors.

Testing

  • Built the modified C++ project and compiled the changed translation units including STrack.cpp, BYTETracker.cpp, BirdEyeViewRenderer.cpp, and BirdEyeViewTracker.cpp successfully.
  • Ran the demo binary on a sample video which invoked BirdEyeViewTracker and produced per-frame BEV images under the configured bev_outputs directory without runtime errors.

Codex Task

Summary by Sourcery

Integrate a bird's-eye-view (BEV) visualization into the TensorRT C++ ByteTrack demo while propagating world coordinates and class metadata through tracking.

New Features:

  • Add BirdEyeViewRenderer to generate per-frame bird's-eye-view images with grid, axes, and track markers from world-coordinate track data.
  • Introduce BirdEyeViewTracker as a BYTETracker subclass that renders both tracked and lost tracks into BEV each frame and exposes access to the renderer.
  • Extend STrack and Object to carry class id and ground-point (gp) information needed for BEV rendering, and expose accessors for tracked and lost tracks in BYTETracker.
  • Update the TensorRT C++ demo to use BirdEyeViewTracker and emit BEV images alongside the regular tracking output.

Enhancements:

  • Make BYTETracker's update method and destructor virtual and provide getters for tracked and lost tracks to support tracker extensions like BEV rendering.

Documentation:

  • Add BEV_INTEGRATION.md documenting how to integrate BirdEyeViewTracker and how to pass class and ground-point data into ByteTrack.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 15, 2026

Reviewer's Guide

Adds a BirdEyeViewTracker/BirdEyeViewRenderer pipeline on top of BYTETracker to generate per-frame bird’s-eye-view BEV images using world ground-point coordinates and class info carried through Object and STrack, while slightly extending BYTETracker’s API for subclassing and inspection of tracked/lost tracks, and wiring this into the TensorRT C++ demo plus brief docs.

Sequence diagram for per-frame BEV rendering via BirdEyeViewTracker

sequenceDiagram
    actor User
    participant Main as main_bytetrack_cpp
    participant Tracker as BirdEyeViewTracker
    participant Base as BYTETracker
    participant Renderer as BirdEyeViewRenderer

    User->>Main: start demo()
    Main->>Tracker: BirdEyeViewTracker(fps, track_buffer,
    Main->>Tracker: "bev_outputs", y_min_m, y_max_m, ...)

    loop For each video frame
        Main->>Main: preprocess frame, build vector<Object> objects
        Main->>Tracker: update(objects)
        activate Tracker
            Tracker->>Base: update(objects)
            activate Base
                Base->>Base: build detections from Object
                Base->>Base: run association and Kalman updates
                Base-->>Tracker: vector<STrack> output_tracks
            deactivate Base

            Tracker->>Base: get_tracked_stracks()
            Tracker->>Base: get_lost_stracks()
            Tracker->>Tracker: appendTracksToBevInput(tracked, Tracked, bev_tracks)
            Tracker->>Tracker: appendTracksToBevInput(lost, Lost, bev_tracks)
            Tracker->>Renderer: renderFrame(bev_tracks, bev_frame_index)
        deactivate Tracker
        Tracker-->>Main: vector<STrack> output_tracks

        Main->>Main: draw 2D boxes from output_tracks
        Main->>Main: write RGB frame if needed
    end

    Main-->>User: demo finished, BEV images in bev_outputs/
Loading

Flow diagram for BEV data propagation from detections to rendered images

flowchart LR
    A["Detector outputs
    vector<Object>
    rect, label, prob, gp"] --> B["BYTETracker::update
    constructs STrack
    with tlwh, score,
    class_id, gp"]

    B --> C["BYTETracker
    tracking loop
    updates tracked_stracks
    and lost_stracks"]

    C --> D["BirdEyeViewTracker::update
    calls base update,
    then collects:
    get_tracked_stracks()
    get_lost_stracks()"]

    D --> E["appendTracksToBevInput
    build vector<BEVTrackPoint>
    from STrack.track_id,
    class_id, gp, state"]

    E --> F["BirdEyeViewRenderer::renderFrame
    buildCanvas + drawGridAndAxes"]

    F --> G["worldToImage(x, y)
    map meters -> pixels"]

    G --> H["drawTrackPoint
    circle / cross,
    id + class label"]

    H --> I["cv::imwrite
    bev_outputs/bev_XXXXXX.jpg"]
Loading

File-Level Changes

Change Details Files
Extend tracking model types to carry ground-point and class metadata through Objects into STrack instances.
  • Add gp (cv::Point3f) with default value to Object and keep existing rect/label/prob fields
  • Change STrack constructor to accept class_id and gp (with defaults) and assign them in the constructor
  • Store class_id and gp members in STrack and propagate them in re_activate and update so track state retains metadata
  • Update BYTETracker::update to construct STrack from detection tlwh, score, label, and gp
deploy/TensorRT/cpp/include/BYTETracker.h
deploy/TensorRT/cpp/include/STrack.h
deploy/TensorRT/cpp/src/STrack.cpp
deploy/TensorRT/cpp/src/BYTETracker.cpp
Make BYTETracker subclassable and expose internal tracked/lost track lists needed by the BEV tracker.
  • Make BYTETracker destructor virtual to allow safe polymorphic deletion
  • Make BYTETracker::update virtual so subclasses can extend tracking while reusing core logic
  • Add const accessors get_tracked_stracks() and get_lost_stracks() returning internal vectors
deploy/TensorRT/cpp/include/BYTETracker.h
deploy/TensorRT/cpp/src/BYTETracker.cpp
Introduce a BirdEyeViewRenderer utility that converts world coordinates to a BEV canvas and renders tracked and lost tracks per frame.
  • Define BEVTrackPoint and BEVRenderedTrackInfo structs to describe input/output metadata for BEV rendering
  • Implement BirdEyeViewRenderer with world range config, grid/axes drawing, worldToImage mapping, per-frame image writing, and track-color management
  • Add internal directory-creation helper and latestRenderedTracks() accessor for inspection/testing
deploy/TensorRT/cpp/include/BirdEyeViewRenderer.h
deploy/TensorRT/cpp/src/BirdEyeViewRenderer.cpp
Add BirdEyeViewTracker as a BYTETracker subclass that feeds tracked and lost STrack objects into the BEV renderer each frame.
  • Subclass BYTETracker in BirdEyeViewTracker and construct an internal BirdEyeViewRenderer with configurable output directory, ranges, and canvas params
  • Override update to delegate to BYTETracker::update then build BEVTrackPoint list from tracked and lost STracks and call renderFrame with a monotonically increasing frame index
  • Provide helper appendTracksToBevInput to map STrack fields to BEVTrackPoint and expose renderer() accessors
deploy/TensorRT/cpp/include/BirdEyeViewTracker.h
deploy/TensorRT/cpp/src/BirdEyeViewTracker.cpp
Wire BirdEyeViewTracker into the TensorRT C++ demo and document BEV integration and gp usage.
  • Switch demo main from including BYTETracker.h to BirdEyeViewTracker.h and instantiate BirdEyeViewTracker with example BEV config instead of BYTETracker
  • Add BEV_INTEGRATION.md describing how to construct BirdEyeViewTracker, how Object now carries label and gp, and how gp flows into STrack for BEV rendering
deploy/TensorRT/cpp/src/bytetrack.cpp
deploy/TensorRT/cpp/BEV_INTEGRATION.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In BirdEyeViewTracker and the BEV structs you treat track_state as an int and pass expected_state as int; consider using the TrackState enum type directly throughout to improve type safety and readability.
  • BirdEyeViewRenderer::ensureOutputDirectory silently returns false on failure and renderFrame ignores that result; consider surfacing a clear error (e.g., throw or log) so failures to create the output directory are easier to detect.
  • BYTETracker::get_tracked_stracks / get_lost_stracks expose internal storage by const reference; if there’s any risk of callers caching these references across updates, consider returning a copy or documenting that they must be used only within the same update cycle.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `BirdEyeViewTracker` and the BEV structs you treat `track_state` as an `int` and pass `expected_state` as `int`; consider using the `TrackState` enum type directly throughout to improve type safety and readability.
- `BirdEyeViewRenderer::ensureOutputDirectory` silently returns `false` on failure and `renderFrame` ignores that result; consider surfacing a clear error (e.g., throw or log) so failures to create the output directory are easier to detect.
- `BYTETracker::get_tracked_stracks` / `get_lost_stracks` expose internal storage by const reference; if there’s any risk of callers caching these references across updates, consider returning a copy or documenting that they must be used only within the same update cycle.

## Individual Comments

### Comment 1
<location path="deploy/TensorRT/cpp/include/BirdEyeViewTracker.h" line_range="24-25" />
<code_context>
+    BirdEyeViewRenderer& renderer();
+
+private:
+    void appendTracksToBevInput(const vector<STrack>& tracks,
+                                int expected_state,
+                                vector<BEVTrackPoint>& bev_tracks) const;
+
</code_context>
<issue_to_address>
**suggestion:** Use `TrackState` instead of `int` for `expected_state` to preserve type safety.

Because `STrack::state` is a `TrackState` enum, using `int` here weakens type safety and allows unrelated values. Taking `expected_state` as `TrackState` would clarify the API, prevent implicit conversions, and let `appendTracksToBevInput` compare directly against a `TrackState`.

Suggested implementation:

```c
    BirdEyeViewRenderer& renderer();

private:
    void appendTracksToBevInput(const vector<STrack>& tracks,
                                TrackState expected_state,
                                vector<BEVTrackPoint>& bev_tracks) const;

```

1. In the corresponding implementation file (likely `BirdEyeViewTracker.cpp`), update the definition of `appendTracksToBevInput` to use `TrackState expected_state` instead of `int expected_state`.
2. Ensure all call sites that pass `expected_state` are updated to pass a `TrackState` (e.g., `TrackState::Tracked`) rather than raw integers.
3. If `TrackState` is not already visible in the header, confirm it is brought in via the included headers (`BYTETracker.h` / `STrack` definition). If not, add the appropriate include for the `TrackState` enum.
</issue_to_address>

### Comment 2
<location path="deploy/TensorRT/cpp/include/BirdEyeViewRenderer.h" line_range="17" />
<code_context>
+    std::string class_label;
+    cv::Point3f gp = cv::Point3f(0.0F, 0.0F, 0.0F);
+    bool is_active = true;
+    int track_state = TrackState::Tracked;
+};
+
</code_context>
<issue_to_address>
**suggestion:** Consider storing `track_state` as `TrackState` instead of `int` in BEV structs.

`BEVTrackPoint` and `BEVRenderedTrackInfo` store `track_state` as `int` even though they represent `TrackState`. Using the `TrackState` enum directly would improve type safety and readability; only cast to `int` at serialization/logging boundaries if needed.

Suggested implementation:

```c
struct BEVTrackPoint {
    int track_id = -1;
    int class_id = -1;
    std::string class_label;
    cv::Point3f gp = cv::Point3f(0.0F, 0.0F, 0.0F);
    bool is_active = true;
    TrackState track_state = TrackState::Tracked;
};

```

```c
struct BEVRenderedTrackInfo {
    int track_id = -1;
    int class_id = -1;
    std::string class_label;
    cv::Point3f gp = cv::Point3f(0.0F, 0.0F, 0.0F);
    bool is_active = true;
    TrackState track_state = TrackState::Tracked;
};

```

1. If `BEVRenderedTrackInfo` or `BEVTrackPoint` are defined slightly differently in your local copy (e.g., different member order or missing default initializer), adjust the `SEARCH` blocks to match the exact existing definitions while still changing `int track_state` to `TrackState track_state`.
2. Any serialization, logging, or external interface code that expects `int track_state` should now explicitly cast `track_state` (e.g., `static_cast<int>(track_state)`) at the boundary as needed.
</issue_to_address>

### Comment 3
<location path="deploy/TensorRT/cpp/src/BirdEyeViewRenderer.cpp" line_range="56-57" />
<code_context>
+        drawTrackPoint(canvas, tracks[i], frame_index);
+    }
+
+    const std::string output_path = frameOutputPath(frame_index);
+    cv::imwrite(output_path, canvas);
+}
+
</code_context>
<issue_to_address>
**suggestion:** Handle or surface possible `cv::imwrite` failures when writing BEV frames.

`cv::imwrite` can fail (permissions, disk full, etc.) and is currently ignored. Since writing the BEV frame is a key side-effect, consider checking the return value and logging or otherwise surfacing failures instead of silently dropping outputs.

Suggested implementation:

```cpp
void BirdEyeViewRenderer::renderFrame(const std::vector<BEVTrackPoint>& tracks, int frame_index) {
    cv::Mat canvas = buildCanvas();
    latest_rendered_tracks_.clear();
    latest_rendered_tracks_.reserve(tracks.size());

    for (size_t i = 0; i < tracks.size(); ++i) {
        drawTrackPoint(canvas, tracks[i], frame_index);
    }

    const std::string output_path = frameOutputPath(frame_index);
    if (!cv::imwrite(output_path, canvas)) {
        std::cerr << "BirdEyeViewRenderer: failed to write BEV frame to '" << output_path << "'" << std::endl;
    }


#include <cerrno>

```

```cpp
#include <sstream>
#include <sys/stat.h>
#include <sys/types.h>

#include <algorithm>
#include <iostream>

```

If your codebase has a central logging facility (e.g., a `LOG(ERROR)` macro or similar), replace the `std::cerr` line with the appropriate logging call to keep logging consistent across the project.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread deploy/TensorRT/cpp/include/BirdEyeViewTracker.h Outdated
std::string class_label;
cv::Point3f gp = cv::Point3f(0.0F, 0.0F, 0.0F);
bool is_active = true;
int track_state = TrackState::Tracked;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider storing track_state as TrackState instead of int in BEV structs.

BEVTrackPoint and BEVRenderedTrackInfo store track_state as int even though they represent TrackState. Using the TrackState enum directly would improve type safety and readability; only cast to int at serialization/logging boundaries if needed.

Suggested implementation:

struct BEVTrackPoint {
    int track_id = -1;
    int class_id = -1;
    std::string class_label;
    cv::Point3f gp = cv::Point3f(0.0F, 0.0F, 0.0F);
    bool is_active = true;
    TrackState track_state = TrackState::Tracked;
};
struct BEVRenderedTrackInfo {
    int track_id = -1;
    int class_id = -1;
    std::string class_label;
    cv::Point3f gp = cv::Point3f(0.0F, 0.0F, 0.0F);
    bool is_active = true;
    TrackState track_state = TrackState::Tracked;
};
  1. If BEVRenderedTrackInfo or BEVTrackPoint are defined slightly differently in your local copy (e.g., different member order or missing default initializer), adjust the SEARCH blocks to match the exact existing definitions while still changing int track_state to TrackState track_state.
  2. Any serialization, logging, or external interface code that expects int track_state should now explicitly cast track_state (e.g., static_cast<int>(track_state)) at the boundary as needed.

Comment on lines +56 to +57
const std::string output_path = frameOutputPath(frame_index);
cv::imwrite(output_path, canvas);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Handle or surface possible cv::imwrite failures when writing BEV frames.

cv::imwrite can fail (permissions, disk full, etc.) and is currently ignored. Since writing the BEV frame is a key side-effect, consider checking the return value and logging or otherwise surfacing failures instead of silently dropping outputs.

Suggested implementation:

void BirdEyeViewRenderer::renderFrame(const std::vector<BEVTrackPoint>& tracks, int frame_index) {
    cv::Mat canvas = buildCanvas();
    latest_rendered_tracks_.clear();
    latest_rendered_tracks_.reserve(tracks.size());

    for (size_t i = 0; i < tracks.size(); ++i) {
        drawTrackPoint(canvas, tracks[i], frame_index);
    }

    const std::string output_path = frameOutputPath(frame_index);
    if (!cv::imwrite(output_path, canvas)) {
        std::cerr << "BirdEyeViewRenderer: failed to write BEV frame to '" << output_path << "'" << std::endl;
    }


#include <cerrno>
#include <sstream>
#include <sys/stat.h>
#include <sys/types.h>

#include <algorithm>
#include <iostream>

If your codebase has a central logging facility (e.g., a LOG(ERROR) macro or similar), replace the std::cerr line with the appropriate logging call to keep logging consistent across the project.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d48d6af90

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +124 to +125
for (int x = static_cast<int>(std::ceil(world_range_.min_x)); x <= static_cast<int>(std::floor(world_range_.max_x));
x += grid_step_m_) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard grid_step_m to prevent infinite render loops

drawGridAndAxes increments both grid loops by grid_step_m_ without validating that it is positive. If a caller constructs BirdEyeViewRenderer/BirdEyeViewTracker with grid_step_m <= 0 (a valid value under the current API), the for loop never makes progress and renderFrame hangs the tracking pipeline on the first frame.

Useful? React with 👍 / 👎.

const float usable_h = static_cast<float>(canvas_height_ - 2 * padding_px_);

const float nx = (x_m - world_range_.min_x) / x_span;
const float ny = (y_m - world_range_.min_y) / y_span;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate y-range before dividing by span

worldToImage divides by y_span but the constructor allows y_min_m == y_max_m, which makes y_span zero and produces invalid coordinates (inf/NaN) during rendering. This can surface as corrupted BEV output or OpenCV drawing failures when users pass a degenerate y-range in configuration.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant