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 side = 2.0F *
static_cast<float>(window_radius) + 1.0F;
90 const float min_eig = (trace * 0.5F - disc) / (side * side);
92 if (min_eig < eigen_threshold)
93 return { .position = pt, .error = 1.0F, .tracked =
false };
95 const float inv_det = 1.0F / (det + 1e-10F);
97 float vx = 0.0F, vy = 0.0F;
99 for (uint32_t iter = 0; iter < max_iterations; ++iter) {
100 float bx = 0.0F, by = 0.0F;
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;
110 const float gx = dx[ni];
111 const float gy = dy[ni];
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;
123 const float dvx = (syy * bx - sxy * by) * inv_det;
124 const float dvy = (sxx * by - sxy * bx) * inv_det;
129 if (dvx * dvx + dvy * dvy < 0.03F * 0.03F)
133 const float nx =
px + vx;
134 const float ny =
py + vy;
136 if (nx < 0.0F || ny < 0.0F
137 || nx >=
pw || ny >=
ph)
138 return { .position = pt, .error = 1.0F, .tracked =
false };
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,
151 const float diff = p_val - c_val;
152 error += diff * diff;
156 error = std::sqrt(error /
static_cast<float>(
count));
158 if (error > error_threshold)
159 return { .position = pt, .error =
error, .tracked =
false };
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)
183 auto grad =
sobel(prev_gray, w,
h);
187 window_radius, max_iterations,
188 eigen_threshold, error_threshold,
189 forward_backward_threshold);
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)
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);
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(),
219 auto result = lk_point(
220 prev_gray, curr_gray,
229 if (forward_backward_threshold > 0.0F && result.tracked) {
230 const auto reverse = lk_point(
231 curr_gray, prev_gray,
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];
244 result.tracked =
false;
247 result.previous = prev_points[i];
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, float forward_backward_threshold)
Track keypoints from prev_gray to curr_gray via Lucas-Kanade.