Uses 911K of libraries. Click here for Pure Java version (19060L/109K).
1 | !7 |
2 | |
3 | cmodule ChessBoardRecognizer { |
4 | S segmenterPreset; |
5 | ParameterizedSegmenter segmenter; |
6 | L<Rect> segments; |
7 | Rect boardLocation; |
8 | double recognitionScore; |
9 | S status; |
10 | S selectedSquare; |
11 | S piece; |
12 | LPair<S, Double> recognized; |
13 | S fen; |
14 | |
15 | switchable bool squareHeightAtBottom = true; // for YouTube videos |
16 | transient int recreatedSquareSize = 32; |
17 | transient ReliableSingleThread rst = dm_rst(module(), r recognize); |
18 | transient BufferedImage img, board, square, recreated; |
19 | transient LL<BufferedImage> squareImages; |
20 | transient ImageSurface isInput, isBoard, isSquare, isRecreated; |
21 | |
22 | transient LS pieces = ai_chessPieces(); |
23 | transient Map<Bool, Map<S, BufferedImage>> recreateStock; // false = dark, true = light |
24 | |
25 | visualize { |
26 | isBoard = jImageSurface(); |
27 | imageSurfaceOnHover(isBoard, voidfunc(Pt p) { |
28 | if (board == null || p == null) ret with noToolTip(isBoard); |
29 | setToolTip(isBoard, toolTipForSquare(boardPointToSquare(p)); |
30 | }); |
31 | imageSurfaceOnLeftClick(isBoard, voidfunc(Pt p) { |
32 | selectSquare(boardPointToSquare(p)) |
33 | }); |
34 | |
35 | isRecreated = jImageSurface(recreated); |
36 | imageSurfaceOnHover(isRecreated, voidfunc(Pt p) { |
37 | if (recreated == null || p == null) ret with noToolTip(isRecreated); |
38 | setToolTip(isRecreated, toolTipForSquare(recreatedPointToSquare(p)); |
39 | }); |
40 | imageSurfaceOnLeftClick(isRecreated, voidfunc(Pt p) { |
41 | selectSquare(recreatedPointToSquare(p)) |
42 | }); |
43 | |
44 | ret withCalc(rst, jvsplit( |
45 | jCenteredSection("Input image", jscroll_center(isInput = jImageSurface(or_func(img, () -> whiteImage(100))))), |
46 | northCenterAndSouthWithMargins( |
47 | centerAndEastWithMargin(withLabel("Segmenter preset: ", main.onChange(rst, dm_comboBox segmenterPreset(agiBlue_chessBoardSegmenterPresetNames()))), |
48 | hstackWithSpacing( |
49 | jbutton("Recognize", rst), |
50 | jPopDownButton_noText( |
51 | "Show possible chess boards found", rThread showSegments, |
52 | "Publish training image...", rThread publishImage, |
53 | "---", null, |
54 | "Screenshot (hiding OS)", rThread { |
55 | isInput.setImageAndZoomToDisplay(dm_shootScreenHidingOS()); |
56 | rst.trigger(); |
57 | }) |
58 | )), |
59 | hgridWithSpacing( |
60 | jCenteredSection("Recreated", jscroll_center(isRecreated)), |
61 | jCenteredSection("Chess board in image", |
62 | centerAndSouthWithMargin(jscroll_center(isBoard), |
63 | dm_centeredCalculatedLabel(() -> "Recognition score: " + formatDouble(recognitionScore, 2)) |
64 | )), |
65 | jCenteredLiveValueSection(dm_calculatedLiveValue(S, () -> "Selected square" + (empty(selectedSquare) ? "" : " (" + selectedSquare + ")")), centerAndSouthWithMargin( |
66 | jscroll_center(isSquare = jImageSurface(square)), |
67 | rightAlignedLine( |
68 | withLabel("Piece: ", dm_comboBox('piece, pieces)), |
69 | jbutton("Save & upload", rThread saveAndUploadPiece)) |
70 | ))), |
71 | vstackWithSpacing( |
72 | dm_label('status), |
73 | withLabel("FEN:", dm_label('fen)) |
74 | ) |
75 | ))); |
76 | } |
77 | |
78 | void recognize { |
79 | status("Recognizing..."); |
80 | segmenter = parameterizedSegmenterFromAGIBlue(segmenterPreset); |
81 | if (segmenter == null) ret with infoBox(status("No segmenter")); |
82 | //img = dm_shootScreenHidingOS(); |
83 | if (isInput != null) img = isInput.getImage(); |
84 | L<Rect> rects = segmenter.get(img); |
85 | setField(segments := rects); |
86 | if (empty(rects)) { |
87 | clearImageSurfaces(isBoard, isRecreated); |
88 | setField(fen := ""); |
89 | ret with status("No chess board found"); |
90 | } |
91 | Rect r = first(rects); |
92 | bool corrected = squareHeightAtBottom && abs(r.w-r.h) > 3; |
93 | if (corrected) r.h = r.w; |
94 | new LS comments; |
95 | if (l(rects) > 1) comments.add("randomly choosing one from " + nBoards(rects)); |
96 | if (corrected) comments.add("height-corrected"); |
97 | status("Chess board found at: " + r + appendRoundBracketed(joinWithComma(comments))); |
98 | setField(boardLocation := r); |
99 | board = clipBufferedImage(img, r); |
100 | |
101 | squareImages = bufferedImageMNGrid(board, 8, 8); |
102 | |
103 | if (isBoard != null) |
104 | isBoard.setImageAndZoomToDisplay( |
105 | mergeBufferedImagesVertically( |
106 | map(bufferedImageNVerticalSlices(8, board), i -> |
107 | mergeBufferedImagesHorizontally(bufferedImageNHorizontalSlices(8, i))))); |
108 | |
109 | new ChessPieceRecognizer pieceRecognizer; |
110 | pieceRecognizer.load(); |
111 | recognized = new L; |
112 | for (BufferedImage img : concatLists(squareImages)) |
113 | recognized.add(pieceRecognizer.recognize(img)); |
114 | setField(recognitionScore := doubleAvg(pairsB(recognized)); |
115 | setField(fen := makeFEN()); |
116 | print(recognized); |
117 | recreate(); |
118 | } |
119 | |
120 | S status(S status) { setField(+status); ret status; } |
121 | |
122 | O _getReloadData() { ret img; } |
123 | void _setReloadData(BufferedImage img) { this.img = img; } |
124 | |
125 | void setImage(BufferedImage img) { |
126 | this.img = img; |
127 | if (isInput != null) isInput.setImageAndZoomToDisplay(img); |
128 | rst.trigger(); |
129 | } |
130 | |
131 | S boardPointToSquare(Pt p) { |
132 | double space = imageMergeSpacing(); |
133 | double squareW = board.getWidth()/8.0+space; |
134 | double squareH = board.getHeight()/8.0+space; |
135 | int x = clamp(iratio_floor(p.x+space/2, squareW), 0, 7); |
136 | int y = clamp(iratio_floor(p.y+space/2, squareH), 0, 7); |
137 | ret strCharPlus('A', x) + (8-y); |
138 | } |
139 | |
140 | S recreatedPointToSquare(Pt p) { |
141 | int x = clamp(iratio_floor(p.x, recreatedSquareSize), 0, 7); |
142 | int y = clamp(iratio_floor(p.y, recreatedSquareSize), 0, 7); |
143 | ret strCharPlus('A', x) + (8-y); |
144 | } |
145 | |
146 | void saveAndUploadPiece enter { |
147 | if (square == null || empty(piece)) ret; |
148 | chessOCR_uploadPieceImage(selectedSquare, piece, square); |
149 | if (mapGet(mapGet(recreateStock, chess_isLightSquare(selectedSquare)), piece) == null) recreateStock = null; |
150 | rst.trigger(); |
151 | } |
152 | |
153 | void recreate { |
154 | if (recognized == null) ret; |
155 | if (recreateStock == null) |
156 | recreateStock = mapToValues(ll(false, true), light -> |
157 | mapValues(pairsToMap(reversePairs(chessPieceImagesFromAGIBlue(light))), imageID -> resizeImage(loadImage2(imageID), recreatedSquareSize, recreatedSquareSize))); |
158 | |
159 | new L<BufferedImage> out; |
160 | int i = 0; |
161 | for (S piece : pairsA(recognized)) { |
162 | bool light = even(i/8+i%8); |
163 | out.add(or_func(mapGet(mapGet(recreateStock, light), piece), () -> whiteImage(recreatedSquareSize, recreatedSquareSize))); |
164 | ++i; |
165 | } |
166 | |
167 | new L<BufferedImage> rows; |
168 | for (L<BufferedImage> row : listToChunks(out, 8)) |
169 | rows.add(mergeBufferedImagesHorizontally(row, spacing := 0)); |
170 | recreated = mergeBufferedImagesVertically(rows, spacing := 0); |
171 | if (isRecreated != null) isRecreated.setImageAndZoomToDisplay(recreated); |
172 | } |
173 | |
174 | Pair<S, Double> recognitionForSquare(S square) { |
175 | int x, y = unpair chess_squareToPos(square); |
176 | ret _get(recognized, y*8+x); |
177 | } |
178 | |
179 | S toolTipForSquare(S square) { |
180 | Pair<S, Double> rec = recognitionForSquare(square); |
181 | ret square + " - " + (rec == null ? "?" : rec.a + " [" + iround(clamp(rec.b, 0, 100)) + "%]"); |
182 | } |
183 | |
184 | void selectSquare(S _square) { |
185 | setField(selectedSquare := _square); |
186 | S rec = pairA(recognitionForSquare(selectedSquare)); |
187 | if (rec != null) setField(piece := rec); |
188 | int x, y = unpair chess_squareToPos(selectedSquare); |
189 | square = _get(_get(squareImages, y), x); |
190 | |
191 | ChessPieceProfile1 profile = chessOCR_pieceProfileFromRawImage_1(square, withPreprocessedImages := true); |
192 | isSquare.setImage(mergeBufferedImagesHorizontally( |
193 | itemPlusList(square, map toBufferedImage(profile.preprocessedImages)))); |
194 | //printVars_str("leftClick", +p, +selectedSquare, +x, +y, +square); |
195 | } |
196 | |
197 | void showSegments { |
198 | L<Rect> segments = this.segments; |
199 | showImage_centered(n2(segments, "segment") + " found", |
200 | mergeBufferedImagesVertically(map(segments, r -> clipBufferedImage(img, r)))); |
201 | } |
202 | |
203 | void publishImage enter { |
204 | BufferedImage img = this.img; |
205 | S segmenterPreset = this.segmenterPreset; |
206 | |
207 | JCheckBox cbFound = jcheckbox(true), cbHasBoard = jcheckbox(true); |
208 | JTextField tfFEN = jtextfield(fen), tfComments = jtextfield(), |
209 | tfImageCredits = jtextfield(); |
210 | |
211 | showFormTitled("Publish training image", |
212 | "Image", jImageSurface(scaleImageToWidth(img, 500)), |
213 | "Segmenter used", jlabel(segmenterPreset), |
214 | "Is there a chess board in the image?", cbHasBoard, |
215 | "FEN (empty if no board in image)", tfFEN, |
216 | "Was the chess board found?", cbFound, // TODO: make clearer what this means if there is no board in image |
217 | "Image credits (optional)", tfImageCredits, |
218 | "Random comments", tfComments, |
219 | "NOTE:", jMultiLineLabel("By clicking 'publish', you PUBLISH this image (fair use/public domain)."), |
220 | "", jThreadedButton("Publish", r { |
221 | temp tempInfoBox_noHide("Uploading training image..."); |
222 | S imageURL = uploadToImageServer("Chess board recognition training image", img); |
223 | S desc = "Chess board recognition training image " + assertNempty("Parseable image URL", parseSnippetImageURL(imageURL)); |
224 | new LinkedHashMap<S> map; |
225 | map.put("Input image", imageURL); |
226 | mapPutIfNemptyValue(map, "Image credits", gtt(tfImageCredits)); |
227 | S fen = gtt(tfFEN); |
228 | map.put("Image has chess board", yesNo_short_firstUpper(isChecked(cbHasBoard) || nempty(fen))); |
229 | mapPutIfNemptyValue(map, "FEN", fen); |
230 | map.put("Segmenter used", segmenterPreset); |
231 | map.put("Chess board found", yesNo_short_firstUpper(isChecked(cbFound))); |
232 | if (isChecked(cbFound)) |
233 | map.put("Board location", struct(boardLocation)); |
234 | S sliceID = agiBlue_chessBoardRecognitionTrainingImagesSliceID(); |
235 | new L<Map> toPost; |
236 | S name = agiBlue_createUnusedNumberedPage(sliceID, desc + " #"); |
237 | for (S key, value : map) |
238 | toPost.add(litmap(q := name, +key, +value)); |
239 | agiBot_postMulti(keyPairForProgram(), toPost, slice := sliceID); |
240 | infoBox("Training image uploaded!"); |
241 | openURLInBrowser(agiBlue_linkForPhrase(name, slice := sliceID)); |
242 | disposeWindow(heldButton()); |
243 | }) |
244 | ); |
245 | } |
246 | |
247 | S makeFEN() { |
248 | ret chess_makeFEN(listToChunks(pairsA(recognized), 8)); |
249 | } |
250 | } |
Began life as a copy of #1024674
download show line numbers debug dex old transpilations
Travelled to 6 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1024849 |
Snippet name: | Chess Board Recognizer backup |
Eternal ID of this version: | #1024849/1 |
Text MD5: | cd3d7c3088fbd55d9a6ad9c4571144bc |
Transpilation MD5: | a754af502b229875faad6400537038de |
Author: | stefan |
Category: | javax / image recognition |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2019-08-27 01:30:59 |
Source code size: | 10480 bytes / 250 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 168 / 229 |
Referenced in: | [show references] |