1 | sclass SimpleRecognizer { |
2 | bool useCache1 = true, useCache2 = true; |
3 | |
4 | S initial = [[ |
5 | The images "9176714c9a935fc91e14595dbb3adddf 35731666e72dd0c9448f616ff3a7464a da9bc5a24bd503e898f69ad43bb9b92e" are the characters "yet". |
6 | The images "412f9e0f8e177817a4fa285415c5a13f 386b289407599f31e3a7c57c3adb2587 6cd2a2284a5a6fda3a7cad3e3a048671 b0c47c014d665ec5b658f510c258dc47 b273ff598fe6afa04cfed9f9e8fb4109" are the grouped characters "[Ob]ject". |
7 | The images "175116b749670b7b65707c10c935b42c ddaf2b9c198818c49628387ecc2910ed 3777597e39eb0de16c022293c8c91cc0 fad86bda3f716ef4e7fa9d199f6c4383 35731666e72dd0c9448f616ff3a7464a 03b96c30adaaed5b60cea4ab9bc37263" are the characters "images". |
8 | ]]; |
9 | |
10 | Lock lock = lock(); |
11 | |
12 | class GlyphInfo { |
13 | S meaning; |
14 | bool multi; // multiple meanings seen |
15 | } |
16 | |
17 | // key = md5 |
18 | new Map<S, GlyphInfo> glyphInfos; |
19 | |
20 | // optional for full similarity search - character image to MD5 |
21 | Map<BWImage, S> fullSearchMap; |
22 | |
23 | S unknownCharacter = diamond(); // "\u2666" - diamond suit symbol; used for unknown characters |
24 | |
25 | *() { |
26 | load(initial); |
27 | } |
28 | |
29 | void load(S info) { |
30 | lock lock; |
31 | recognizeGrouped_cache.clear(); |
32 | new Matches m; |
33 | for (S s : toLinesFullTrim(info)) { |
34 | if (find3("the images * are the characters *", s, m)) { |
35 | L<S> md5s = splitAtSpace($1); |
36 | L<S> characters = eachCharAsString(dropSpaces($2)); |
37 | saveMeanings(md5s, characters); |
38 | } else if (find3("the images * are the grouped characters *", s, m)) { |
39 | L<S> md5s = splitAtSpace($1); |
40 | L<S> characters = ocr_parseGlyphs(dropSpaces($2)); |
41 | saveMeanings(md5s, characters); |
42 | } else if (nempty(javaTokC(s))) { |
43 | print("huh? " + s); |
44 | } |
45 | } |
46 | //print("Have " + n(l(glyphInfos), "glyph info")); |
47 | //psl(glyphInfos); |
48 | } |
49 | |
50 | void saveMeaning(S md5, S meaning) { |
51 | GlyphInfo info = getGlyphInfo(md5); |
52 | if (info.multi) ret; |
53 | if (hasDifferent(info.meaning, meaning)) { |
54 | //info.meaning = null; |
55 | info.meaning = meaning; |
56 | info.multi = true; |
57 | //print("multi"); |
58 | } else |
59 | info.meaning = meaning; |
60 | } |
61 | |
62 | // gets or creates GlyphInfo |
63 | GlyphInfo getGlyphInfo(S md5) { |
64 | GlyphInfo info = glyphInfos.get(md5); |
65 | if (info == null) |
66 | glyphInfos.put(md5, info = new GlyphInfo); |
67 | ret info; |
68 | } |
69 | |
70 | void saveMeanings(L<S> md5s, L<S> characters) { |
71 | if (l(md5s) != l(characters)) { print("huh?"); ret; } |
72 | for i over md5s: |
73 | saveMeaning(md5s.get(i), characters.get(i)); |
74 | } |
75 | |
76 | // lookup stored recognition of whole image |
77 | Scored<S> recognizeCheat(BWImage img) { |
78 | lock lock; |
79 | Scored<GlyphInfo> scored = recognizeGlyph(img, false); |
80 | GlyphInfo info = getVar(scored); |
81 | if (info != null && info.meaning != null) ret scored(info.meaning, scored); |
82 | null; |
83 | } |
84 | |
85 | S recognize(BWImage img) { |
86 | ret ocr_joinGroups(recognizeGrouped(img)); |
87 | } |
88 | |
89 | Scored<S> recognizeScored(BWImage img) { |
90 | Scored<L<S>> s = recognizeGrouped(img, null); |
91 | ret scored(ocr_joinGroups(s!), s); |
92 | } |
93 | |
94 | L<S> recognizeGrouped(BWImage img) { |
95 | ret getVar(recognizeGrouped(img, null)); |
96 | } |
97 | |
98 | // md5 -> recognition result |
99 | Map<S, Scored<L<S>>> recognizeGrouped_cache = synchroMap(); |
100 | int cantCache, cacheHits, cacheMisses; |
101 | |
102 | Scored<L<S>> recognizeGrouped(BWImage img, L<Rect> clips_out) { |
103 | S md5 = null; |
104 | if (clips_out != null || !useCache1) ++cantCache; |
105 | else { |
106 | md5 = md5OfBWImage(img); |
107 | Scored<L<S>> result = recognizeGrouped_cache.get(md5); |
108 | if (result != null) { |
109 | ++cacheHits; |
110 | ret result; |
111 | } else ++cacheMisses; |
112 | } |
113 | |
114 | Scored<L<S>> result = recognizeGrouped_uncached(img, clips_out); |
115 | if (md5 != null) recognizeGrouped_cache.put(md5, result); |
116 | ret result; |
117 | } |
118 | |
119 | Scored<L<S>> recognizeGrouped_uncached(BWImage img, L<Rect> clips_out) { |
120 | lock lock; |
121 | // TODO: auto-crop here? |
122 | Scored<S> s = recognizeCheat(img); |
123 | if (s != null) { |
124 | if (clips_out != null) clips_out.add(new Rect(0, 0, img.w(), img.h())); |
125 | ret scored(ll(ocr_escapeMeaning(s!)), s); // hmm... how to group? does it matter? |
126 | } |
127 | |
128 | new L<S> buf; |
129 | L<Rect> rects = horizontalAutoSplit2ThenAutoCrop(img); |
130 | if (empty(rects)) ret scored(ll(unknownCharacter), 0.5); |
131 | new L<Scored> scores; |
132 | iLoop: for (int i = 0; i < l(rects); i++) { |
133 | Rect r = null; |
134 | for (int j = i; j < l(rects); j++) { |
135 | r = rectUnion(r, rects.get(j)); |
136 | BWImage cImg = img.clip(r); |
137 | Scored<GlyphInfo> scored = recognizeGlyph(cImg, false); |
138 | GlyphInfo info = getVar(scored); |
139 | if (info != null && info.meaning != null) { |
140 | buf.add(info.meaning); |
141 | buf.addAll(rep("_", j-i)); |
142 | if (clips_out != null) clips_out.addAll(rep(r, j-i+1)); |
143 | scores.add(scored); |
144 | i = j; |
145 | continue iLoop; |
146 | } |
147 | } |
148 | r = rects.get(i); |
149 | Scored<GlyphInfo> scored = recognizeGlyph(img.clip(r), true); |
150 | GlyphInfo info = getVar(scored); |
151 | if (info != null && info.meaning != null) |
152 | buf.add(info.meaning); |
153 | else |
154 | buf.add(unknownCharacter); |
155 | if (clips_out != null) clips_out.add(r); |
156 | scores.add(scored); |
157 | } |
158 | ret scored(buf, averageScore(scores)); |
159 | } |
160 | |
161 | // md5 -> recognition result |
162 | Map<S, Scored<GlyphInfo>> recognizeGlyph_cache = synchroMap(); |
163 | static int cacheHits2, cacheMisses2; |
164 | |
165 | Scored<GlyphInfo> recognizeGlyph(BWImage img, bool fullSearch) { |
166 | S md5 = md5OfBWImage(img); |
167 | GlyphInfo info = glyphInfos.get(md5); |
168 | if (info != null || !fullSearch || fullSearchMap == null) ret fullScored(info); |
169 | if (useCache2) { |
170 | Scored<GlyphInfo> result = recognizeGlyph_cache.get(md5); |
171 | if (result != null) { ++cacheHits2; ret result; } |
172 | cacheMisses2++; |
173 | } |
174 | |
175 | new Best<S> best; |
176 | for (BWImage cImg : keys(fullSearchMap)) { |
177 | float sim = bwImageSimilarityResized(img, cImg, (float) best.bestScore()); |
178 | best.put(fullSearchMap.get(cImg), sim); |
179 | } |
180 | Scored<GlyphInfo> result = !best.has() ? null : scored(glyphInfos.get(best!), best.score()); |
181 | if (useCache2) |
182 | recognizeGlyph_cache.put(md5, result); |
183 | ret result; |
184 | } |
185 | |
186 | S cacheStats() { |
187 | //ret "Cache size: " + l(recognizeGrouped_cache) + ", hits: " + cacheHits + ", misses: " + cacheMisses + ", uncachable: " + cantCache; |
188 | ret "Cache size: " + l(recognizeGlyph_cache) + ", hits: " + cacheHits2 + ", misses: " + cacheMisses2 + ", full search map: " + l(fullSearchMap); |
189 | } |
190 | } |
Began life as a copy of #1006108
download show line numbers debug dex old transpilations
Travelled to 13 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1015444 |
Snippet name: | class SimpleRecognizer v1 - recognizes a line of text - works, but slowly |
Eternal ID of this version: | #1015444/1 |
Text MD5: | 643bb1d800a7b79993812b9da91ef543 |
Author: | stefan |
Category: | javax / ocr |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2018-05-14 01:04:14 |
Source code size: | 6653 bytes / 190 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 355 / 395 |
Referenced in: | [show references] |