CARLA
Loading...
Searching...
No Matches
OpticalFlowEncoding.h
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
7#pragma once
8
9#include <algorithm>
10#include <array>
11#include <cmath>
12#include <cstdint>
13#include <vector>
14
15namespace carla {
16namespace ros2 {
17
18// HSV colour-wheel encoding for an optical-flow pixel:
19// hue = atan2(vy, vx) + pi, mapped to [0, 360) degrees
20// sat = 1
21// value = clamp(a * log(magnitude + shift), 0, 1)
22// where shift = 0.999 and a = 1 / log(0.1 + shift), so the intensity reaches 1
23// (full brightness) when magnitude == 0.1 and stays clamped at 1 above that.
24// Small flows therefore brighten quickly while large ones plateau, matching
25// the legacy ue5-dev wire format that existing ROS 2 subscribers depend on.
26//
27// Output is BGRA8: B, G, R, A with the alpha byte deliberately set to 0 to
28// match the existing on-the-wire format ue5-dev clients are subscribed to.
29//
30// M_PI is used here, not std::numbers::pi_v, because this header is consumed
31// by the carla-ros2-native ExternalProject in Ros2Native/, which does not
32// configure CMAKE_CXX_STANDARD and therefore defaults to C++17 where <numbers>
33// is unavailable.
34[[nodiscard]] inline std::array<uint8_t, 4> EncodeFlowPixelToBgra(
35 float vx, float vy) noexcept {
36 constexpr float rad2ang = 360.0f / (2.0f * static_cast<float>(M_PI));
37 float angle = 180.0f + std::atan2(vy, vx) * rad2ang;
38 if (angle < 0.0f) {
39 angle = 360.0f + angle;
40 }
41 angle = std::fmod(angle, 360.0f);
42
43 const float norm = std::sqrt(vx * vx + vy * vy);
44 constexpr float shift = 0.999f;
45 const float a = 1.0f / std::log(0.1f + shift);
46 const float intensity = std::clamp(a * std::log(norm + shift), 0.0f, 1.0f);
47
48 const float h_60 = angle * (1.0f / 60.0f);
49 const float saturation = 1.0f;
50 const float value = intensity;
51 const float c = value * saturation;
52 const float x = c * (1.0f - std::abs(std::fmod(h_60, 2.0f) - 1.0f));
53 const float m = value - c;
54
55 float r = 0.0f;
56 float g = 0.0f;
57 float b = 0.0f;
58 switch (static_cast<unsigned int>(h_60)) {
59 case 0:
60 r = c;
61 g = x;
62 b = 0.0f;
63 break;
64 case 1:
65 r = x;
66 g = c;
67 b = 0.0f;
68 break;
69 case 2:
70 r = 0.0f;
71 g = c;
72 b = x;
73 break;
74 case 3:
75 r = 0.0f;
76 g = x;
77 b = c;
78 break;
79 case 4:
80 r = x;
81 g = 0.0f;
82 b = c;
83 break;
84 case 5:
85 r = c;
86 g = 0.0f;
87 b = x;
88 break;
89 default:
90 r = 1.0f;
91 g = 1.0f;
92 b = 1.0f;
93 break;
94 }
95
96 return {
97 static_cast<uint8_t>((b + m) * 255.0f),
98 static_cast<uint8_t>((g + m) * 255.0f),
99 static_cast<uint8_t>((r + m) * 255.0f),
100 static_cast<uint8_t>(0),
101 };
102}
103
104// Converts a height*width buffer of (vx, vy) float pairs into a height*width*4
105// BGRA uint8 buffer suitable for sensor_msgs::Image with encoding "bgra8".
106[[nodiscard]] inline std::vector<uint8_t> EncodeFlowImageToBgra(
107 uint32_t height, uint32_t width, const float *data) {
108 const size_t pixel_count = static_cast<size_t>(height) * static_cast<size_t>(width);
109 std::vector<uint8_t> out;
110 out.resize(pixel_count * 4u);
111 for (size_t i = 0; i < pixel_count; ++i) {
112 const float vx = data[i * 2u];
113 const float vy = data[i * 2u + 1u];
114 const auto bgra = EncodeFlowPixelToBgra(vx, vy);
115 out[i * 4u + 0u] = bgra[0];
116 out[i * 4u + 1u] = bgra[1];
117 out[i * 4u + 2u] = bgra[2];
118 out[i * 4u + 3u] = bgra[3];
119 }
120 return out;
121}
122
123} // namespace ros2
124} // namespace carla
float intensity
float x
std::vector< uint8_t > EncodeFlowImageToBgra(uint32_t height, uint32_t width, const float *data)
std::array< uint8_t, 4 > EncodeFlowPixelToBgra(float vx, float vy) noexcept
This file contains definitions of common data structures used in traffic manager.