1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.openimaj.image.connectedcomponent;
31
32 import gnu.trove.map.hash.TIntIntHashMap;
33
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 import java.util.LinkedHashSet;
37 import java.util.List;
38 import java.util.Map;
39
40 import org.openimaj.image.FImage;
41 import org.openimaj.image.analyser.ImageAnalyser;
42 import org.openimaj.image.pixel.ConnectedComponent;
43 import org.openimaj.image.pixel.ConnectedComponent.ConnectMode;
44 import org.openimaj.image.pixel.Pixel;
45
46
47
48
49
50
51 public class ConnectedComponentLabeler implements ImageAnalyser<FImage> {
52
53
54
55
56
57 public enum Algorithm {
58
59
60
61
62
63
64 SINGLE_PASS {
65 @Override
66 public List<ConnectedComponent> findComponents(FImage image, float bgThreshold, ConnectMode mode) {
67 final List<ConnectedComponent> components = new ArrayList<ConnectedComponent>();
68
69
70
71
72 for (int y = 0; y < image.height; y++) {
73 for (int x = 0; x < image.width; x++) {
74 final float element = image.pixels[y][x];
75
76 if (element > bgThreshold) {
77 final List<Pixel> neighbours = mode.getNeighbours(image, x, y, bgThreshold);
78
79 ConnectedComponent currentComponent = null;
80 for (final Pixel p : neighbours) {
81 final ConnectedComponent cc = searchPixel(p, components);
82 if (cc != null) {
83 if (currentComponent == null) {
84 currentComponent = cc;
85 } else if (currentComponent != cc) {
86 currentComponent.merge(cc);
87 components.remove(cc);
88 }
89 }
90 }
91
92 if (currentComponent == null) {
93 currentComponent = new ConnectedComponent();
94 components.add(currentComponent);
95 }
96
97 currentComponent.addPixel(x, y);
98 }
99 }
100 }
101
102 return components;
103 }
104
105 private ConnectedComponent searchPixel(Pixel p, List<ConnectedComponent> components) {
106 for (final ConnectedComponent c : components) {
107 if (c.find(p))
108 return c;
109 }
110 return null;
111 }
112 },
113
114
115
116
117
118 TWO_PASS {
119 @Override
120 public List<ConnectedComponent> findComponents(FImage image, float bgThreshold, ConnectMode mode) {
121 final List<ConnectedComponent> components = new ArrayList<ConnectedComponent>();
122 final TIntIntHashMap linked = new TIntIntHashMap();
123 final int[][] labels = new int[image.height][image.width];
124 int nextLabel = 1;
125
126
127 for (int y = 0; y < image.height; y++) {
128 for (int x = 0; x < image.width; x++) {
129 final float element = image.pixels[y][x];
130
131 if (element > bgThreshold) {
132 final List<Pixel> neighbours = mode.getNeighbours(image, x, y, bgThreshold);
133 final List<Integer> L = new ArrayList<Integer>();
134
135 for (final Pixel p : neighbours)
136 if (labels[p.y][p.x] != 0)
137 L.add(labels[p.y][p.x]);
138
139 if (L.size() == 0) {
140 linked.put(nextLabel, nextLabel);
141 labels[y][x] = nextLabel;
142 nextLabel++;
143 } else {
144 int min = Integer.MAX_VALUE;
145 for (final int i : L)
146 if (i < min)
147 min = i;
148 labels[y][x] = min;
149
150 for (final int i : L) {
151 merge(linked, i, min);
152 }
153 }
154 }
155 }
156 }
157
158
159 final Map<Integer, ConnectedComponent> comp = new HashMap<Integer, ConnectedComponent>();
160
161 for (int i = 1; i <= linked.size(); i++) {
162 int min = linked.get(i);
163
164 while (true) {
165 final int m = linked.get(min);
166
167 if (m == min)
168 break;
169 else
170 min = m;
171 }
172 linked.put(i, min);
173 }
174
175 for (int y = 0; y < image.height; y++) {
176 for (int x = 0; x < image.width; x++) {
177 if (labels[y][x] != 0) {
178 final int min = linked.get(labels[y][x]);
179
180
181 if (comp.containsKey(min)) {
182 comp.get(min).addPixel(x, y);
183 } else {
184 final ConnectedComponent cc = new ConnectedComponent();
185 cc.addPixel(x, y);
186 comp.put(min, cc);
187 }
188 }
189 }
190 }
191 components.addAll(comp.values());
192
193 return components;
194 }
195
196 private void merge(TIntIntHashMap linked, int start, int target) {
197 if (start == target)
198 return;
199
200 final int old = linked.get(start);
201
202 if (old > target) {
203 linked.put(start, target);
204 merge(linked, old, target);
205 } else {
206 merge(linked, target, old);
207 }
208 }
209 },
210
211
212
213
214
215 FLOOD_FILL {
216 @Override
217 public List<ConnectedComponent> findComponents(FImage image, float bgThreshold, ConnectMode mode) {
218 final List<ConnectedComponent> components = new ArrayList<ConnectedComponent>();
219 final int[][] labels = new int[image.height][image.width];
220 int nextColor = 1;
221
222 for (int y = 0; y < image.height; y++) {
223 for (int x = 0; x < image.width; x++) {
224 if (image.pixels[y][x] != 0 && labels[y][x] == 0) {
225 components.add(floodFill(image, new Pixel(x, y), labels, nextColor));
226 nextColor++;
227 }
228 }
229 }
230
231 return components;
232 }
233
234 protected ConnectedComponent floodFill(FImage image, Pixel start, int[][] output, int color) {
235 final ConnectedComponent cc = new ConnectedComponent();
236
237
238
239 final LinkedHashSet<Pixel> queue = new LinkedHashSet<Pixel>();
240
241
242 if (image.pixels[start.y][start.x] == 0)
243 return cc;
244
245
246 queue.add(start);
247
248
249 while (queue.size() > 0) {
250
251 final Pixel n = queue.iterator().next();
252 queue.remove(n);
253
254
255 if (image.pixels[n.y][n.x] != 0 && output[n.y][n.x] != color) {
256
257 int e = n.x, w = n.x;
258
259
260 while (w > 0 && image.pixels[n.y][w - 1] != 0)
261 w--;
262
263
264
265 while (e < image.width - 1 && image.pixels[n.y][e + 1] != 0)
266 e++;
267
268
269
270 for (int i = w; i <= e; i++) {
271 output[n.y][i] = color;
272 cc.addPixel(i, n.y);
273
274
275 final int north = n.y - 1;
276 final int south = n.y + 1;
277
278
279 if (north >= 0 && image.pixels[north][i] != 0 && output[north][i] != color)
280 queue.add(new Pixel(i, north));
281
282
283 if (south < image.height && image.pixels[south][i] != 0 && output[south][i] != color)
284 queue.add(new Pixel(i, south));
285 }
286
287 }
288 }
289
290 return cc;
291 }
292 };
293
294
295
296
297
298
299
300
301
302
303
304
305
306 public abstract List<ConnectedComponent> findComponents(FImage image, float bgThreshold, ConnectMode mode);
307 }
308
309 protected float bgThreshold = 0;
310 protected Algorithm algorithm = Algorithm.TWO_PASS;
311 protected ConnectMode mode;
312 protected List<ConnectedComponent> components;
313
314
315
316
317
318
319
320
321 public ConnectedComponentLabeler(ConnectMode mode) {
322 this.mode = mode;
323 }
324
325
326
327
328
329
330
331
332
333
334 public ConnectedComponentLabeler(Algorithm algorithm, ConnectMode mode) {
335 this.algorithm = algorithm;
336 this.mode = mode;
337 }
338
339
340
341
342
343
344
345
346
347
348
349
350
351 public ConnectedComponentLabeler(Algorithm algorithm, float bgThreshold, ConnectMode mode) {
352 this.algorithm = algorithm;
353 this.bgThreshold = bgThreshold;
354 this.mode = mode;
355 }
356
357
358
359
360
361
362
363
364
365 public List<ConnectedComponent> findComponents(FImage image) {
366 analyseImage(image);
367 return components;
368 }
369
370 @Override
371 public void analyseImage(FImage image) {
372 components = algorithm.findComponents(image, bgThreshold, mode);
373 }
374
375
376
377
378
379 public List<ConnectedComponent> getComponents() {
380 return components;
381 }
382 }