Libraryless. Click here for Pure Java version (6669L/39K).
1 | srecord noeq ImageSimplifier(BufferedImage inputImage) { |
2 | replace Channel with int. |
3 | |
4 | // input image (mandatory) |
5 | |
6 | CompactIntegralImage mainImage; |
7 | |
8 | // Be verbose? |
9 | |
10 | bool verbose, verboseImageSize, debug; |
11 | |
12 | // the big internal cache |
13 | |
14 | new Map<Rect, IIntegralImage> clipCache; |
15 | |
16 | // channel definitions (r, g, b and grayscale are channels) |
17 | |
18 | static final int grayscale = 3; // channel number for grayscale |
19 | static final int channels = 4; |
20 | |
21 | // user-settable maximums |
22 | |
23 | long maxSteps = 1000; |
24 | |
25 | // very important value. size of smallest features in relation to min(image width, image height) |
26 | |
27 | double featureSize = 0.1; |
28 | |
29 | long steps; |
30 | double lowestExecutedProbability; |
31 | |
32 | // OUTPUT of the algorithm (found points) |
33 | |
34 | new ProbabilisticList<IIntegralImage> liveliestPoints; |
35 | |
36 | // INTERNAL (probabilistic scheduling, memory) |
37 | |
38 | ProbabilisticList<IIntegralImage> scheduler; |
39 | Set<IIntegralImage> lookedAt; |
40 | |
41 | abstract class IIntegralImage implements main IIntegralImage { |
42 | // width and height of image |
43 | int w, h; |
44 | |
45 | public int getWidth() { ret w; } |
46 | public int getHeight() { ret h; } |
47 | |
48 | int liveliness_cachedChannel = -1; |
49 | double liveliness_cache; |
50 | |
51 | long discoveredInStep; |
52 | |
53 | public abstract double getIntegralValue(int x, int y, Channel channel); |
54 | |
55 | BufferedImage render() { |
56 | ret imageFromFunction(w, h, (x, y) -> rgbPixel(x, y, x+1, y+1) | fullAlphaMask()); |
57 | } |
58 | |
59 | double getPixel(Rect r, int channel) { |
60 | ret getPixel(r.x, r.y, r.x2(), r.y2(), channel); |
61 | } |
62 | |
63 | double getPixel(int channel) { ret getPixel(0, 0, w, h, channel); } |
64 | |
65 | // return value ranges from 0 to 1 (usually) |
66 | double getPixel(int x1, int y1, int x2, int y2, int channel) { |
67 | ret doubleRatio(rectSum(x1, y1, x2, y2, channel), (x2-x1)*(y2-y1)*255.0); |
68 | } |
69 | |
70 | public double rectSum(Rect r, int channel) { |
71 | ret rectSum(r.x, r.y, r.x2(), r.y2(), channel); |
72 | } |
73 | |
74 | public double rectSum(int x1, int y1, int x2, int y2, int channel) { |
75 | double bottomRight = getIntegralValue(x2-1, y2-1, channel); |
76 | double topRight = getIntegralValue(x2-1, y1-1, channel); |
77 | double bottomLeft = getIntegralValue(x1-1, y2-1, channel); |
78 | double topLeft = getIntegralValue(x1-1, y1-1, channel); |
79 | ret bottomRight-topRight-bottomLeft+topLeft; |
80 | } |
81 | |
82 | int rgbPixel(int x1, int y1, int x2, int y2) { |
83 | int r = iround(clampZeroToOne(getPixel(x1, y1, x2, y2, 0))*255); |
84 | int g = iround(clampZeroToOne(getPixel(x1, y1, x2, y2, 1))*255); |
85 | int b = iround(clampZeroToOne(getPixel(x1, y1, x2, y2, 2))*255); |
86 | ret rgbInt(r, g, b); |
87 | } |
88 | |
89 | double liveliness(int channel) { |
90 | if (liveliness_cachedChannel != channel) { |
91 | // optimization (but no change in semantics): |
92 | // if (w <= 1 && h <= 1) ret 0; // liveliness of single pixel is 0 |
93 | liveliness_cache = standardDeviation(map(q -> q.getPixel(channel), quadrants())); |
94 | liveliness_cachedChannel = channel; |
95 | } |
96 | ret liveliness_cache; |
97 | } |
98 | |
99 | // no duplicates, without full image |
100 | L<IIntegralImage> descentShapes_cleaned() { |
101 | ret uniquify(listMinus(descentShapes(), this)); |
102 | } |
103 | |
104 | L<IIntegralImage> descentShapes() { |
105 | ret centerPlusQuadrants(); |
106 | } |
107 | |
108 | L<IIntegralImage> centerPlusQuadrants() { |
109 | int midX = w/2, midY = h/2; |
110 | Rect r = rectAround(iround(midX), iround(midY), max(midX, 1), max(midY, 1)); |
111 | ret itemPlusList(clip(r), quadrants()); |
112 | } |
113 | |
114 | L<IIntegralImage> quadrants() { |
115 | if (w <= 1 && h <= 1) null; // let's really not have quadrants of a single pixel |
116 | int midX = w/2, midY = h/2; |
117 | ret mapLL clip( |
118 | rect(0, 0, max(midX, 1), max(midY, 1)), |
119 | rect(midX, 0, w-midX, max(midY, 1)), |
120 | rect(0, midY, max(midX, 1), h-midY), |
121 | rect(midX, midY, w-midX, h-midY) |
122 | ); |
123 | } |
124 | |
125 | IIntegralImage liveliestSubshape(int channel) { |
126 | ret highestBy(q -> q.liveliness(channel), quadrants()); |
127 | } |
128 | |
129 | ProbabilisticList<IIntegralImage> liveliestSubshape_probabilistic(int channel) { |
130 | ret new ProbabilisticList<IIntegralImage>(map(descentShapes(), shape -> |
131 | withProbability(shape.liveliness(channel), shape))); |
132 | } |
133 | |
134 | public IIntegralImage clip(Rect r) { |
135 | Rect me = rect(0, 0, w, h); |
136 | r = intersectRects(r, 0, 0, w, h); |
137 | if (r.x == 0 && r.y == 0 && r.w == w && r.h == h) this; |
138 | ret actuallyClip(r); |
139 | } |
140 | |
141 | IIntegralImage actuallyClip(Rect r) { |
142 | ret newClip(this, r); |
143 | } |
144 | |
145 | public IIntegralImage clip(int x1, int y1, int w, int h) { ret clip(rect(x1, y1, w, h)); } |
146 | |
147 | Rect positionInImage(IIntegralImage mainImage) { |
148 | ret this == mainImage ? positionInImage() : null; |
149 | } |
150 | |
151 | Rect positionInImage() { |
152 | ret rect(0, 0, w, h); |
153 | } |
154 | |
155 | Pt center() { |
156 | ret main center(positionInImage()); |
157 | } |
158 | |
159 | double area() { ret w*h; } |
160 | double relativeArea() { ret area()/mainImage.area(); } |
161 | |
162 | bool singlePixel() { ret w <= 1 && h <= 1; } |
163 | |
164 | toString { ret w + "*" + h; } |
165 | } |
166 | |
167 | // virtual clip of an integral image |
168 | class Clip extends IIntegralImage { |
169 | IIntegralImage fullImage; |
170 | int x1, y1; |
171 | |
172 | *(IIntegralImage *fullImage, Rect r) { |
173 | x1 = r.x; y1 = r.y; w = r.w; h = r.h; |
174 | } |
175 | |
176 | *(IIntegralImage *fullImage, int *x1, int *y1, int *w, int *h) {} |
177 | |
178 | public double getIntegralValue(int x, int y, int channel) { |
179 | ret fullImage.getIntegralValue(x+x1, y+y1, channel); |
180 | } |
181 | |
182 | // don't clip a clip - be smarter than that! |
183 | IIntegralImage actuallyClip(Rect r) { |
184 | ret newClip(fullImage, translateRect(r, x1, y1)); |
185 | } |
186 | |
187 | Rect positionInImage() { |
188 | ret rect(x1, y1, w, h); |
189 | } |
190 | |
191 | Rect positionInImage(IIntegralImage mainImage) { |
192 | try object Rect r = super.positionInImage(mainImage); |
193 | if (fullImage == mainImage) ret rect(x1, y1, w, h); |
194 | null; |
195 | } |
196 | |
197 | toString { ret positionInImage() + " in " + fullImage; } |
198 | } |
199 | |
200 | class CompactIntegralImage extends IIntegralImage { |
201 | short[] data; |
202 | |
203 | // for each 8*8 block, 4 shorts |
204 | // We store the bits 15 to 30 so we have one overlapping |
205 | // bit with the lower words (only way to get the addition right) |
206 | short[] highWords; |
207 | static final int blockShift = 3; |
208 | static final int blockSize = 1 << 3; |
209 | int gridW, gridH; |
210 | |
211 | *(CompactIntegralImage img) { |
212 | w = img.w; |
213 | h = img.h; |
214 | data = img.data; |
215 | highWords = img.highWords; |
216 | gridW = img.gridW; |
217 | gridH = img.gridH; |
218 | } |
219 | |
220 | *(BufferedImage img) { |
221 | w = img.getWidth(); h = img.getHeight(); |
222 | int safety = 64; |
223 | if (longMul(w, h)*channels*256 > Int.MAX_VALUE-safety) fail("Image too big: " + w + "*" + h); |
224 | gridW = iceil_divideByPowerOfTwo(blockShift, w); |
225 | gridH = iceil_divideByPowerOfTwo(blockShift, h); |
226 | highWords = new short[gridW*gridH*channels]; |
227 | |
228 | int[] pixels = pixelsOfBufferedImage(img); |
229 | data = new short[w*h*channels]; |
230 | int i = 0, j = 0; |
231 | int[] sum = new[channels]; |
232 | int[] lastRow = new[w*channels]; |
233 | int iHighWords = 0; |
234 | for y to h: { |
235 | for c to channels: sum[c] = 0; |
236 | int iLastRow = 0; |
237 | for x to w: { |
238 | int rgb = pixels[j++] & 0xFFFFFF; |
239 | for c to channels: { |
240 | int current; |
241 | if (c == grayscale) |
242 | current = iround((sum[0]+sum[1]+sum[2])/3); |
243 | else { |
244 | current = (sum[c] += rgb >> 16); |
245 | rgb = (rgb << 8) & 0xFFFFFF; |
246 | } |
247 | int value = current + lastRow[iLastRow]; |
248 | short low = (short) value; |
249 | data[i] = low; |
250 | lastRow[iLastRow++] = value; |
251 | i++; |
252 | |
253 | if (debug) |
254 | printVars(+x, +y, +c, rgb := intToHex(rgb), +current, +value, low := ushortToInt(low), +iLastRow, +iHighWords); |
255 | |
256 | // if top-left corner of a block, set high word |
257 | if ((x & (blockSize-1)) == 0 && (y & (blockSize-1)) == 0) { |
258 | short high = (short) (value >> 15); |
259 | highWords[iHighWords++] = high; |
260 | if (debug) |
261 | printVars(+high); |
262 | ifdef CompactIntegralImage_safetyTests |
263 | int blockX = x >> blockShift, blockY = y >> blockShift; |
264 | assertEquals(iHighWords-1, (blockY*gridW+blockX)*channels+c); |
265 | assertEquals(ushortToInt(high), getHighWord(x, y, c)); |
266 | if (blockX == 141) { |
267 | printVars(+x, +y, +c, rgb := intToHex(rgb), +current, +value, low := ushortToInt(low), +iLastRow, +iHighWords); |
268 | printVars(+blockX, +blockY, +high); |
269 | } |
270 | endifdef |
271 | } |
272 | } |
273 | } |
274 | } |
275 | } |
276 | |
277 | int getHighWord(int x, int y, int channel) { |
278 | int blockX = x >> blockShift, blockY = y >> blockShift; |
279 | ret ushortToInt(highWords[(blockY*gridW+blockX)*channels+channel]); |
280 | } |
281 | |
282 | public double getIntegralValue(int x, int y, Channel channel) { |
283 | if (x < 0 || y < 0) ret 0; |
284 | y = min(y, h-1); |
285 | x = min(x, w-1); |
286 | int blockX = x >> blockShift, blockY = y >> blockShift; |
287 | int low = data[(y*w+x)*channels+channel]; |
288 | int high = ushortToInt(highWords[(blockY*gridW+blockX)*channels+channel]); |
289 | |
290 | int value = ((high & 1) == 0) |
291 | // - need unsigned expansion of low |
292 | // bit 15 in block is 0 in top left corner |
293 | ? (high << 15) + (low & 0xFFFF) |
294 | |
295 | // bit 15 in block is 1 in top left corner |
296 | // - need signed expansion of low |
297 | : ((high+1) << 15) + low; |
298 | |
299 | if (debug) |
300 | printVars getIntegralValue(+x, +y, +channel, +blockX, +blockY, +high, low := low + "/" + ushortToInt((short) low), +value); |
301 | |
302 | ret value; |
303 | } |
304 | } |
305 | |
306 | IIntegralImage newClip(IIntegralImage fullImage, Rect r) { |
307 | assertSame(fullImage, mainImage); |
308 | ret getOrCreate(clipCache, r, () -> new Clip(fullImage, r)); |
309 | } |
310 | |
311 | IIntegralImage liveliestPointIn(IIntegralImage image) { |
312 | ret applyUntilEqual_goOneBackOnNull(c -> c.liveliestSubshape(grayscale), image); |
313 | } |
314 | |
315 | // level++ <=> a fourth the area |
316 | double level(IIntegralImage image) { |
317 | ret -log(image.relativeArea(), 4); |
318 | } |
319 | |
320 | // featureSize = relative to smaller image dimension |
321 | double actualFeatureSize() { |
322 | ret featureSize*min(mainImage.w, mainImage.h); |
323 | } |
324 | |
325 | void clearCaches { |
326 | clipCache.clear(); |
327 | } |
328 | |
329 | void prepareImage { |
330 | if (mainImage == null) |
331 | // make integral image |
332 | mainImage = new CompactIntegralImage(inputImage); |
333 | else |
334 | // not sure why we are doing this one or whether we should do it |
335 | mainImage = new CompactIntegralImage(mainImage); |
336 | inputImage = null; |
337 | |
338 | if (verbose || verboseImageSize) print("Full image size: " + mainImage.w + "*" + mainImage.h); |
339 | } |
340 | |
341 | run { |
342 | prepareImage(); |
343 | |
344 | time "Simplification" { |
345 | } |
346 | } |
347 | |
348 | void setInputImage aka setImage(BufferedImage image) { |
349 | inputImage = image; |
350 | mainImage = null; |
351 | } |
352 | } |
Began life as a copy of #1032248
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx
No comments. add comment
Snippet ID: | #1032258 |
Snippet name: | ImageSimplifier v1 [backup with broken CompactIntegralImage - wrong approach] |
Eternal ID of this version: | #1032258/1 |
Text MD5: | 5168e46fdf1a3f1253fa799a389c3b46 |
Transpilation MD5: | c927585b45170053f596ff3c06282f3d |
Author: | stefan |
Category: | javax / image recognition |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-08-23 03:21:00 |
Source code size: | 11341 bytes / 352 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 132 / 188 |
Referenced in: | [show references] |