MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Contours.cpp
Go to the documentation of this file.
1#include "Contours.hpp"
2
4
5#include <execution>
6
8
9namespace {
10
11 constexpr int32_t dx8[] = { 1, 1, 0, -1, -1, -1, 0, 1 };
12 constexpr int32_t dy8[] = { 0, 1, 1, 1, 0, -1, -1, -1 };
13
14 struct TraceSeed {
15 size_t pixel;
16 uint32_t label;
17 uint32_t parent;
18 };
19
20 template <typename Inside>
21 std::vector<glm::ivec2> walk_boundary(
22 glm::ivec2 start, size_t max_points, Inside inside)
23 {
24 std::vector<glm::ivec2> points;
25 glm::ivec2 current = start;
26 glm::ivec2 first_next {};
27 int32_t backtrack = 4;
28
29 while (points.size() < max_points) {
30 int32_t direction = -1;
31 glm::ivec2 next {};
32 for (int32_t i = 1; i <= 8; ++i) {
33 const int32_t d = (backtrack + i) % 8;
34 next = current + glm::ivec2(dx8[d], dy8[d]);
35 if (inside(next)) {
36 direction = d;
37 break;
38 }
39 }
40
41 if (direction < 0)
42 break;
43 if (!points.empty() && current == start && next == first_next)
44 break;
45 if (points.empty())
46 first_next = next;
47
48 points.push_back(current);
49 current = next;
50 backtrack = (direction + 6 - direction % 2) % 8;
51 }
52
53 return points;
54 }
55
56 Contour measure_contour(
57 const std::vector<glm::ivec2>& pixels, uint32_t w, uint32_t h,
58 uint32_t parent)
59 {
60 float area = 0.0F;
61 float perimeter = 0.0F;
62 for (size_t i = 0; i < pixels.size(); ++i) {
63 const glm::vec2 a(pixels[i]);
64 const glm::vec2 b(pixels[(i + 1) % pixels.size()]);
65 area += a.x * b.y - b.x * a.y;
66 perimeter += glm::length(b - a);
67 }
68
69 Contour contour {
70 .points = {},
71 .area = (std::abs(area) * 0.5F + perimeter * 0.5F + 1.0F)
72 / (static_cast<float>(w) * static_cast<float>(h)),
73 .perimeter = perimeter,
74 .parent_label = parent == 0 ? Contour::no_parent : parent,
75 };
76 contour.points.reserve(pixels.size());
77 const glm::vec2 extent(static_cast<float>(w), static_cast<float>(h));
78 for (const auto& p : pixels)
79 contour.points.push_back(glm::vec2(p) / extent);
80 return contour;
81 }
82
83 bool point_in_contour(const std::vector<glm::vec2>& pts, float px, float py) noexcept
84 {
85 int winding = 0;
86 const size_t n = pts.size();
87 for (size_t i = 0; i < n; ++i) {
88 const glm::vec2 a = pts[i];
89 const glm::vec2 b = pts[(i + 1) % n];
90 if (a.y <= py) {
91 if (b.y > py) {
92 const float cross = (b.x - a.x) * (py - a.y)
93 - (b.y - a.y) * (px - a.x);
94 if (cross > 0.0F)
95 ++winding;
96 }
97 } else {
98 if (b.y <= py) {
99 const float cross = (b.x - a.x) * (py - a.y)
100 - (b.y - a.y) * (px - a.x);
101 if (cross < 0.0F)
102 --winding;
103 }
104 }
105 }
106 return winding != 0;
107 }
108
109} // namespace
110
111std::vector<Contour> find_contours(
112 std::span<const float> mask, uint32_t w, uint32_t h,
113 float min_area, uint32_t max_contours, uint32_t max_points_per_contour)
114{
115 if (w == 0 || h == 0)
116 return {};
117
118 const auto cc = connected_components(mask, w, h);
119 return find_contours(cc, w, h, min_area, max_contours, max_points_per_contour);
120}
121
122std::vector<Contour> find_contours(
123 const ComponentResult& cc, uint32_t w, uint32_t h,
124 float min_area, uint32_t max_contours, uint32_t max_points_per_contour)
125{
126 if (w == 0 || h == 0 || cc.count == 0)
127 return {};
128
129 const size_t n = static_cast<size_t>(w) * h;
130 std::vector<TraceSeed> seeds(cc.count, TraceSeed { .pixel = n, .label = 0, .parent = 0 });
131 for (size_t i = 0; i < n; ++i) {
132 const uint32_t label = cc.label_map[i];
133 if (label != 0 && seeds[label - 1].pixel == n)
134 seeds[label - 1] = { .pixel = i, .label = label, .parent = 0 };
135 }
136
137 std::vector<uint32_t> background(n, 0);
138 std::vector<size_t> pending;
139 uint32_t region = 0;
140 for (size_t seed = 0; seed < n; ++seed) {
141 if (cc.label_map[seed] != 0 || background[seed] != 0)
142 continue;
143
144 ++region;
145 pending.clear();
146 pending.push_back(seed);
147 background[seed] = region;
148 bool exterior = false;
149 for (size_t head = 0; head < pending.size(); ++head) {
150 const size_t pixel = pending[head];
151 const auto x = static_cast<uint32_t>(pixel % w);
152 const auto y = static_cast<uint32_t>(pixel / w);
153 exterior = exterior || x == 0 || y == 0 || x + 1 == w || y + 1 == h;
154
155 const auto visit = [&](size_t neighbor) {
156 if (cc.label_map[neighbor] == 0 && background[neighbor] == 0) {
157 background[neighbor] = region;
158 pending.push_back(neighbor);
159 }
160 };
161 if (x > 0)
162 visit(pixel - 1);
163 if (x + 1 < w)
164 visit(pixel + 1);
165 if (y > 0)
166 visit(pixel - w);
167 if (y + 1 < h)
168 visit(pixel + w);
169 }
170
171 if (!exterior) {
172 const uint32_t parent = cc.label_map[seed - w];
173 seeds.push_back({ .pixel = seed, .label = region, .parent = parent });
174 }
175 }
176
177 std::vector<Contour> result(seeds.size());
178 std::vector<uint8_t> valid(seeds.size(), 0);
179 const size_t max_points = max_points_per_contour > 0
180 ? static_cast<size_t>(max_points_per_contour)
181 : n * 8;
182
183 std::for_each(std::execution::par_unseq,
184 std::views::iota(size_t { 0 }, seeds.size()).begin(),
185 std::views::iota(size_t { 0 }, seeds.size()).end(),
186 [&](size_t ci) {
187 const auto& seed = seeds[ci];
188 const glm::ivec2 start(static_cast<int32_t>(seed.pixel % w), static_cast<int32_t>(seed.pixel / w));
189 const auto inside = [&](glm::ivec2 p) {
190 if (p.x < 0 || p.y < 0 || static_cast<uint32_t>(p.x) >= w || static_cast<uint32_t>(p.y) >= h)
191 return false;
192 const size_t index = static_cast<size_t>(p.y) * w + static_cast<uint32_t>(p.x);
193 return seed.parent == 0 ? cc.label_map[index] == seed.label : background[index] == seed.label;
194 };
195 const auto points = walk_boundary(start, max_points, inside);
196 if (points.size() < 3)
197 return;
198
199 auto contour = measure_contour(points, w, h, seed.parent);
200 if (contour.area < min_area)
201 return;
202
203 result[ci] = std::move(contour);
204 valid[ci] = 1;
205 });
206
207 std::vector<Contour> out;
208 out.reserve(seeds.size());
209 for (size_t i = 0; i < seeds.size(); ++i) {
210 if (valid[i])
211 out.push_back(std::move(result[i]));
212 }
213
214 if (max_contours > 0) {
215 const size_t count = std::min(out.size(), static_cast<size_t>(max_contours));
216 std::partial_sort(out.begin(),
217 out.begin() + static_cast<ptrdiff_t>(count),
218 out.end(),
219 [](const Contour& a, const Contour& b) { return a.area > b.area; });
220 out.resize(count);
221 }
222
223 return out;
224}
225
227 std::span<float> pixels,
228 uint32_t w, uint32_t h,
229 uint32_t channels,
230 const Contour& contour,
231 float origin_x, float origin_y,
232 float scale_x, float scale_y)
233{
234 if (pixels.empty() || contour.points.empty() || channels == 0)
235 return;
236
237 for (uint32_t row = 0; row < h; ++row) {
238 const float py = origin_y + (static_cast<float>(row) + 0.5F) * scale_y;
239 for (uint32_t col = 0; col < w; ++col) {
240 const float px = origin_x + (static_cast<float>(col) + 0.5F) * scale_x;
241 if (!point_in_contour(contour.points, px, py)) {
242 const size_t base = (static_cast<size_t>(row) * w + col) * channels;
243 for (uint32_t c = 0; c < channels; ++c)
244 pixels[base + c] = 0.0F;
245 }
246 }
247 }
248}
249
250} // namespace MayaFlux::Kinesis::Vision
std::vector< uint32_t > parent
Connected component labelling on normalised float masks.
size_t pixel
Definition Contours.cpp:15
uint32_t label
Definition Contours.cpp:16
Contour extraction from binary float masks.
const std::vector< float > * pixels
Definition Decoder.cpp:48
std::vector< glm::vec2 > * points
glm::vec2 current
uint32_t h
Definition InkPress.cpp:29
size_t a
size_t b
std::vector< float > * out
uint32_t index
Definition VKDevice.cpp:122
size_t count
uint32_t channels
uint32_t py
uint32_t px
ComponentResult connected_components(std::span< const float > mask, uint32_t w, uint32_t h)
Label connected foreground components in a binary mask.
std::vector< Contour > find_contours(std::span< const float > mask, uint32_t w, uint32_t h, float min_area, uint32_t max_contours, uint32_t max_points_per_contour)
Extract outer and hole contours from a binary mask.
Definition Contours.cpp:111
void apply_contour_mask(std::span< float > pixels, uint32_t w, uint32_t h, uint32_t channels, const Contour &contour, float origin_x, float origin_y, float scale_x, float scale_y)
Zero all pixels in pixels that fall outside contour.
Definition Contours.cpp:226
std::vector< uint32_t > label_map
Per-pixel labels; CPU executor output is empty unless export_labels is set.
uint32_t count
Number of components found.
Result of connected component labelling.
static constexpr uint32_t no_parent
Outer contour parent sentinel.
Definition Features.hpp:47
std::vector< glm::vec2 > points
Definition Features.hpp:49
Closed contour extracted from a binary mask.
Definition Features.hpp:46