MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
OpticalFlow.cpp
Go to the documentation of this file.
1#include "OpticalFlow.hpp"
2
3#include "Gradient.hpp"
4#include "ImageFilter.hpp"
5
6#include <execution>
7
8#include <utility>
9
11
12namespace {
13
14 // =========================================================================
15 // Bilinear sample with clamp-to-edge
16 // =========================================================================
17
18 float sample(std::span<const float> img, uint32_t w, uint32_t h,
19 float x, float y)
20 {
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);
23
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);
28
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);
31
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];
36
37 return v00 * (1.0F - fx) * (1.0F - fy)
38 + v10 * fx * (1.0F - fy)
39 + v01 * (1.0F - fx) * fy
40 + v11 * fx * fy;
41 }
42
43 // =========================================================================
44 // Lucas-Kanade for a single point
45 // =========================================================================
46
47 TrackResult lk_point(
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,
53 glm::vec2 pt,
54 uint32_t window_radius,
55 uint32_t max_iterations,
56 float eigen_threshold,
57 float error_threshold)
58 {
59 const auto pw = static_cast<float>(w);
60 const auto ph = static_cast<float>(h);
61
62 float px = pt.x * pw - 0.5F;
63 float py = pt.y * ph - 0.5F;
64
65 const auto r = static_cast<int32_t>(window_radius);
66
67 float sxx = 0.0F, syy = 0.0F, sxy = 0.0F;
68
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;
76
77 const float gx = dx[ni];
78 const float gy = dy[ni];
79 sxx += gx * gx;
80 syy += gy * gy;
81 sxy += gx * gy;
82 }
83 }
84
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 side = 2.0F * static_cast<float>(window_radius) + 1.0F;
90 const float min_eig = (trace * 0.5F - disc) / (side * side);
91
92 if (min_eig < eigen_threshold)
93 return { .position = pt, .error = 1.0F, .tracked = false };
94
95 const float inv_det = 1.0F / (det + 1e-10F);
96
97 float vx = 0.0F, vy = 0.0F;
98
99 for (uint32_t iter = 0; iter < max_iterations; ++iter) {
100 float bx = 0.0F, by = 0.0F;
101
102 for (int32_t wy = -r; wy <= r; ++wy) {
103 for (int32_t wx = -r; wx <= r; ++wx) {
104 const float sx = std::clamp(px + wx, 0.0F, pw - 1.0F);
105 const float sy = std::clamp(py + wy, 0.0F, ph - 1.0F);
106 const auto ix = static_cast<int32_t>(sx);
107 const auto iy = static_cast<int32_t>(sy);
108 const size_t ni = static_cast<size_t>(iy) * w + ix;
109
110 const float gx = dx[ni];
111 const float gy = dy[ni];
112
113 const float p_val = prev[ni];
114 const float c_val = sample(curr, w, h,
115 px + wx + vx, py + wy + vy);
116 const float it = p_val - c_val;
117
118 bx += it * gx;
119 by += it * gy;
120 }
121 }
122
123 const float dvx = (syy * bx - sxy * by) * inv_det;
124 const float dvy = (sxx * by - sxy * bx) * inv_det;
125
126 vx += dvx;
127 vy += dvy;
128
129 if (dvx * dvx + dvy * dvy < 0.03F * 0.03F)
130 break;
131 }
132
133 const float nx = px + vx;
134 const float ny = py + vy;
135
136 if (nx < 0.0F || ny < 0.0F
137 || nx >= pw || ny >= ph)
138 return { .position = pt, .error = 1.0F, .tracked = false };
139
140 float error = 0.0F;
141 int32_t count = 0;
142 for (int32_t wy = -r; wy <= r; ++wy) {
143 for (int32_t wx = -r; wx <= r; ++wx) {
144 const float sx = std::clamp(px + wx, 0.0F, pw - 1.0F);
145 const float sy = std::clamp(py + wy, 0.0F, ph - 1.0F);
146 const auto ix = static_cast<int32_t>(sx);
147 const auto iy = static_cast<int32_t>(sy);
148 const float p_val = prev[static_cast<size_t>(iy) * w + ix];
149 const float c_val = sample(curr, w, h,
150 nx + wx, ny + wy);
151 const float diff = p_val - c_val;
152 error += diff * diff;
153 ++count;
154 }
155 }
156 error = std::sqrt(error / static_cast<float>(count));
157
158 if (error > error_threshold)
159 return { .position = pt, .error = error, .tracked = false };
160
161 return {
162 .position = {
163 (nx + 0.5F) / pw,
164 (ny + 0.5F) / ph },
165 .error = error,
166 .tracked = true,
167 };
168 }
169
170} // namespace
171
172std::vector<TrackResult> track_keypoints(
173 std::span<const float> prev_gray,
174 std::span<const float> curr_gray,
175 uint32_t w, uint32_t h,
176 std::span<const glm::vec2> prev_points,
177 uint32_t window_radius,
178 uint32_t max_iterations,
179 float eigen_threshold,
180 float error_threshold,
181 float forward_backward_threshold)
182{
183 auto grad = sobel(prev_gray, w, h);
184 return track_keypoints(prev_gray, curr_gray,
185 grad.dx, grad.dy,
186 w, h, prev_points,
187 window_radius, max_iterations,
188 eigen_threshold, error_threshold,
189 forward_backward_threshold);
190}
191
192std::vector<TrackResult> track_keypoints(
193 std::span<const float> prev_gray,
194 std::span<const float> curr_gray,
195 std::span<const float> grad_dx,
196 std::span<const float> grad_dy,
197 uint32_t w, uint32_t h,
198 std::span<const glm::vec2> prev_points,
199 uint32_t window_radius,
200 uint32_t max_iterations,
201 float eigen_threshold,
202 float error_threshold,
203 float forward_backward_threshold)
204{
205 const size_t np = prev_points.size();
206 std::vector<TrackResult> results(np);
207 std::vector<float> curr_dx;
208 std::vector<float> curr_dy;
209 if (forward_backward_threshold > 0.0F) {
210 auto grad = sobel(curr_gray, w, h);
211 curr_dx = std::move(grad.dx);
212 curr_dy = std::move(grad.dy);
213 }
214
215 std::for_each(std::execution::par_unseq,
216 std::views::iota(size_t { 0 }, np).begin(),
217 std::views::iota(size_t { 0 }, np).end(),
218 [&](size_t i) {
219 auto result = lk_point(
220 prev_gray, curr_gray,
221 grad_dx, grad_dy,
222 w, h,
223 prev_points[i],
224 window_radius,
225 max_iterations,
226 eigen_threshold,
227 error_threshold);
228
229 if (forward_backward_threshold > 0.0F && result.tracked) {
230 const auto reverse = lk_point(
231 curr_gray, prev_gray,
232 curr_dx, curr_dy,
233 w, h,
234 result.position,
235 window_radius,
236 max_iterations,
237 eigen_threshold,
238 error_threshold);
239 const glm::vec2 error_px = (reverse.position - prev_points[i])
240 * glm::vec2(static_cast<float>(w), static_cast<float>(h));
241 if (!reverse.tracked || glm::length(error_px) > forward_backward_threshold) {
242 result.position = prev_points[i];
243 result.error = 1.0F;
244 result.tracked = false;
245 }
246 }
247 result.previous = prev_points[i];
248 results[i] = result;
249 });
250
251 return results;
252}
253
254} // namespace MayaFlux::Kinesis::Vision
Gradient and edge detection on normalised float image spans.
2D spatial filtering on normalised float image spans.
uint32_t h
Definition InkPress.cpp:29
Sparse optical flow via Lucas-Kanade tracker.
size_t count
uint32_t ph
uint32_t py
uint32_t pw
uint32_t px
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.
Definition Gradient.cpp:64
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, float forward_backward_threshold)
Track keypoints from prev_gray to curr_gray via Lucas-Kanade.