Canny edge detector.
Pipeline: gaussian_blur(sigma) -> sobel -> non-maximum suppression -> double threshold -> hysteresis. Output is binary: 1.0f on edges, 0.0f elsewhere.
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) {
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])
201 }
202
203 while (!
stack.empty()) {
204 const size_t idx =
stack.back();
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)
224 }
225 }
226 }
227
229}
std::shared_ptr< PlacementStack > stack
std::vector< float > * out
std::vector< float > gaussian_blur(std::span< const float > src, uint32_t w, uint32_t h, float sigma)
Separable Gaussian blur.