18 float sample(std::span<const float> img, uint32_t w, uint32_t
h,
21 x = std::clamp(x, 0.0F,
static_cast<float>(w) - 1.001F);
22 y = std::clamp(y, 0.0F,
static_cast<float>(
h) - 1.001F);
24 const auto ix =
static_cast<int32_t
>(x);
25 const auto iy =
static_cast<int32_t
>(y);
26 const float fx = x -
static_cast<float>(ix);
27 const float fy = y -
static_cast<float>(iy);
29 const int32_t ix1 = std::min(ix + 1,
static_cast<int32_t
>(w) - 1);
30 const int32_t iy1 = std::min(iy + 1,
static_cast<int32_t
>(
h) - 1);
32 const float v00 = img[
static_cast<size_t>(iy) * w + ix];
33 const float v10 = img[
static_cast<size_t>(iy) * w + ix1];
34 const float v01 = img[
static_cast<size_t>(iy1) * w + ix];
35 const float v11 = img[
static_cast<size_t>(iy1) * w + ix1];
37 return v00 * (1.0F - fx) * (1.0F - fy)
38 + v10 * fx * (1.0F - fy)
39 + v01 * (1.0F - fx) * fy
48 std::span<const float> prev,
49 std::span<const float> curr,
50 std::span<const float> dx,
51 std::span<const float> dy,
52 uint32_t w, uint32_t
h,
54 uint32_t window_radius,
55 uint32_t max_iterations,
56 float eigen_threshold,
57 float error_threshold)
59 const auto pw =
static_cast<float>(w);
60 const auto ph =
static_cast<float>(
h);
62 float px = pt.x * pw - 0.5F;
63 float py = pt.y * ph - 0.5F;
65 const auto r =
static_cast<int32_t
>(window_radius);
67 float sxx = 0.0F, syy = 0.0F, sxy = 0.0F;
69 for (int32_t wy = -r; wy <= r; ++wy) {
70 for (int32_t wx = -r; wx <= r; ++wx) {
71 const float sx = std::clamp(px + wx, 0.0F, pw - 1.0F);
72 const float sy = std::clamp(py + wy, 0.0F, ph - 1.0F);
73 const auto ix =
static_cast<int32_t
>(sx);
74 const auto iy =
static_cast<int32_t
>(sy);
75 const size_t ni =
static_cast<size_t>(iy) * w + ix;
77 const float gx = dx[ni];
78 const float gy = dy[ni];
85 const float trace = sxx + syy;
86 const float det = sxx * syy - sxy * sxy;
87 const float disc = std::sqrt(std::max(0.0F,
88 trace * trace * 0.25F - det));
89 const float min_eig = trace * 0.5F - disc;
91 if (min_eig < eigen_threshold)
92 return { .position = pt, .error = 1.0F, .tracked =
false };
94 const float inv_det = 1.0F / (det + 1e-10F);
96 float vx = 0.0F, vy = 0.0F;
98 for (uint32_t iter = 0; iter < max_iterations; ++iter) {
99 float bx = 0.0F, by = 0.0F;
101 for (int32_t wy = -r; wy <= r; ++wy) {
102 for (int32_t wx = -r; wx <= r; ++wx) {
103 const float sx = std::clamp(px + wx, 0.0F, pw - 1.0F);
104 const float sy = std::clamp(py + wy, 0.0F, ph - 1.0F);
105 const auto ix =
static_cast<int32_t
>(sx);
106 const auto iy =
static_cast<int32_t
>(sy);
107 const size_t ni =
static_cast<size_t>(iy) * w + ix;
109 const float gx = dx[ni];
110 const float gy = dy[ni];
112 const float p_val = prev[ni];
113 const float c_val = sample(curr, w,
h,
114 px + wx + vx, py + wy + vy);
115 const float it = p_val - c_val;
122 const float dvx = (syy * bx - sxy * by) * inv_det;
123 const float dvy = (sxx * by - sxy * bx) * inv_det;
128 if (dvx * dvx + dvy * dvy < 0.03F * 0.03F)
132 const float nx = px + vx;
133 const float ny = py + vy;
135 if (nx < 0.0F || ny < 0.0F
136 || nx >= pw || ny >= ph)
137 return { .position = pt, .error = 1.0F, .tracked =
false };
141 for (int32_t wy = -r; wy <= r; ++wy) {
142 for (int32_t wx = -r; wx <= r; ++wx) {
143 const float sx = std::clamp(px + wx, 0.0F, pw - 1.0F);
144 const float sy = std::clamp(py + wy, 0.0F, ph - 1.0F);
145 const auto ix =
static_cast<int32_t
>(sx);
146 const auto iy =
static_cast<int32_t
>(sy);
147 const float p_val = prev[
static_cast<size_t>(iy) * w + ix];
148 const float c_val = sample(curr, w,
h,
150 const float diff = p_val - c_val;
151 error += diff * diff;
155 error = std::sqrt(error /
static_cast<float>(
count));
157 if (error > error_threshold)
158 return { .position = pt, .error =
error, .tracked =
false };
172 std::span<const float> prev_gray,
173 std::span<const float> curr_gray,
174 uint32_t w, uint32_t
h,
175 std::span<const glm::vec2> prev_points,
176 uint32_t window_radius,
177 uint32_t max_iterations,
178 float eigen_threshold,
179 float error_threshold)
181 auto grad =
sobel(prev_gray, w,
h);
185 window_radius, max_iterations,
186 eigen_threshold, error_threshold);
190 std::span<const float> prev_gray,
191 std::span<const float> curr_gray,
192 std::span<const float> grad_dx,
193 std::span<const float> grad_dy,
194 uint32_t w, uint32_t
h,
195 std::span<const glm::vec2> prev_points,
196 uint32_t window_radius,
197 uint32_t max_iterations,
198 float eigen_threshold,
199 float error_threshold)
201 const size_t np = prev_points.size();
202 std::vector<TrackResult> results(np);
204 P::for_each(P::par_unseq,
205 std::views::iota(
size_t { 0 }, np).begin(),
206 std::views::iota(
size_t { 0 }, np).end(),
208 results[i] = lk_point(
209 prev_gray, curr_gray,
Gradient and edge detection on normalised float image spans.
2D spatial filtering on normalised float image spans.
Sparse optical flow via Lucas-Kanade tracker.
void error(Component component, Context context, std::source_location location, std::string_view message)
Log an error message and optionally throw an exception.
void sobel(std::span< const float > gray, std::span< float > dx, std::span< float > dy, std::span< float > tmp, uint32_t w, uint32_t h)
Sobel gradient writing dx and dy into caller-supplied buffers.
std::vector< TrackResult > track_keypoints(std::span< const float > prev_gray, std::span< const float > curr_gray, uint32_t w, uint32_t h, std::span< const glm::vec2 > prev_points, uint32_t window_radius, uint32_t max_iterations, float eigen_threshold, float error_threshold)
Track keypoints from prev_gray to curr_gray via Lucas-Kanade.