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.contour;
31
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36
37 import org.openimaj.citation.annotation.Reference;
38 import org.openimaj.citation.annotation.ReferenceType;
39 import org.openimaj.image.FImage;
40 import org.openimaj.image.analyser.ImageAnalyser;
41 import org.openimaj.image.pixel.Pixel;
42 import org.openimaj.math.geometry.shape.Rectangle;
43 import org.openimaj.util.function.Operation;
44 import org.openimaj.util.pair.IndependentPair;
45
46
47
48
49
50
51
52
53 @Reference(
54 type = ReferenceType.Article,
55 author = { "Suzuki, S.", "Abe, K." },
56 title = "Topological Structural Analysis of Digitized Binary Image by Border Following",
57 year = "1985",
58 journal = "Computer Vision, Graphics and Image Processing",
59 pages = { "32", "46" },
60 month = "January",
61 number = "1",
62 volume = "30")
63 public class SuzukiContourProcessor implements ImageAnalyser<FImage> {
64
65
66
67 public Contour root;
68 private double minRelativeChildProp = -1;
69
70 @Override
71 public void analyseImage(final FImage image) {
72 this.root = findContours(image, this);
73 }
74
75
76
77
78
79
80
81
82 public static Contour findContours(final FImage image) {
83 return findContours(image, new SuzukiContourProcessor());
84 }
85
86
87
88
89
90
91
92
93
94
95
96 public static Contour findContours(final FImage image, SuzukiContourProcessor proc) {
97 final float nbd[] = new float[] { 1 };
98 final float lnbd[] = new float[] { 1 };
99
100 final Contour root = new Contour(ContourType.HOLE);
101 final Rectangle bb = image.getBounds();
102 root.points.addAll(bb.asPolygon().getVertices());
103 root.finish();
104
105 final Map<Float, Contour> borderMap = new HashMap<Float, Contour>();
106 borderMap.put(lnbd[0], root);
107 final SuzukiNeighborStrategy borderFollow = new SuzukiNeighborStrategy();
108
109 for (int i = 0; i < image.height; i++) {
110 lnbd[0] = 1;
111
112 for (int j = 0; j < image.width; j++) {
113 final float fji = image.getPixel(j, i);
114 final boolean isOuter = isOuterBorderStart(image, i, j);
115
116 final boolean isHole = isHoleBorderStart(image, i, j);
117
118 if (isOuter || isHole) {
119 final Contour border = new Contour(j, i);
120 Contour borderPrime = null;
121 final Pixel from = new Pixel(j, i);
122 if (isOuter) {
123 nbd[0] += 1;
124 from.x -= 1;
125 border.type = ContourType.OUTER;
126 borderPrime = borderMap.get(lnbd[0]);
127
128 switch (borderPrime.type) {
129 case OUTER:
130 border.setParent(borderPrime.parent);
131 break;
132 case HOLE:
133 border.setParent(borderPrime);
134 break;
135 }
136 }
137 else {
138 nbd[0] += 1;
139
140
141 if (fji > 1)
142 lnbd[0] = fji;
143 borderPrime = borderMap.get(lnbd[0]);
144 from.x += 1;
145 border.type = ContourType.HOLE;
146
147 switch (borderPrime.type) {
148 case OUTER:
149 border.setParent(borderPrime);
150 break;
151 case HOLE:
152 border.setParent(borderPrime.parent);
153 break;
154 }
155 }
156
157 final Pixel ij = new Pixel(j, i);
158 borderFollow.directedContour(image, ij, from,
159 new Operation<IndependentPair<Pixel, boolean[]>>() {
160
161 @Override
162 public void perform(IndependentPair<Pixel, boolean[]> object) {
163 final Pixel p = object.firstObject();
164 final boolean[] d = object.secondObject();
165 border.points.add(p);
166 if (crossesEastBorder(image, d, p)) {
167 image.setPixel(p.x, p.y, -nbd[0]);
168 } else if (image.getPixel(p) == 1f) {
169
170
171 image.setPixel(p.x, p.y, nbd[0]);
172 }
173 }
174
175 });
176
177
178 if (border.points.size() == 0) {
179 border.points.add(ij);
180 image.setPixel(j, i, -nbd[0]);
181 }
182 border.finish();
183
184 borderMap.put(nbd[0], border);
185 }
186
187 if (fji != 0 && fji != 1)
188 lnbd[0] = Math.abs(fji);
189
190 }
191 }
192 if (proc.minRelativeChildProp > 0) {
193 removeSmall(root, proc.minRelativeChildProp);
194 }
195 return root;
196 }
197
198 private static void removeSmall(Contour root, double minRelativeChildProp) {
199 final List<Contour> toSearch = new ArrayList<Contour>();
200 toSearch.add(root);
201 while (toSearch.size() != 0) {
202 final Contour ret = toSearch.remove(0);
203 if (ret.parent != null && ret.rect.calculateArea() / ret.parent.rect.calculateArea() < minRelativeChildProp) {
204 ret.parent.children.remove(ret);
205 } else {
206 toSearch.addAll(ret.children);
207 }
208 }
209 }
210
211 private static boolean crossesEastBorder(final FImage image, boolean[] checked, final Pixel p) {
212 final boolean b = checked[Direction.fromTo(p, new Pixel(p.x + 1, p.y)).ordinal()];
213 return image.getPixel(p) != 0 && (p.x == image.width - 1 || b);
214
215
216
217
218
219
220
221 }
222
223 private static boolean isOuterBorderStart(FImage image, int i, int j) {
224 return (image.pixels[i][j] == 1 && (j == 0 || image.pixels[i][j - 1] == 0));
225 }
226
227 private static boolean isHoleBorderStart(FImage image, int i, int j) {
228 return (image.pixels[i][j] >= 1 && (j == image.width - 1 || image.pixels[i][j + 1] == 0));
229 }
230
231
232
233
234
235
236
237
238 public void setMinRelativeChildProp(double d) {
239 this.minRelativeChildProp = d;
240 }
241
242 }