MayaFlux 0.5.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
7
8namespace P = MayaFlux::Parallel;
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 min_eig = trace * 0.5F - disc;
90
91 if (min_eig < eigen_threshold)
92 return { .position = pt, .error = 1.0F, .tracked = false };
93
94 const float inv_det = 1.0F / (det + 1e-10F);
95
96 float vx = 0.0F, vy = 0.0F;
97
98 for (uint32_t iter = 0; iter < max_iterations; ++iter) {
99 float bx = 0.0F, by = 0.0F;
100
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;
108
109 const float gx = dx[ni];
110 const float gy = dy[ni];
111
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;
116
117 bx += it * gx;
118 by += it * gy;
119 }
120 }
121
122 const float dvx = (syy * bx - sxy * by) * inv_det;
123 const float dvy = (sxx * by - sxy * bx) * inv_det;
124
125 vx += dvx;
126 vy += dvy;
127
128 if (dvx * dvx + dvy * dvy < 0.03F * 0.03F)
129 break;
130 }
131
132 const float nx = px + vx;
133 const float ny = py + vy;
134
135 if (nx < 0.0F || ny < 0.0F
136 || nx >= pw || ny >= ph)
137 return { .position = pt, .error = 1.0F, .tracked = false };
138
139 float error = 0.0F;
140 int32_t count = 0;
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,
149 nx + wx, ny + wy);
150 const float diff = p_val - c_val;
151 error += diff * diff;
152 ++count;
153 }
154 }
155 error = std::sqrt(error / static_cast<float>(count));
156
157 if (error > error_threshold)
158 return { .position = pt, .error = error, .tracked = false };
159
160 return {
161 .position = {
162 (nx + 0.5F) / pw,
163 (ny + 0.5F) / ph },
164 .error = error,
165 .tracked = true,
166 };
167 }
168
169} // namespace
170
171std::vector<TrackResult> track_keypoints(
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)
180{
181 auto grad = sobel(prev_gray, w, h);
182 return track_keypoints(prev_gray, curr_gray,
183 grad.dx, grad.dy,
184 w, h, prev_points,
185 window_radius, max_iterations,
186 eigen_threshold, error_threshold);
187}
188
189std::vector<TrackResult> track_keypoints(
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)
200{
201 const size_t np = prev_points.size();
202 std::vector<TrackResult> results(np);
203
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(),
207 [&](size_t i) {
208 results[i] = lk_point(
209 prev_gray, curr_gray,
210 grad_dx, grad_dy,
211 w, h,
212 prev_points[i],
213 window_radius,
214 max_iterations,
215 eigen_threshold,
216 error_threshold);
217 });
218
219 return results;
220}
221
222} // 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:28
Sparse optical flow via Lucas-Kanade tracker.
size_t count
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:66
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.