MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
KernelSpec.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * @file KernelSpec.hpp
5 * @brief Compile-time separable filter kernel definition and named kernel bank.
6 *
7 * KernelSpec<N> is a constexpr wrapper around std::array<float, N> with
8 * chainable compile-time transforms. All methods return a new KernelSpec
9 * leaving the original unchanged.
10 *
11 * Named constants at the bottom of this file cover the standard operators.
12 * Custom kernels can be constructed inline at any call site:
13 *
14 * constexpr auto k = KernelSpec<5>{ 1.F, 4.F, 6.F, 4.F, 1.F }.normalize();
15 * filter_separable(src, tmp, dst, w, h, k, k);
16 *
17 * KernelSpec<N> converts implicitly to std::span<const float> so it can be
18 * passed directly to any filter function without an explicit cast.
19 */
20
22
23// ============================================================================
24// KernelSpec<N>
25// ============================================================================
26
27/**
28 * @brief Compile-time 1D separable filter kernel.
29 *
30 * @tparam N Kernel length. Must be odd and >= 1.
31 */
32template <size_t N>
33struct KernelSpec {
34 static_assert(N % 2 == 1, "KernelSpec length must be odd");
35 static_assert(N >= 1, "KernelSpec length must be >= 1");
36
37 static constexpr size_t length = N;
38 static constexpr size_t half = N / 2;
39
40 std::array<float, N> taps {};
41
42 // -------------------------------------------------------------------------
43 // Construction
44 // -------------------------------------------------------------------------
45
46 constexpr KernelSpec() = default;
47
48 template <typename... Ts>
49 requires(sizeof...(Ts) == N) && (std::is_convertible_v<Ts, float> && ...)
50 constexpr explicit KernelSpec(Ts... values) noexcept
51 : taps { static_cast<float>(values)... }
52 {
53 }
54
55 // -------------------------------------------------------------------------
56 // Transforms — each returns a new KernelSpec, original unchanged
57 // -------------------------------------------------------------------------
58
59 /**
60 * @brief Divide all taps by their sum.
61 *
62 * Produces a unit-sum kernel suitable for smoothing passes.
63 * No-op if sum is zero.
64 */
65 [[nodiscard]] constexpr KernelSpec normalize() const noexcept
66 {
67 float sum = 0.0F;
68 for (float v : taps)
69 sum += v;
70 if (sum == 0.0F)
71 return *this;
72 KernelSpec out;
73 for (size_t i = 0; i < N; ++i)
74 out.taps[i] = taps[i] / sum;
75 return out;
76 }
77
78 /**
79 * @brief Negate all taps.
80 *
81 * Derives the transpose derivative kernel from its pair:
82 * sobel_ky = sobel_kx.negate()
83 * Not needed for Sobel/Scharr since kx == ky, but useful for
84 * asymmetric operators.
85 */
86 [[nodiscard]] constexpr KernelSpec negate() const noexcept
87 {
88 KernelSpec out;
89 for (size_t i = 0; i < N; ++i)
90 out.taps[i] = -taps[i];
91 return out;
92 }
93
94 /**
95 * @brief Reverse tap order.
96 *
97 * Reflects the kernel about its centre tap. For symmetric kernels
98 * this is a no-op. Useful when a vertical kernel is the mirror of
99 * its horizontal counterpart.
100 */
101 [[nodiscard]] constexpr KernelSpec reflect() const noexcept
102 {
103 KernelSpec out;
104 for (size_t i = 0; i < N; ++i)
105 out.taps[i] = taps[N - 1 - i];
106 return out;
107 }
108
109 /**
110 * @brief Multiply all taps by a scalar.
111 */
112 [[nodiscard]] constexpr KernelSpec scale(float s) const noexcept
113 {
114 KernelSpec out;
115 for (size_t i = 0; i < N; ++i)
116 out.taps[i] = taps[i] * s;
117 return out;
118 }
119
120 /**
121 * @brief Add a scalar to all taps.
122 *
123 * Useful for shifting a zero-mean derivative kernel to a non-negative
124 * range for visualisation.
125 */
126 [[nodiscard]] constexpr KernelSpec shift(float s) const noexcept
127 {
128 KernelSpec out;
129 for (size_t i = 0; i < N; ++i)
130 out.taps[i] = taps[i] + s;
131 return out;
132 }
133
134 /**
135 * @brief Convolve this kernel with another of the same length.
136 *
137 * Produces a kernel of length 2*N-1 representing the sequential
138 * application of both. Use to construct larger kernels from smaller
139 * primitives, e.g. binomial5 = binomial3.convolve(binomial3).
140 *
141 * @tparam M Length of the other kernel. Result length is N+M-1.
142 */
143 template <size_t M>
144 [[nodiscard]] constexpr KernelSpec<N + M - 1> convolve(
145 const KernelSpec<M>& other) const noexcept
146 {
147 KernelSpec<N + M - 1> out;
148 for (size_t i = 0; i < N; ++i)
149 for (size_t j = 0; j < M; ++j)
150 out.taps[i + j] += taps[i] * other.taps[j];
151 return out;
152 }
153
154 // -------------------------------------------------------------------------
155 // Span interop
156 // -------------------------------------------------------------------------
157
158 [[nodiscard]] constexpr std::span<const float> span() const noexcept
159 {
160 return { taps.data(), N };
161 }
162
163 constexpr operator std::span<const float>() const noexcept
164 {
165 return span();
166 }
167
168 [[nodiscard]] constexpr const float* data() const noexcept { return taps.data(); }
169 [[nodiscard]] constexpr size_t size() const noexcept { return N; }
170};
171
172// ============================================================================
173// Named kernels
174// ============================================================================
175
176namespace Kernels {
177
178 // ----------------------------------------------------------------------------
179 // Sobel
180 //
181 // 3x3. Applied separably:
182 // dx = filter_separable(gray, sobel_kx, sobel_smooth)
183 // dy = filter_separable(gray, sobel_smooth, sobel_ky)
184 //
185 // kx/ky: finite difference [-1, 0, 1]
186 // smooth: binomial averaging [1, 2, 1], normalised to unit sum
187 // ----------------------------------------------------------------------------
188
189 inline constexpr auto sobel_kx = KernelSpec<3> { -1.0F, 0.0F, 1.0F };
190 inline constexpr auto sobel_ky = KernelSpec<3> { -1.0F, 0.0F, 1.0F };
191 inline constexpr auto sobel_smooth = KernelSpec<3> { 1.0F, 2.0F, 1.0F }.normalize();
192
193 // ----------------------------------------------------------------------------
194 // Scharr
195 //
196 // 3x3. Superior rotational symmetry vs Sobel. Same smooth kernel.
197 // dx = filter_separable(gray, scharr_kx, sobel_smooth)
198 // dy = filter_separable(gray, sobel_smooth, scharr_ky)
199 // ----------------------------------------------------------------------------
200
201 inline constexpr auto scharr_kx = KernelSpec<3> { -3.0F, 0.0F, 3.0F };
202 inline constexpr auto scharr_ky = KernelSpec<3> { -3.0F, 0.0F, 3.0F };
203
204 // ----------------------------------------------------------------------------
205 // Prewitt
206 //
207 // 3x3. Uniform smoothing axis. Lower noise suppression than Sobel.
208 // dx = filter_separable(gray, prewitt_kx, prewitt_smooth)
209 // dy = filter_separable(gray, prewitt_smooth, prewitt_ky)
210 // ----------------------------------------------------------------------------
211
212 inline constexpr auto prewitt_kx = KernelSpec<3> { -1.0F, 0.0F, 1.0F };
213 inline constexpr auto prewitt_ky = KernelSpec<3> { -1.0F, 0.0F, 1.0F };
214 inline constexpr auto prewitt_smooth = KernelSpec<3> { 1.0F, 1.0F, 1.0F }.normalize();
215
216 // ----------------------------------------------------------------------------
217 // Binomial smoothing
218 //
219 // Pascal's triangle coefficients, normalised. Fast approximation to
220 // Gaussian with integer arithmetic heritage. Use for repeated smoothing
221 // passes or as building blocks via convolve().
222 //
223 // binomial3 = [1, 2, 1] / 4 sigma ~= 0.5
224 // binomial5 = [1, 4, 6, 4, 1] / 16 sigma ~= 1.0
225 // binomial7 = [1,6,15,20,15,6,1]/64 sigma ~= 1.5
226 // ----------------------------------------------------------------------------
227
228 inline constexpr auto binomial3 = KernelSpec<3> { 1.0F, 2.0F, 1.0F }.normalize();
229 inline constexpr auto binomial5 = KernelSpec<5> { 1.0F, 4.0F, 6.0F, 4.0F, 1.0F }.normalize();
230 inline constexpr auto binomial7 = KernelSpec<7> { 1.0F, 6.0F, 15.0F, 20.0F, 15.0F, 6.0F, 1.0F }.normalize();
231
232 // ----------------------------------------------------------------------------
233 // Box filters
234 //
235 // Uniform averaging. Fast but introduces ringing on sharp edges.
236 // Prefer binomial for image processing; use box for debug/preview.
237 // ----------------------------------------------------------------------------
238
239 inline constexpr auto box3 = KernelSpec<3> { 1.0F, 1.0F, 1.0F }.normalize();
240 inline constexpr auto box5 = KernelSpec<5> { 1.0F, 1.0F, 1.0F, 1.0F, 1.0F }.normalize();
241
242 // ----------------------------------------------------------------------------
243 // Difference of Gaussians approximation
244 //
245 // Two fixed-sigma Gaussian approximations via binomial convolution.
246 // Subtract coarse from fine (pointwise) after two separate blur passes
247 // to approximate a Laplacian of Gaussian without runtime kernel construction.
248 //
249 // log_fine ~= sigma 1.0 (binomial5)
250 // log_coarse ~= sigma 2.0 (binomial5 convolved with itself -> binomial9)
251 // ----------------------------------------------------------------------------
252
253 inline constexpr auto log_fine = binomial5;
254 inline constexpr auto log_coarse = binomial5.convolve(binomial5).normalize();
255
256} // namespace Kernels
257
258} // namespace MayaFlux::Kinesis::Vision
#define N(method_name, full_type_name)
Definition Creator.hpp:106
constexpr KernelSpec shift(float s) const noexcept
Add a scalar to all taps.
constexpr KernelSpec reflect() const noexcept
Reverse tap order.
constexpr KernelSpec scale(float s) const noexcept
Multiply all taps by a scalar.
constexpr KernelSpec< N+M - 1 > convolve(const KernelSpec< M > &other) const noexcept
Convolve this kernel with another of the same length.
constexpr size_t size() const noexcept
constexpr KernelSpec normalize() const noexcept
Divide all taps by their sum.
constexpr std::span< const float > span() const noexcept
constexpr KernelSpec negate() const noexcept
Negate all taps.
constexpr KernelSpec(Ts... values) noexcept
constexpr const float * data() const noexcept
Compile-time 1D separable filter kernel.