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