Label connected foreground components in a binary mask.
Uses a two-pass union-find algorithm. First pass assigns provisional labels and records equivalences. Second pass resolves equivalences and relabels to a compact 1..count range.
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 const std::array<uint32_t, 4> neighbors {
66 px > 0 ? labels[idx - 1] : 0U,
67 py > 0 &&
px > 0 ? labels[idx - w - 1] : 0U,
68 py > 0 ? labels[idx - w] : 0U,
69 py > 0 &&
px + 1 < w ? labels[idx - w + 1] : 0U,
70 };
71
73 for (uint32_t neighbor : neighbors) {
74 if (neighbor == 0)
75 continue;
78 else
79 uf.unite(
label, neighbor);
80 }
81
82 labels[idx] =
label != 0 ?
label : next_label++;
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}