MayaFlux 0.5.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
6
7namespace P = MayaFlux::Parallel;
8
10
11namespace {
12
13 // 8-connected clockwise direction offsets starting from right (+x)
14 // Order: E, SE, S, SW, W, NW, N, NE
15 constexpr int32_t dx8[] = { 1, 1, 0, -1, -1, -1, 0, 1 };
16 constexpr int32_t dy8[] = { 0, 1, 1, 1, 0, -1, -1, -1 };
17
18 // =========================================================================
19 // Shoelace area and perimeter
20 // =========================================================================
21
22 float polygon_area(const std::vector<glm::vec2>& pts)
23 {
24 float area = 0.0F;
25 const size_t n = pts.size();
26 for (size_t i = 0, j = n - 1; i < n; j = i++) {
27 area += pts[j].x * pts[i].y;
28 area -= pts[i].x * pts[j].y;
29 }
30 return std::abs(area) * 0.5F;
31 }
32
33 float polygon_perimeter(const std::vector<glm::vec2>& pts)
34 {
35 float perim = 0.0F;
36 const size_t n = pts.size();
37 for (size_t i = 0, j = n - 1; i < n; j = i++)
38 perim += glm::length(pts[i] - pts[j]);
39 return perim;
40 }
41
42 bool point_in_contour(const std::vector<glm::vec2>& pts, float px, float py) noexcept
43 {
44 int winding = 0;
45 const size_t n = pts.size();
46 for (size_t i = 0; i < n; ++i) {
47 const glm::vec2 a = pts[i];
48 const glm::vec2 b = pts[(i + 1) % n];
49 if (a.y <= py) {
50 if (b.y > py) {
51 const float cross = (b.x - a.x) * (py - a.y)
52 - (b.y - a.y) * (px - a.x);
53 if (cross > 0.0F)
54 ++winding;
55 }
56 } else {
57 if (b.y <= py) {
58 const float cross = (b.x - a.x) * (py - a.y)
59 - (b.y - a.y) * (px - a.x);
60 if (cross < 0.0F)
61 --winding;
62 }
63 }
64 }
65 return winding != 0;
66 }
67
68} // namespace
69
70std::vector<Contour> find_contours(
71 std::span<const float> mask, uint32_t w, uint32_t h,
72 float min_area, uint32_t max_contours)
73{
74 const auto cc = connected_components(mask, w, h);
75 if (cc.count == 0)
76 return {};
77
78 std::vector<Contour> result(cc.count);
79 std::vector<uint8_t> valid(cc.count, 0);
80
81 P::for_each(P::par_unseq,
82 std::views::iota(uint32_t { 0 }, cc.count).begin(),
83 std::views::iota(uint32_t { 0 }, cc.count).end(),
84 [&](uint32_t ci) {
85 const uint32_t lbl = ci + 1;
86 const auto& box = cc.boxes[ci];
87
88 const auto x0 = static_cast<uint32_t>(box.x * static_cast<float>(w));
89 const auto y0 = static_cast<uint32_t>(box.y * static_cast<float>(h));
90 const auto x1 = std::min(x0 + static_cast<uint32_t>(box.w * static_cast<float>(w)) - 1U, w - 1);
91 const auto y1 = std::min(y0 + static_cast<uint32_t>(box.h * static_cast<float>(h)) - 1U, h - 1);
92
93 uint32_t start_x = 0, start_y = 0;
94 bool found = false;
95 for (uint32_t py = y0; py <= y1 && !found; ++py) {
96 for (uint32_t px = x0; px <= x1 && !found; ++px) {
97 if (cc.label_map[static_cast<size_t>(py) * w + px] == lbl) {
98 start_x = px;
99 start_y = py;
100 found = true;
101 }
102 }
103 }
104
105 if (!found)
106 return;
107
108 const uint32_t bw = x1 - x0 + 1;
109 const uint32_t bh = y1 - y0 + 1;
110
111 if (bw < 2 || bh < 2)
112 return;
113
114 std::vector<uint8_t> visited(static_cast<size_t>(bw) * bh, 0);
115
116 auto is_component_fg = [&](int32_t px, int32_t py) -> bool {
117 if (px < 0 || py < 0
118 || static_cast<uint32_t>(px) >= w
119 || static_cast<uint32_t>(py) >= h)
120 return false;
121 return cc.label_map[static_cast<size_t>(py) * w + px] == lbl;
122 };
123
124 auto mark_visited = [&](int32_t px, int32_t py) {
125 if (static_cast<uint32_t>(px) < x0 || static_cast<uint32_t>(py) < y0
126 || static_cast<uint32_t>(px) > x1 || static_cast<uint32_t>(py) > y1)
127 return;
128 const uint32_t lx = static_cast<uint32_t>(px) - x0;
129 const uint32_t ly = static_cast<uint32_t>(py) - y0;
130 visited[static_cast<size_t>(ly) * bw + lx] = 1;
131 };
132
133 auto cx = static_cast<int32_t>(start_x);
134 auto cy = static_cast<int32_t>(start_y);
135
136 int32_t start_dir = 0;
137 for (int32_t d = 0; d < 8; ++d) {
138 const int32_t nx = cx + dx8[d];
139 const int32_t ny = cy + dy8[d];
140 if (!is_component_fg(nx, ny)) {
141 start_dir = d;
142 break;
143 }
144 }
145
146 const int32_t orig_x = cx;
147 const int32_t orig_y = cy;
148 int32_t dir = start_dir;
149 bool first = true;
150 std::vector<glm::vec2> points;
151
152 while (true) {
153 bool step_found = false;
154 for (int32_t i = 0; i < 8; ++i) {
155 const int32_t d = (dir + 6 + i) % 8;
156 const int32_t nx = cx + dx8[d];
157 const int32_t ny = cy + dy8[d];
158
159 if (!is_component_fg(nx, ny))
160 continue;
161
162 points.emplace_back(
163 static_cast<float>(cx) / static_cast<float>(w),
164 static_cast<float>(cy) / static_cast<float>(h));
165 mark_visited(cx, cy);
166 cx = nx;
167 cy = ny;
168 dir = d;
169 step_found = true;
170 break;
171 }
172
173 if (!step_found)
174 break;
175 if (!first && cx == orig_x && cy == orig_y)
176 break;
177 first = false;
178 if (points.size() > static_cast<size_t>(w) * h)
179 break;
180 }
181
182 if (points.size() < 3)
183 return;
184
185 const float area = polygon_area(points);
186 if (area < min_area)
187 return;
188
189 result[ci] = { .points = std::move(points), .area = area, .perimeter = polygon_perimeter(points) };
190 valid[ci] = 1;
191 });
192
193 std::vector<Contour> out;
194 out.reserve(cc.count);
195 for (uint32_t i = 0; i < cc.count; ++i) {
196 if (valid[i])
197 out.push_back(std::move(result[i]));
198 }
199
200 if (max_contours > 0 && out.size() > static_cast<size_t>(max_contours)) {
201 std::partial_sort(out.begin(),
202 out.begin() + static_cast<ptrdiff_t>(max_contours),
203 out.end(),
204 [](const Contour& a, const Contour& b) { return a.area > b.area; });
205 out.resize(max_contours);
206 }
207
208 return out;
209}
210
212 std::span<float> pixels,
213 uint32_t w, uint32_t h,
214 uint32_t channels,
215 const Contour& contour,
216 float origin_x, float origin_y,
217 float scale_x, float scale_y)
218{
219 if (pixels.empty() || contour.points.empty() || channels == 0)
220 return;
221
222 for (uint32_t row = 0; row < h; ++row) {
223 const float py = origin_y + (static_cast<float>(row) + 0.5F) * scale_y;
224 for (uint32_t col = 0; col < w; ++col) {
225 const float px = origin_x + (static_cast<float>(col) + 0.5F) * scale_x;
226 if (!point_in_contour(contour.points, px, py)) {
227 const size_t base = (static_cast<size_t>(row) * w + col) * channels;
228 for (uint32_t c = 0; c < channels; ++c)
229 pixels[base + c] = 0.0F;
230 }
231 }
232 }
233}
234
235} // namespace MayaFlux::Kinesis::Vision
Connected component labelling on normalised float masks.
Contour extraction from binary float masks.
const std::vector< float > * pixels
Definition Decoder.cpp:65
std::vector< glm::vec2 > * points
uint32_t h
Definition InkPress.cpp:28
size_t a
size_t b
float min_area
uint32_t max_contours
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)
Extract outer contours from a binary mask.
Definition Contours.cpp:70
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:211
std::vector< glm::vec2 > points
Definition Features.hpp:43
Closed contour extracted from a binary mask.
Definition Features.hpp:42