MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches

◆ canny() [2/2]

MAYAFLUX_API std::vector< float > MayaFlux::Kinesis::Vision::canny ( std::span< const float >  gray,
uint32_t  w,
uint32_t  h,
float  sigma,
float  low_threshold,
float  high_threshold 
)

Canny edge detector.

Pipeline: gaussian_blur(sigma) -> sobel -> non-maximum suppression -> double threshold -> hysteresis. Output is binary: 1.0f on edges, 0.0f elsewhere.

Parameters
graySingle-channel float span, size must be w * h.
wImage width in pixels.
hImage height in pixels.
sigmaGaussian pre-blur sigma. 0.0f skips blur.
low_thresholdHysteresis low threshold in [0, 1].
high_thresholdHysteresis high threshold in [0, 1].
Returns
Binary float vector of length w * h.

Definition at line 130 of file Gradient.cpp.

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}
std::shared_ptr< PlacementStack > stack
uint32_t h
Definition InkPress.cpp:29
const uint32_t * src
std::vector< float > * out
uint32_t py
uint32_t px
std::vector< float > gaussian_blur(std::span< const float > src, uint32_t w, uint32_t h, float sigma)
Separable Gaussian blur.

References gaussian_blur(), h, out, px, py, sobel(), src, and stack.

Referenced by canny(), and MayaFlux::Kinesis::Vision::VisionExecutor::run().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: