MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ConnectedComponents.cpp
Go to the documentation of this file.
2
4
5namespace {
6
7 inline bool is_fg(float v) noexcept { return v >= 0.5F; }
8
9 // =========================================================================
10 // Union-find with path compression and union by rank
11 // =========================================================================
12
13 struct UnionFind {
14 std::vector<uint32_t> parent;
15 std::vector<uint32_t> rank;
16
17 explicit UnionFind(size_t n)
18 : parent(n)
19 , rank(n, 0)
20 {
21 std::iota(parent.begin(), parent.end(), 0U);
22 }
23
24 uint32_t find(uint32_t x)
25 {
26 while (parent[x] != x) {
27 parent[x] = parent[parent[x]];
28 x = parent[x];
29 }
30 return x;
31 }
32
33 void unite(uint32_t a, uint32_t b)
34 {
35 a = find(a);
36 b = find(b);
37 if (a == b)
38 return;
39 if (rank[a] < rank[b])
40 std::swap(a, b);
41 parent[b] = a;
42 if (rank[a] == rank[b])
43 ++rank[a];
44 }
45 };
46
47} // namespace
48
50 std::span<const float> mask, uint32_t w, uint32_t h)
51{
52 const size_t n = static_cast<size_t>(w) * h;
53
54 std::vector<uint32_t> labels(n, 0);
55 UnionFind uf(n + 1);
56 uint32_t next_label = 1;
57
58 for (uint32_t py = 0; py < h; ++py) {
59 for (uint32_t px = 0; px < w; ++px) {
60 const size_t idx = static_cast<size_t>(py) * w + px;
61
62 if (!is_fg(mask[idx]))
63 continue;
64
65 uint32_t left = 0;
66 uint32_t above = 0;
67
68 if (px > 0 && is_fg(mask[idx - 1]))
69 left = labels[idx - 1];
70 if (py > 0 && is_fg(mask[idx - w]))
71 above = labels[idx - w];
72
73 if (left == 0 && above == 0) {
74 labels[idx] = next_label++;
75 } else if (left != 0 && above == 0) {
76 labels[idx] = left;
77 } else if (left == 0 && above != 0) {
78 labels[idx] = above;
79 } else {
80 labels[idx] = left;
81 uf.unite(left, above);
82 }
83 }
84 }
85
86 std::vector<uint32_t> remap(next_label, 0);
87 uint32_t compact = 0;
88 for (uint32_t i = 1; i < next_label; ++i) {
89 const uint32_t root = uf.find(i);
90 if (remap[root] == 0)
91 remap[root] = ++compact;
92 remap[i] = remap[root];
93 }
94
95 struct Extent {
96 uint32_t x_min, y_min, x_max, y_max;
97 uint32_t pixel_count;
98 };
99
100 std::vector<Extent> extents(compact + 1,
101 Extent { .x_min = w, .y_min = h, .x_max = 0, .y_max = 0, .pixel_count = 0 });
102
103 for (uint32_t py = 0; py < h; ++py) {
104 for (uint32_t px = 0; px < w; ++px) {
105 const size_t idx = static_cast<size_t>(py) * w + px;
106 if (labels[idx] == 0)
107 continue;
108
109 const uint32_t lbl = remap[labels[idx]];
110 labels[idx] = lbl;
111
112 auto& e = extents[lbl];
113 e.x_min = std::min(e.x_min, px);
114 e.y_min = std::min(e.y_min, py);
115 e.x_max = std::max(e.x_max, px);
116 e.y_max = std::max(e.y_max, py);
117 ++e.pixel_count;
118 }
119 }
120
121 const float inv_w = 1.0F / static_cast<float>(w);
122 const float inv_h = 1.0F / static_cast<float>(h);
123
124 std::vector<BoundingBox> boxes;
125 boxes.reserve(compact);
126
127 for (uint32_t i = 1; i <= compact; ++i) {
128 const auto& e = extents[i];
129 const float x = static_cast<float>(e.x_min) * inv_w;
130 const float y = static_cast<float>(e.y_min) * inv_h;
131 const float bw = static_cast<float>(e.x_max - e.x_min + 1) * inv_w;
132 const float bh = static_cast<float>(e.y_max - e.y_min + 1) * inv_h;
133 boxes.push_back({ .x = x, .y = y, .w = bw, .h = bh, .confidence = 1.0F, .label_id = i });
134 }
135
136 return { .label_map = std::move(labels), .boxes = std::move(boxes), .count = compact };
137}
138
139} // namespace MayaFlux::Kinesis::Vision
std::vector< uint32_t > rank
std::vector< uint32_t > parent
Connected component labelling on normalised float masks.
uint32_t h
Definition InkPress.cpp:28
size_t a
size_t b
ComponentResult connected_components(std::span< const float > mask, uint32_t w, uint32_t h)
Label connected foreground components in a binary mask.
Result of connected component labelling.