Uses 108K of libraries. Click here for Pure Java version (14093L/86K).
1 | do not include class IntegralImage. |
2 | do not include class IIntegralImage. |
3 | |
4 | srecord noeq MinimalRecognizer(BufferedImage inputImage) { |
5 | replace Channel with int. |
6 | |
7 | IIntegralImage mainImage; |
8 | |
9 | static final int grayscale = 3; // channel number for grayscale |
10 | |
11 | abstract class IIntegralImage { |
12 | // width and height of image |
13 | int w, h; |
14 | |
15 | abstract double integralValue(int x, int y, Channel channel); |
16 | |
17 | BufferedImage render() { |
18 | ret imageFromFunction(w, h, (x, y) -> rgbPixel(x, y, x+1, y+1) | fullAlphaMask()); |
19 | } |
20 | |
21 | double getPixel(Rect r, int channel) { |
22 | ret getPixel(r.x, r.y, r.x2(), r.y2(), channel); |
23 | } |
24 | |
25 | double getPixel(int channel) { ret getPixel(0, 0, w, h, channel); } |
26 | |
27 | // return value ranges from 0 to 1 (usually) |
28 | double getPixel(int x1, int y1, int x2, int y2, int channel) { |
29 | ret doubleRatio(rectSum(x1, y1, x2, y2, channel), (x2-x1)*(y2-y1)*255.0); |
30 | } |
31 | |
32 | double rectSum(Rect r, int channel) { |
33 | ret rectSum(r.x, r.y, r.x2(), r.y2(), channel); |
34 | } |
35 | |
36 | double rectSum(int x1, int y1, int x2, int y2, int channel) { |
37 | double bottomLeft = integralValue(x1-1, y2-1, channel); |
38 | double bottomRight = integralValue(x2-1, y2-1, channel); |
39 | double topLeft = integralValue(x1-1, y1-1, channel); |
40 | double topRight = integralValue(x2-1, y1-1, channel); |
41 | ret bottomRight-topRight-bottomLeft+topLeft; |
42 | } |
43 | |
44 | int rgbPixel(int x1, int y1, int x2, int y2) { |
45 | int r = iround(clampZeroToOne(getPixel(x1, y1, x2, y2, 0))*255); |
46 | int g = iround(clampZeroToOne(getPixel(x1, y1, x2, y2, 1))*255); |
47 | int b = iround(clampZeroToOne(getPixel(x1, y1, x2, y2, 2))*255); |
48 | ret rgbInt(r, g, b); |
49 | } |
50 | |
51 | double liveliness(int channel) { |
52 | // optimization (but no change in semantics): |
53 | // if (w <= 1 && h <= 1) ret 0; // liveliness of single pixel is 0 |
54 | ret standardDeviation(map(q -> q.getPixel(channel), quadrants())); |
55 | } |
56 | |
57 | L<IIntegralImage> descentShapes() { |
58 | ret centerPlusQuadrants(); |
59 | } |
60 | |
61 | L<IIntegralImage> centerPlusQuadrants() { |
62 | int midX = w/2, midY = h/2; |
63 | Rect r = rectAround(midX, midY, max(midX/2, 1), max(midY/2, 1)); |
64 | ret itemPlusList(clip(r), quadrants()); |
65 | } |
66 | |
67 | L<IIntegralImage> quadrants() { |
68 | if (w <= 1 && h <= 1) null; // let's really not have quadrants of a single pixel |
69 | int midX = w/2, midY = h/2; |
70 | ret mapLL clip( |
71 | rect(0, 0, max(midX, 1), max(midY, 1)), |
72 | rect(midX, 0, w-midX, max(midY, 1)), |
73 | rect(0, midY, max(midX, 1), h-midY), |
74 | rect(midX, midY, w-midX, h-midY) |
75 | ); |
76 | } |
77 | |
78 | IIntegralImage liveliestSubshape(int channel) { |
79 | ret highestBy(q -> q.liveliness(channel), quadrants()); |
80 | } |
81 | |
82 | ProbabilisticList<IIntegralImage> liveliestSubshape_probabilistic(int channel) { |
83 | ret new ProbabilisticList<IIntegralImage>(map(descentShapes(), shape -> |
84 | withProbability(shape.liveliness(channel), shape))); |
85 | } |
86 | |
87 | Clip clip(Rect r) { ret new Clip(this, r); } |
88 | Clip clip(int x1, int y1, int w, int h) { ret clip(rect(x1, y1, w, h)); } |
89 | |
90 | Rect positionInImage(IIntegralImage mainImage) { |
91 | if (this == mainImage) ret rect(0, 0, w, h); |
92 | null; |
93 | } |
94 | } |
95 | |
96 | // virtual clip of an integral image |
97 | class Clip extends IIntegralImage { |
98 | IIntegralImage fullImage; |
99 | int x1, y1; |
100 | |
101 | *(IIntegralImage *fullImage, Rect r) { |
102 | x1 = r.x; y1 = r.y; w = r.w; h = r.h; |
103 | } |
104 | |
105 | *(IIntegralImage *fullImage, int *x1, int *y1, int *w, int *h) {} |
106 | |
107 | public double integralValue(int x, int y, int channel) { |
108 | ret fullImage.integralValue(x+x1, y+y1, channel); |
109 | } |
110 | |
111 | // don't clip a clip - be smarter than that! |
112 | Clip clip(Rect r) { |
113 | ret new Clip(fullImage, translateRect(r, x1, y1)); |
114 | } |
115 | |
116 | Rect rectInImage() { |
117 | ret rect(x1, y1, w, h); |
118 | } |
119 | |
120 | Rect positionInImage(IIntegralImage mainImage) { |
121 | try object Rect r = super.positionInImage(mainImage); |
122 | if (fullImage == mainImage) ret rect(x1, y1, w, h); |
123 | null; |
124 | } |
125 | |
126 | toString { ret rectInImage() + " in " + fullImage; } |
127 | } |
128 | |
129 | class IntegralImage extends IIntegralImage { |
130 | int[] data; |
131 | |
132 | *(BufferedImage img) { |
133 | w = img.getWidth(); h = img.getHeight(); |
134 | if (longMul(w, h) > 8000000) fail("Image too big: " + w + "*" + h); |
135 | int[] pixels = pixelsOfBufferedImage(img); |
136 | data = new int[w*h*3]; |
137 | int i = 0, j = 0; |
138 | int[] sum = new[3]; |
139 | for y to h: { |
140 | for c to 3: sum[c] = 0; |
141 | for x to w: { |
142 | int rgb = pixels[j++] & 0xFFFFFF; |
143 | for c to 3: { |
144 | data[i] = (sum[c] += rgb >> 16); |
145 | if (y > 0) |
146 | data[i] += data[i-w*3]; |
147 | i++; |
148 | rgb = (rgb << 8) & 0xFFFFFF; |
149 | } |
150 | } |
151 | } |
152 | } |
153 | |
154 | public double integralValue(int x, int y, Channel channel) { |
155 | if (channel == grayscale) |
156 | ret doubleAvg(countIterator(3, c -> integralValue(x, y, c))); |
157 | |
158 | ret x < 0 || y < 0 ? 0 |
159 | : data[(min(y, h-1)*w+min(x, w-1))*3+channel]; |
160 | } |
161 | } |
162 | |
163 | IIntegralImage liveliestPointIn(IIntegralImage image) { |
164 | ret applyUntilEqual_goOneBackOnNull(c -> c.liveliestSubshape(grayscale), image); |
165 | } |
166 | |
167 | int highLevel = 3; // determines how many points you get |
168 | int maxPoints = 1000; |
169 | |
170 | run { |
171 | mainImage = new IntegralImage(inputImage); |
172 | inputImage = null; // save space |
173 | |
174 | //print(liveliness := mainImage.liveliness(grayscale)); |
175 | new L<ProbabilisticList<IIntegralImage>> schedulers; |
176 | new BetterLinkedHashSet<IIntegralImage> liveliestPoints; |
177 | schedulers.add(mainImage.liveliestSubshape_probabilistic(grayscale)); |
178 | |
179 | while ping (nempty(schedulers)) { |
180 | // go to high level |
181 | truncateList(schedulers, highLevel); |
182 | |
183 | // clean up used schedulers |
184 | while (nempty(schedulers) && empty(last(schedulers))) |
185 | removeLast(schedulers); |
186 | |
187 | if (empty(schedulers)) break; |
188 | |
189 | // quick descend |
190 | while (l(schedulers) < 20) { |
191 | var nextSeed = popFirst(last(schedulers))!; |
192 | var newOptions = nextSeed.liveliestSubshape_probabilistic(grayscale); |
193 | print(+nextSeed); |
194 | pnl(newOptions); |
195 | if (empty(newOptions)) break; |
196 | schedulers.add(newOptions); |
197 | } |
198 | |
199 | if (empty(schedulers)) break; |
200 | |
201 | // return a result |
202 | liveliestPoints.add(popFirst(last(schedulers))!); |
203 | if (l(liveliestPoints) >= maxPoints) break; |
204 | } |
205 | showImageWithSelections(mainImage.render(), |
206 | map(liveliestPoints, p -> growRect(1.5, p.positionInImage(mainImage)))); |
207 | //pnl(subshapes); |
208 | } |
209 | } |
Began life as a copy of #1032199
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx
No comments. add comment
Snippet ID: | #1032205 |
Snippet name: | Minimal Recognizer [backup 2] |
Eternal ID of this version: | #1032205/1 |
Text MD5: | 9c8ef4b75efd3365aeadec6d541ee82c |
Transpilation MD5: | 8f03e2bbfcfd8888b7ac493fac69f9d2 |
Author: | stefan |
Category: | |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-08-20 06:15:50 |
Source code size: | 6791 bytes / 209 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 155 / 212 |
Referenced in: | [show references] |