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.processing.threshold;
31
32 import org.openimaj.citation.annotation.Reference;
33 import org.openimaj.citation.annotation.ReferenceType;
34 import org.openimaj.image.FImage;
35 import org.openimaj.image.processor.ImageProcessor;
36 import org.openimaj.util.array.ArrayUtils;
37 import org.openimaj.util.pair.FloatFloatPair;
38
39
40
41
42
43
44
45
46
47 @Reference(
48 type = ReferenceType.Article,
49 author = { "Nobuyuki Otsu" },
50 title = "A Threshold Selection Method from Gray-Level Histograms",
51 year = "1979",
52 journal = "Systems, Man and Cybernetics, IEEE Transactions on",
53 pages = { "62", "66" },
54 number = "1",
55 volume = "9",
56 customData = {
57 "keywords",
58 "Displays;Gaussian distribution;Histograms;Least squares approximation;Marine vehicles;Q measurement;Radar tracking;Sea measurements;Surveillance;Target tracking",
59 "doi", "10.1109/TSMC.1979.4310076",
60 "ISSN", "0018-9472"
61 })
62 public class OtsuThreshold implements ImageProcessor<FImage> {
63 private static final int DEFAULT_NUM_BINS = 256;
64 int numBins = DEFAULT_NUM_BINS;
65
66
67
68
69 public OtsuThreshold() {
70
71 }
72
73
74
75
76
77
78
79 public OtsuThreshold(int numBins) {
80 this.numBins = numBins;
81 }
82
83 protected static int[] makeHistogram(FImage fimg, int numBins) {
84 final int[] histData = new int[numBins];
85
86
87 for (int r = 0; r < fimg.height; r++) {
88 for (int c = 0; c < fimg.width; c++) {
89 final int h = (int) (fimg.pixels[r][c] * (numBins - 1));
90 histData[h]++;
91 }
92 }
93
94 return histData;
95 }
96
97 protected static int[] makeHistogram(float[] data, int numBins, float min, float max) {
98 final int[] histData = new int[numBins];
99
100
101 for (int c = 0; c < data.length; c++) {
102 final float d = (data[c] - min) / (max - min);
103 final int h = (int) (d * (numBins - 1));
104 histData[h]++;
105 }
106
107 return histData;
108 }
109
110
111
112
113
114
115
116
117
118
119 public static float calculateThreshold(FImage img, int numBins) {
120 final int[] histData = makeHistogram(img, numBins);
121
122
123 final int total = img.getWidth() * img.getHeight();
124
125 return computeThresholdFromHistogram(histData, total);
126 }
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 public static float calculateThreshold(float[] data, int numBins) {
143 final float min = ArrayUtils.minValue(data);
144 final float max = ArrayUtils.maxValue(data);
145 final int[] histData = makeHistogram(data, numBins, min, max);
146
147 return computeThresholdFromHistogram(histData, data.length) + min;
148 }
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164 public static FloatFloatPair calculateThresholdAndVariance(float[] data, int numBins) {
165 final float min = ArrayUtils.minValue(data);
166 final float max = ArrayUtils.maxValue(data);
167 final int[] histData = makeHistogram(data, numBins, min, max);
168
169 final FloatFloatPair result = computeThresholdAndVarianceFromHistogram(histData, data.length);
170 result.first += min;
171 return result;
172 }
173
174
175
176
177
178
179
180
181
182
183 public static float computeThresholdFromHistogram(int[] histData, int total) {
184 return computeThresholdAndVarianceFromHistogram(histData, total).first;
185 }
186
187
188
189
190
191
192
193
194
195
196 public static FloatFloatPair computeThresholdAndVarianceFromHistogram(int[] histData, int total) {
197 final int numBins = histData.length;
198 float sum = 0;
199 for (int t = 0; t < numBins; t++)
200 sum += t * histData[t];
201
202 float sumB = 0;
203 int wB = 0;
204 int wF = 0;
205
206 float varMax = 0;
207 float threshold = 0;
208
209 for (int t = 0; t < numBins; t++) {
210 wB += histData[t];
211 if (wB == 0)
212 continue;
213
214 wF = total - wB;
215 if (wF == 0)
216 break;
217
218 sumB += (t * histData[t]);
219
220 final float mB = sumB / wB;
221 final float mF = (sum - sumB) / wF;
222
223
224 final float varBetween = (float) wB * (float) wF * (mB - mF) * (mB - mF);
225
226
227 if (varBetween > varMax) {
228 varMax = varBetween;
229 threshold = t;
230 }
231 }
232
233 return new FloatFloatPair(threshold / (numBins - 1), varMax / total / total);
234 }
235
236 @Override
237 public void processImage(FImage image) {
238 final float threshold = calculateThreshold(image, numBins);
239
240 image.threshold(threshold);
241 }
242 }