MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Gradient.cpp
Go to the documentation of this file.
1#include "Gradient.hpp"
2
3#include "ImageFilter.hpp"
4#include "KernelSpec.hpp"
5
6#include <execution>
7
8#include <Eigen/Core>
9
11
13
14namespace {
15
16 GradientResult gradient_from_kernels(
17 std::span<const float> gray, uint32_t w, uint32_t h,
18 std::span<const float> kx, std::span<const float> ky)
19 {
20 const size_t n = static_cast<size_t>(w) * h;
21
22 static constexpr float smooth[] = { 1.0F, 2.0F, 1.0F };
23 auto dx = filter_separable(gray, w, h, kx, smooth);
24 auto dy = filter_separable(gray, w, h, smooth, ky);
25
26 std::vector<float> mag(n);
27 std::vector<float> ang(n);
28
29 float max_mag = 0.0F;
30
31 std::for_each(std::execution::par_unseq,
32 std::views::iota(size_t { 0 }, n).begin(),
33 std::views::iota(size_t { 0 }, n).end(),
34 [&](size_t i) {
35 mag[i] = std::sqrt(dx[i] * dx[i] + dy[i] * dy[i]);
36 ang[i] = std::atan2(dy[i], dx[i]);
37 });
38
39 const float peak = *std::ranges::max_element(mag);
40 if (peak > 0.0F) {
41 const float inv = 1.0F / peak;
42 std::transform(std::execution::par_unseq, mag.begin(), mag.end(), mag.begin(),
43 [inv](float v) { return v * inv; });
44 }
45
46 return { .dx = std::move(dx), .dy = std::move(dy), .magnitude = std::move(mag), .angle = std::move(ang) };
47 }
48
49 void gradient_into_buffers(
50 std::span<const float> gray,
51 std::span<float> dx,
52 std::span<float> dy,
53 std::span<float> tmp,
54 uint32_t w, uint32_t h,
55 std::span<const float> kx,
56 std::span<const float> ky)
57 {
58 filter_separable(gray, tmp, dx, w, h, kx, K::sobel_smooth);
59 filter_separable(gray, tmp, dy, w, h, K::sobel_smooth, ky);
60 }
61
62} // namespace
63
64void sobel(
65 std::span<const float> gray,
66 std::span<float> dx, std::span<float> dy, std::span<float> tmp,
67 uint32_t w, uint32_t h)
68{
69 gradient_into_buffers(gray, dx, dy, tmp, w, h, K::sobel_kx, K::sobel_ky);
70}
71
72GradientResult sobel(std::span<const float> gray, uint32_t w, uint32_t h)
73{
74 const size_t n = static_cast<size_t>(w) * h;
76 r.dx.resize(n);
77 r.dy.resize(n);
78 std::vector<float> tmp(n);
79 sobel(gray, r.dx, r.dy, tmp, w, h);
80
81 const auto en = static_cast<Eigen::Index>(n);
82 auto dx = Eigen::Map<const Eigen::ArrayXf>(r.dx.data(), en);
83 auto dy = Eigen::Map<const Eigen::ArrayXf>(r.dy.data(), en);
84 r.magnitude.resize(n);
85 r.angle.resize(n);
86
87 Eigen::Map<Eigen::ArrayXf>(r.magnitude.data(), en) = (dx.square() + dy.square()).sqrt();
88 const float peak = Eigen::Map<const Eigen::ArrayXf>(r.magnitude.data(), en).maxCoeff();
89 if (peak > 0.0F)
90 Eigen::Map<Eigen::ArrayXf>(r.magnitude.data(), en) /= peak;
91
92 std::transform(std::execution::par_unseq, r.dx.begin(), r.dx.end(), r.dy.begin(), r.angle.begin(),
93 [](float gx, float gy) { return std::atan2(gy, gx); });
94 return r;
95}
96
97void scharr(
98 std::span<const float> gray,
99 std::span<float> dx, std::span<float> dy, std::span<float> tmp,
100 uint32_t w, uint32_t h)
101{
102 gradient_into_buffers(gray, dx, dy, tmp, w, h, K::scharr_kx, K::scharr_ky);
103}
104
105GradientResult scharr(std::span<const float> gray, uint32_t w, uint32_t h)
106{
107 const size_t n = static_cast<size_t>(w) * h;
109 r.dx.resize(n);
110 r.dy.resize(n);
111 std::vector<float> tmp(n);
112 scharr(gray, r.dx, r.dy, tmp, w, h);
113
114 const auto en = static_cast<Eigen::Index>(n);
115 auto dx = Eigen::Map<const Eigen::ArrayXf>(r.dx.data(), en);
116 auto dy = Eigen::Map<const Eigen::ArrayXf>(r.dy.data(), en);
117 r.magnitude.resize(n);
118 r.angle.resize(n);
119
120 Eigen::Map<Eigen::ArrayXf>(r.magnitude.data(), en) = (dx.square() + dy.square()).sqrt();
121 const float peak = Eigen::Map<const Eigen::ArrayXf>(r.magnitude.data(), en).maxCoeff();
122 if (peak > 0.0F)
123 Eigen::Map<Eigen::ArrayXf>(r.magnitude.data(), en) /= peak;
124
125 std::transform(std::execution::par_unseq, r.dx.begin(), r.dx.end(), r.dy.begin(), r.angle.begin(),
126 [](float gx, float gy) { return std::atan2(gy, gx); });
127 return r;
128}
129
130std::vector<float> canny(
131 std::span<const float> gray, uint32_t w, uint32_t h,
132 float sigma, float low_threshold, float high_threshold)
133{
134 const size_t n = static_cast<size_t>(w) * h;
135
136 std::vector<float> blurred;
137 std::span<const float> src = gray;
138 if (sigma > 0.0F) {
139 blurred = gaussian_blur(gray, w, h, sigma);
140 src = blurred;
141 }
142
143 auto grad = sobel(src, w, h);
144
145 std::vector<float> nms(n, 0.0F);
146 std::for_each(std::execution::par_unseq,
147 std::views::iota(size_t { 0 }, n).begin(),
148 std::views::iota(size_t { 0 }, n).end(),
149 [&](size_t idx) {
150 const auto px = static_cast<int32_t>(idx % w);
151 const auto py = static_cast<int32_t>(idx / w);
152
153 if (px == 0 || py == 0
154 || px == static_cast<int32_t>(w) - 1
155 || py == static_cast<int32_t>(h) - 1)
156 return;
157
158 const float angle = grad.angle[idx];
159 const float pi = std::numbers::pi_v<float>;
160
161 float norm_angle = angle < 0.0F ? angle + pi : angle;
162 norm_angle = norm_angle / pi * 4.0F;
163
164 int32_t ox = 0, oy = 0;
165 if (norm_angle < 0.5F || norm_angle >= 3.5F) {
166 ox = 1;
167 } else if (norm_angle < 1.5F) {
168 ox = 1;
169 oy = 1;
170 } else if (norm_angle < 2.5F) {
171 oy = 1;
172 } else {
173 ox = -1;
174 oy = 1;
175 }
176
177 const float m = grad.magnitude[idx];
178 const float m_pos = grad.magnitude[static_cast<size_t>(py + oy) * w + static_cast<size_t>(px + ox)];
179 const float m_neg = grad.magnitude[static_cast<size_t>(py - oy) * w + static_cast<size_t>(px - ox)];
180
181 if (m >= m_pos && m >= m_neg)
182 nms[idx] = m;
183 });
184
185 std::vector<float> out(n, 0.0F);
186 std::vector<uint8_t> strong(n, 0);
187 std::vector<uint8_t> weak(n, 0);
188
189 for (size_t i = 0; i < n; ++i) {
190 if (nms[i] >= high_threshold) {
191 strong[i] = 1;
192 } else if (nms[i] >= low_threshold) {
193 weak[i] = 1;
194 }
195 }
196
197 std::vector<size_t> stack;
198 for (size_t i = 0; i < n; ++i) {
199 if (strong[i])
200 stack.push_back(i);
201 }
202
203 while (!stack.empty()) {
204 const size_t idx = stack.back();
205 stack.pop_back();
206 out[idx] = 1.0F;
207
208 const auto px = static_cast<int32_t>(idx % w);
209 const auto py = static_cast<int32_t>(idx / w);
210
211 for (int32_t dy = -1; dy <= 1; ++dy) {
212 for (int32_t dx = -1; dx <= 1; ++dx) {
213 if (dx == 0 && dy == 0)
214 continue;
215 const int32_t nx = px + dx;
216 const int32_t ny = py + dy;
217 if (nx < 0 || ny < 0
218 || nx >= static_cast<int32_t>(w)
219 || ny >= static_cast<int32_t>(h))
220 continue;
221 const size_t ni = static_cast<size_t>(ny) * w + nx;
222 if (weak[ni] && out[ni] == 0.0F)
223 stack.push_back(ni);
224 }
225 }
226 }
227
228 return out;
229}
230
231void canny(
232 std::span<const float> gray,
233 std::span<float> dst,
234 uint32_t w, uint32_t h,
235 float sigma, float low_threshold, float high_threshold)
236{
237 auto result = canny(gray, w, h, sigma, low_threshold, high_threshold);
238 std::ranges::copy(result, dst.begin());
239}
240
241} // namespace MayaFlux::Kinesis::Vision
std::shared_ptr< PlacementStack > stack
Gradient and edge detection on normalised float image spans.
2D spatial filtering on normalised float image spans.
uint32_t h
Definition InkPress.cpp:29
Compile-time separable filter kernel definition and named kernel bank.
uint32_t * dst
const uint32_t * src
std::vector< float > * out
uint32_t py
uint32_t px
std::vector< double > peak(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Peak amplitude per window.
Definition Analysis.cpp:51
std::vector< float > filter_separable(std::span< const float > src, uint32_t w, uint32_t h, std::span< const float > kernel_x, std::span< const float > kernel_y)
Apply a separable 2D filter via two 1D passes.
std::vector< float > canny(std::span< const float > gray, uint32_t w, uint32_t h, float sigma, float low_threshold, float high_threshold)
Canny edge detector.
Definition Gradient.cpp:130
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
void scharr(std::span< const float > gray, std::span< float > dx, std::span< float > dy, std::span< float > tmp, uint32_t w, uint32_t h)
Scharr gradient writing dx and dy into caller-supplied buffers.
Definition Gradient.cpp:97
std::vector< float > gaussian_blur(std::span< const float > src, uint32_t w, uint32_t h, float sigma)
Separable Gaussian blur.
std::vector< float > angle
Gradient angle in radians, [-pi, pi].
Definition Gradient.hpp:26
std::vector< float > magnitude
Gradient magnitude, normalised to [0, 1].
Definition Gradient.hpp:25
std::vector< float > dx
Horizontal gradient component.
Definition Gradient.hpp:23
std::vector< float > dy
Vertical gradient component.
Definition Gradient.hpp:24
Gradient maps produced by Sobel and Scharr operators.
Definition Gradient.hpp:22