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