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)
22 const size_t n =
static_cast<size_t>(w) *
h;
24 static constexpr float smooth[] = { 1.0F, 2.0F, 1.0F };
28 std::vector<float> mag(n);
29 std::vector<float> ang(n);
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(),
37 mag[i] = std::sqrt(dx[i] * dx[i] + dy[i] * dy[i]);
38 ang[i] = std::atan2(dy[i], dx[i]);
41 const float peak = *std::ranges::max_element(mag);
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; });
48 return { .dx = std::move(dx), .dy = std::move(dy), .magnitude = std::move(mag), .angle = std::move(ang) };
51 void gradient_into_buffers(
52 std::span<const float> gray,
56 uint32_t w, uint32_t
h,
57 std::span<const float> kx,
58 std::span<const float> ky)
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)
76 const size_t n =
static_cast<size_t>(w) *
h;
80 std::vector<float> tmp(n);
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);
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();
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); });
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)
109 const size_t n =
static_cast<size_t>(w) *
h;
113 std::vector<float> tmp(n);
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);
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();
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); });
133 std::span<const float> gray, uint32_t w, uint32_t
h,
134 float sigma,
float low_threshold,
float high_threshold)
136 const size_t n =
static_cast<size_t>(w) *
h;
138 std::vector<float> blurred;
139 std::span<const float> src = gray;
145 auto grad =
sobel(src, w,
h);
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(),
152 const auto px =
static_cast<int32_t
>(idx % w);
153 const auto py =
static_cast<int32_t
>(idx / w);
155 if (px == 0 || py == 0
156 || px ==
static_cast<int32_t
>(w) - 1
157 || py ==
static_cast<int32_t
>(
h) - 1)
160 const float angle = grad.angle[idx];
161 const float pi = std::numbers::pi_v<float>;
163 float norm_angle = angle < 0.0F ? angle + pi : angle;
164 norm_angle = norm_angle / pi * 4.0F;
166 int32_t ox = 0, oy = 0;
167 if (norm_angle < 0.5F || norm_angle >= 3.5F) {
169 }
else if (norm_angle < 1.5F) {
172 }
else if (norm_angle < 2.5F) {
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)];
183 if (m >= m_pos && m >= m_neg)
187 std::vector<float> out(n, 0.0F);
188 std::vector<uint8_t> strong(n, 0);
189 std::vector<uint8_t> weak(n, 0);
191 for (
size_t i = 0; i < n; ++i) {
192 if (nms[i] >= high_threshold) {
194 }
else if (nms[i] >= low_threshold) {
199 std::vector<size_t> stack;
200 for (
size_t i = 0; i < n; ++i) {
205 while (!stack.empty()) {
206 const size_t idx = stack.back();
210 const auto px =
static_cast<int32_t
>(idx % w);
211 const auto py =
static_cast<int32_t
>(idx / w);
213 for (int32_t dy = -1; dy <= 1; ++dy) {
214 for (int32_t dx = -1; dx <= 1; ++dx) {
215 if (dx == 0 && dy == 0)
217 const int32_t nx = px + dx;
218 const int32_t ny = py + dy;
220 || nx >=
static_cast<int32_t
>(w)
221 || ny >=
static_cast<int32_t
>(
h))
223 const size_t ni =
static_cast<size_t>(ny) * w + nx;
224 if (weak[ni] && out[ni] == 0.0F)
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)
239 auto result =
canny(gray, w,
h,
sigma, low_threshold, high_threshold);
240 std::ranges::copy(result, dst.begin());
Gradient and edge detection on normalised float image spans.
2D spatial filtering on normalised float image spans.
Compile-time separable filter kernel definition and named kernel bank.
constexpr auto sobel_smooth
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.
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.
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.
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.
std::vector< float > angle
Gradient angle in radians, [-pi, pi].
std::vector< float > magnitude
Gradient magnitude, normalised to [0, 1].
std::vector< float > dx
Horizontal gradient component.
std::vector< float > dy
Vertical gradient component.
Gradient maps produced by Sobel and Scharr operators.