@graciousstar/node-red-contrib-vision-tools 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +875 -0
- package/CHANGELOG.md +524 -0
- package/LICENSE +202 -0
- package/README.md +992 -0
- package/barcode-locate.html +256 -0
- package/barcode-locate.js +156 -0
- package/checkerboard-calibrate.html +167 -0
- package/checkerboard-calibrate.js +282 -0
- package/examples/label-crop-with-line-finder.json +239 -0
- package/golden-compare.html +713 -0
- package/golden-compare.js +1126 -0
- package/icons/checkerboard-calibrate.svg +8 -0
- package/icons/golden-compare.svg +6 -0
- package/label-crop.html +1178 -0
- package/label-crop.js +250 -0
- package/lib/align.js +867 -0
- package/lib/checkerboard.js +331 -0
- package/lib/compare.js +1338 -0
- package/lib/components.js +84 -0
- package/lib/dilate.js +65 -0
- package/lib/inspector.js +188 -0
- package/lib/inspectorCore.js +128 -0
- package/lib/inspectorWorker.js +40 -0
- package/lib/integral.js +76 -0
- package/lib/labelCrop.js +1461 -0
- package/lib/lineFinder.js +765 -0
- package/lib/localAlign.js +360 -0
- package/lib/locate.js +302 -0
- package/lib/nativeSeed.js +292 -0
- package/lib/parallel.js +428 -0
- package/lib/pool.js +250 -0
- package/lib/poolWorker.js +324 -0
- package/lib/scaleFile.js +83 -0
- package/lib/shared.js +87 -0
- package/lib/threshold.js +231 -0
- package/lib/transformFile.js +169 -0
- package/lib/warp.js +216 -0
- package/line-finder.html +1361 -0
- package/line-finder.js +280 -0
- package/package.json +73 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
<script type="text/html" data-template-name="golden-compare">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
4
|
+
<input type="text" id="node-input-name" placeholder="Name" />
|
|
5
|
+
</div>
|
|
6
|
+
<div class="form-row">
|
|
7
|
+
<label for="node-input-goldenPath"
|
|
8
|
+
><i class="fa fa-image"></i> Golden image path</label
|
|
9
|
+
>
|
|
10
|
+
<input
|
|
11
|
+
type="text"
|
|
12
|
+
id="node-input-goldenPath"
|
|
13
|
+
placeholder="/data/golden/reference.png (container path)"
|
|
14
|
+
/>
|
|
15
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
16
|
+
>or send <code>msg.golden</code> (path/Buffer) per message</span
|
|
17
|
+
>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="form-row">
|
|
20
|
+
<label for="node-input-workingSize"
|
|
21
|
+
><i class="fa fa-arrows-alt"></i> Working size</label
|
|
22
|
+
>
|
|
23
|
+
<input
|
|
24
|
+
type="number"
|
|
25
|
+
id="node-input-workingSize"
|
|
26
|
+
min="64"
|
|
27
|
+
max="4096"
|
|
28
|
+
style="width:90px;"
|
|
29
|
+
/>
|
|
30
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
31
|
+
>long edge, px - compare resolution, not the source resolution</span
|
|
32
|
+
>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="form-row">
|
|
35
|
+
<label for="node-input-threshold"
|
|
36
|
+
><i class="fa fa-adjust"></i> Threshold</label
|
|
37
|
+
>
|
|
38
|
+
<input
|
|
39
|
+
type="number"
|
|
40
|
+
id="node-input-threshold"
|
|
41
|
+
min="0"
|
|
42
|
+
max="255"
|
|
43
|
+
style="width:80px;"
|
|
44
|
+
/>
|
|
45
|
+
<label for="node-input-alignSearch" style="margin-left:24px;"
|
|
46
|
+
>Align search</label
|
|
47
|
+
>
|
|
48
|
+
<input
|
|
49
|
+
type="number"
|
|
50
|
+
id="node-input-alignSearch"
|
|
51
|
+
min="0"
|
|
52
|
+
max="200"
|
|
53
|
+
style="width:70px;"
|
|
54
|
+
/>
|
|
55
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
56
|
+
>px, working resolution - extra translation slack</span
|
|
57
|
+
>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="form-row">
|
|
60
|
+
<label for="node-input-thresholdMode"
|
|
61
|
+
><i class="fa fa-sliders"></i> Ink level</label
|
|
62
|
+
>
|
|
63
|
+
<select id="node-input-thresholdMode" style="width:150px;">
|
|
64
|
+
<option value="fixed">Fixed level</option>
|
|
65
|
+
<option value="otsu">Otsu (per image)</option>
|
|
66
|
+
<option value="sauvola">Sauvola (per pixel)</option>
|
|
67
|
+
</select>
|
|
68
|
+
<label for="node-input-sauvolaRadius" style="margin-left:24px;"
|
|
69
|
+
>Sauvola radius</label
|
|
70
|
+
>
|
|
71
|
+
<input
|
|
72
|
+
type="number"
|
|
73
|
+
id="node-input-sauvolaRadius"
|
|
74
|
+
min="2"
|
|
75
|
+
max="200"
|
|
76
|
+
style="width:70px;"
|
|
77
|
+
/>
|
|
78
|
+
<input
|
|
79
|
+
type="number"
|
|
80
|
+
id="node-input-sauvolaK"
|
|
81
|
+
min="0.01"
|
|
82
|
+
max="1"
|
|
83
|
+
step="0.05"
|
|
84
|
+
style="width:70px; margin-left:6px;"
|
|
85
|
+
/>
|
|
86
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
87
|
+
>otsu/sauvola re-derive the level per image, so exposure drift stops
|
|
88
|
+
mattering; radius/k apply to sauvola only</span
|
|
89
|
+
>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="form-row">
|
|
92
|
+
<label for="node-input-inkMargin"
|
|
93
|
+
><i class="fa fa-adjust"></i> Ink margin</label
|
|
94
|
+
>
|
|
95
|
+
<input
|
|
96
|
+
type="number"
|
|
97
|
+
id="node-input-inkMargin"
|
|
98
|
+
min="0"
|
|
99
|
+
max="128"
|
|
100
|
+
style="width:70px;"
|
|
101
|
+
/>
|
|
102
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
103
|
+
>grey levels: pixels that cleared the ink level by less than this are ink
|
|
104
|
+
for alignment but can't raise a defect - stops a screened tint that prints
|
|
105
|
+
heavier than the artwork reporting itself as a blemish. 0 disables</span
|
|
106
|
+
>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="form-row">
|
|
109
|
+
<label for="node-input-transformFilePath"
|
|
110
|
+
><i class="fa fa-crosshairs"></i> Trained transform</label
|
|
111
|
+
>
|
|
112
|
+
<input
|
|
113
|
+
type="text"
|
|
114
|
+
id="node-input-transformFilePath"
|
|
115
|
+
placeholder="/data/golden/transform.json"
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="form-row">
|
|
119
|
+
<label for="node-input-trainTransform" style="width:auto;"
|
|
120
|
+
><input
|
|
121
|
+
type="checkbox"
|
|
122
|
+
id="node-input-trainTransform"
|
|
123
|
+
style="width:auto; vertical-align:top;"
|
|
124
|
+
/>
|
|
125
|
+
Train the transform from the next frame</label
|
|
126
|
+
>
|
|
127
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
128
|
+
>measures magnification and press stretch once and writes them to the file
|
|
129
|
+
above; later frames reuse them and solve only position and angle. Send
|
|
130
|
+
<code>msg.golden</code> with <code>msg.payload</code> to train from any
|
|
131
|
+
two images. Untick when trained, or drive it per message with
|
|
132
|
+
<code>msg.trainTransform</code></span
|
|
133
|
+
>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="form-row">
|
|
136
|
+
<label for="node-input-mismatchScore"
|
|
137
|
+
><i class="fa fa-question-circle"></i> Mismatch score</label
|
|
138
|
+
>
|
|
139
|
+
<input
|
|
140
|
+
type="number"
|
|
141
|
+
id="node-input-mismatchScore"
|
|
142
|
+
min="0"
|
|
143
|
+
max="1"
|
|
144
|
+
step="0.01"
|
|
145
|
+
style="width:70px;"
|
|
146
|
+
/>
|
|
147
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
148
|
+
>warn "this looks like a different label" when the alignment residual
|
|
149
|
+
reaches this <em>and</em> both blemish checks are saturated. Comparing one
|
|
150
|
+
product's artwork against another's photo reports a thousand regions and a
|
|
151
|
+
6% defect ratio, which reads as a catastrophic print rather than the wrong
|
|
152
|
+
golden. 0 disables</span
|
|
153
|
+
>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="form-row">
|
|
156
|
+
<label for="node-input-workers"
|
|
157
|
+
><i class="fa fa-tasks"></i> Worker threads</label
|
|
158
|
+
>
|
|
159
|
+
<input
|
|
160
|
+
type="number"
|
|
161
|
+
id="node-input-workers"
|
|
162
|
+
min="0"
|
|
163
|
+
max="64"
|
|
164
|
+
style="width:70px;"
|
|
165
|
+
/>
|
|
166
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
167
|
+
>the per-pixel stages (warp, tile refinement, threshold, diff) are split
|
|
168
|
+
across a persistent pool. 0 picks one per core up to 16; 1 keeps everything
|
|
169
|
+
on the main thread. Results are identical either way - only the time
|
|
170
|
+
changes. Both summed-area tables run here too, and the alignment polish
|
|
171
|
+
keeps improving to about twelve workers on a 16-core host</span
|
|
172
|
+
>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="form-row">
|
|
175
|
+
<label for="node-input-nativeFastAlign" style="width:auto;"
|
|
176
|
+
><input
|
|
177
|
+
type="checkbox"
|
|
178
|
+
id="node-input-nativeFastAlign"
|
|
179
|
+
style="width:auto; vertical-align:top;"
|
|
180
|
+
/>
|
|
181
|
+
Experimental OpenCV fast alignment</label
|
|
182
|
+
>
|
|
183
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
184
|
+
>lets OpenCV solve and warp the full affine alignment, bypassing the JS
|
|
185
|
+
tables, sweeps, polish, and global warp. Much faster, but deliberately
|
|
186
|
+
allowed to change the transform and inspection result. Implausible
|
|
187
|
+
angle/scale or a poor score automatically falls back to JS</span
|
|
188
|
+
>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="form-row">
|
|
191
|
+
<label for="node-input-localAlign" style="width:auto;"
|
|
192
|
+
><input
|
|
193
|
+
type="checkbox"
|
|
194
|
+
id="node-input-localAlign"
|
|
195
|
+
style="width:auto; vertical-align:top;"
|
|
196
|
+
/>
|
|
197
|
+
Refine alignment per tile</label
|
|
198
|
+
>
|
|
199
|
+
<label for="node-input-localAlignTile" style="margin-left:24px; width:auto;"
|
|
200
|
+
>tile</label
|
|
201
|
+
>
|
|
202
|
+
<input
|
|
203
|
+
type="number"
|
|
204
|
+
id="node-input-localAlignTile"
|
|
205
|
+
min="16"
|
|
206
|
+
max="512"
|
|
207
|
+
style="width:70px; margin-left:6px;"
|
|
208
|
+
/>
|
|
209
|
+
<label for="node-input-localAlignMax" style="margin-left:12px; width:auto;"
|
|
210
|
+
>max px</label
|
|
211
|
+
>
|
|
212
|
+
<input
|
|
213
|
+
type="number"
|
|
214
|
+
id="node-input-localAlignMax"
|
|
215
|
+
min="1"
|
|
216
|
+
max="16"
|
|
217
|
+
style="width:60px; margin-left:6px;"
|
|
218
|
+
/>
|
|
219
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
220
|
+
>a label on a formed tray is not flat, so parts of it sit a few pixels off
|
|
221
|
+
however good the global fit is. Each tile takes up its own offset, capped
|
|
222
|
+
at "max px" so a tile can never slide far enough to hide a fault</span
|
|
223
|
+
>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="form-row">
|
|
226
|
+
<label for="node-input-alignCandidates"
|
|
227
|
+
><i class="fa fa-code-fork"></i> Align candidates</label
|
|
228
|
+
>
|
|
229
|
+
<input
|
|
230
|
+
type="number"
|
|
231
|
+
id="node-input-alignCandidates"
|
|
232
|
+
min="1"
|
|
233
|
+
max="16"
|
|
234
|
+
style="width:70px;"
|
|
235
|
+
/>
|
|
236
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
237
|
+
>how many scale hypotheses survive the coarse search to be judged on real
|
|
238
|
+
pixel disagreement. 1 is the old greedy behaviour, where one bad coarse
|
|
239
|
+
guess misaligns the whole frame and cannot be recovered</span
|
|
240
|
+
>
|
|
241
|
+
</div>
|
|
242
|
+
<div class="form-row">
|
|
243
|
+
<label for="node-input-scaleSearchMin"
|
|
244
|
+
><i class="fa fa-search-plus"></i> Scale search</label
|
|
245
|
+
>
|
|
246
|
+
<input
|
|
247
|
+
type="number"
|
|
248
|
+
id="node-input-scaleSearchMin"
|
|
249
|
+
min="0.1"
|
|
250
|
+
max="10"
|
|
251
|
+
step="0.05"
|
|
252
|
+
style="width:70px;"
|
|
253
|
+
/>
|
|
254
|
+
<input
|
|
255
|
+
type="number"
|
|
256
|
+
id="node-input-scaleSearchMax"
|
|
257
|
+
min="0.1"
|
|
258
|
+
max="10"
|
|
259
|
+
step="0.05"
|
|
260
|
+
style="width:70px; margin-left:6px;"
|
|
261
|
+
/>
|
|
262
|
+
<label for="node-input-scaleSearchSteps" style="margin-left:24px;"
|
|
263
|
+
>Steps</label
|
|
264
|
+
>
|
|
265
|
+
<input
|
|
266
|
+
type="number"
|
|
267
|
+
id="node-input-scaleSearchSteps"
|
|
268
|
+
min="1"
|
|
269
|
+
max="61"
|
|
270
|
+
style="width:70px;"
|
|
271
|
+
/>
|
|
272
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
273
|
+
>min/max magnification of golden within the frame; set both to 1 to pin
|
|
274
|
+
the scale</span
|
|
275
|
+
>
|
|
276
|
+
</div>
|
|
277
|
+
<div class="form-row">
|
|
278
|
+
<label for="node-input-maxAspect"
|
|
279
|
+
><i class="fa fa-arrows-v"></i> Stretch search</label
|
|
280
|
+
>
|
|
281
|
+
<input
|
|
282
|
+
type="number"
|
|
283
|
+
id="node-input-maxAspect"
|
|
284
|
+
min="0"
|
|
285
|
+
max="0.5"
|
|
286
|
+
step="0.01"
|
|
287
|
+
style="width:70px;"
|
|
288
|
+
/>
|
|
289
|
+
<label for="node-input-aspectSteps" style="margin-left:24px;">Steps</label>
|
|
290
|
+
<input
|
|
291
|
+
type="number"
|
|
292
|
+
id="node-input-aspectSteps"
|
|
293
|
+
min="1"
|
|
294
|
+
max="21"
|
|
295
|
+
style="width:70px;"
|
|
296
|
+
/>
|
|
297
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
298
|
+
>how far the print may be stretched along one axis vs the artwork (0.06 =
|
|
299
|
+
6%); 0 assumes the two axes share a scale</span
|
|
300
|
+
>
|
|
301
|
+
</div>
|
|
302
|
+
<div class="form-row">
|
|
303
|
+
<label for="node-input-maxAngleDeg"
|
|
304
|
+
><i class="fa fa-rotate-right"></i> Angle search</label
|
|
305
|
+
>
|
|
306
|
+
<input
|
|
307
|
+
type="number"
|
|
308
|
+
id="node-input-maxAngleDeg"
|
|
309
|
+
min="0"
|
|
310
|
+
max="30"
|
|
311
|
+
step="0.5"
|
|
312
|
+
style="width:70px;"
|
|
313
|
+
/>
|
|
314
|
+
<label for="node-input-angleSteps" style="margin-left:24px;">Steps</label>
|
|
315
|
+
<input
|
|
316
|
+
type="number"
|
|
317
|
+
id="node-input-angleSteps"
|
|
318
|
+
min="1"
|
|
319
|
+
max="21"
|
|
320
|
+
style="width:70px;"
|
|
321
|
+
/>
|
|
322
|
+
<label for="node-input-positionToleranceAngleDeg" style="margin-left:24px;"
|
|
323
|
+
>Angle tolerance</label
|
|
324
|
+
>
|
|
325
|
+
<input
|
|
326
|
+
type="number"
|
|
327
|
+
id="node-input-positionToleranceAngleDeg"
|
|
328
|
+
min="0"
|
|
329
|
+
max="30"
|
|
330
|
+
step="0.1"
|
|
331
|
+
style="width:70px;"
|
|
332
|
+
/>
|
|
333
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
334
|
+
>degrees searched either way, and how far off square still passes</span
|
|
335
|
+
>
|
|
336
|
+
</div>
|
|
337
|
+
<div class="form-row">
|
|
338
|
+
<label><i class="fa fa-tint"></i> Blemish tolerance (px)</label>
|
|
339
|
+
<label for="node-input-printTolerance" style="width:auto; margin-right:4px;"
|
|
340
|
+
>Print</label
|
|
341
|
+
>
|
|
342
|
+
<input
|
|
343
|
+
type="number"
|
|
344
|
+
id="node-input-printTolerance"
|
|
345
|
+
min="0"
|
|
346
|
+
max="50"
|
|
347
|
+
style="width:60px;"
|
|
348
|
+
/>
|
|
349
|
+
<label
|
|
350
|
+
for="node-input-backgroundTolerance"
|
|
351
|
+
style="width:auto; margin-left:16px; margin-right:4px;"
|
|
352
|
+
>Background</label
|
|
353
|
+
>
|
|
354
|
+
<input
|
|
355
|
+
type="number"
|
|
356
|
+
id="node-input-backgroundTolerance"
|
|
357
|
+
min="0"
|
|
358
|
+
max="50"
|
|
359
|
+
style="width:60px;"
|
|
360
|
+
/>
|
|
361
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
362
|
+
>independent checks: missing ink (print) vs. unwanted ink
|
|
363
|
+
(background)</span
|
|
364
|
+
>
|
|
365
|
+
</div>
|
|
366
|
+
<div class="form-row">
|
|
367
|
+
<label for="node-input-scaleFilePath"
|
|
368
|
+
><i class="fa fa-crosshairs"></i> Scale file path</label
|
|
369
|
+
>
|
|
370
|
+
<input
|
|
371
|
+
type="text"
|
|
372
|
+
id="node-input-scaleFilePath"
|
|
373
|
+
placeholder="/data/golden/calibration.json (optional)"
|
|
374
|
+
/>
|
|
375
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
376
|
+
>from <code>checkerboard-calibrate</code> - enables mm position tolerances
|
|
377
|
+
below</span
|
|
378
|
+
>
|
|
379
|
+
</div>
|
|
380
|
+
<div class="form-row">
|
|
381
|
+
<label><i class="fa fa-arrows"></i> Position tolerance</label>
|
|
382
|
+
<label
|
|
383
|
+
for="node-input-positionToleranceXMm"
|
|
384
|
+
style="width:auto; margin-right:4px;"
|
|
385
|
+
>X [mm]</label
|
|
386
|
+
>
|
|
387
|
+
<input
|
|
388
|
+
type="number"
|
|
389
|
+
id="node-input-positionToleranceXMm"
|
|
390
|
+
min="0"
|
|
391
|
+
step="0.1"
|
|
392
|
+
style="width:60px;"
|
|
393
|
+
/>
|
|
394
|
+
<label
|
|
395
|
+
for="node-input-positionToleranceYMm"
|
|
396
|
+
style="width:auto; margin-left:16px; margin-right:4px;"
|
|
397
|
+
>Y [mm]</label
|
|
398
|
+
>
|
|
399
|
+
<input
|
|
400
|
+
type="number"
|
|
401
|
+
id="node-input-positionToleranceYMm"
|
|
402
|
+
min="0"
|
|
403
|
+
step="0.1"
|
|
404
|
+
style="width:60px;"
|
|
405
|
+
/>
|
|
406
|
+
</div>
|
|
407
|
+
<div class="form-row">
|
|
408
|
+
<label> </label>
|
|
409
|
+
<label
|
|
410
|
+
for="node-input-positionToleranceXPx"
|
|
411
|
+
style="width:auto; margin-right:4px;"
|
|
412
|
+
>X [px]</label
|
|
413
|
+
>
|
|
414
|
+
<input
|
|
415
|
+
type="number"
|
|
416
|
+
id="node-input-positionToleranceXPx"
|
|
417
|
+
min="0"
|
|
418
|
+
style="width:60px;"
|
|
419
|
+
/>
|
|
420
|
+
<label
|
|
421
|
+
for="node-input-positionToleranceYPx"
|
|
422
|
+
style="width:auto; margin-left:16px; margin-right:4px;"
|
|
423
|
+
>Y [px]</label
|
|
424
|
+
>
|
|
425
|
+
<input
|
|
426
|
+
type="number"
|
|
427
|
+
id="node-input-positionToleranceYPx"
|
|
428
|
+
min="0"
|
|
429
|
+
style="width:60px;"
|
|
430
|
+
/>
|
|
431
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
432
|
+
>px fallback, used when no scale file is configured/found</span
|
|
433
|
+
>
|
|
434
|
+
</div>
|
|
435
|
+
<div class="form-row">
|
|
436
|
+
<label for="node-input-blockSize"
|
|
437
|
+
><i class="fa fa-th"></i> Heat map block</label
|
|
438
|
+
>
|
|
439
|
+
<input
|
|
440
|
+
type="number"
|
|
441
|
+
id="node-input-blockSize"
|
|
442
|
+
min="4"
|
|
443
|
+
max="256"
|
|
444
|
+
style="width:70px;"
|
|
445
|
+
/>
|
|
446
|
+
<label for="node-input-blockThreshold" style="margin-left:24px;"
|
|
447
|
+
>Block threshold</label
|
|
448
|
+
>
|
|
449
|
+
<input
|
|
450
|
+
type="number"
|
|
451
|
+
id="node-input-blockThreshold"
|
|
452
|
+
min="0"
|
|
453
|
+
max="1"
|
|
454
|
+
step="0.01"
|
|
455
|
+
style="width:70px;"
|
|
456
|
+
/>
|
|
457
|
+
</div>
|
|
458
|
+
<div class="form-row">
|
|
459
|
+
<label for="node-input-failThreshold"
|
|
460
|
+
><i class="fa fa-exclamation-triangle"></i> Fail threshold</label
|
|
461
|
+
>
|
|
462
|
+
<input
|
|
463
|
+
type="number"
|
|
464
|
+
id="node-input-failThreshold"
|
|
465
|
+
min="0"
|
|
466
|
+
max="1"
|
|
467
|
+
step="0.01"
|
|
468
|
+
style="width:70px;"
|
|
469
|
+
/>
|
|
470
|
+
<label for="node-input-failRatio" style="margin-left:24px;"
|
|
471
|
+
>Fail ratio</label
|
|
472
|
+
>
|
|
473
|
+
<input
|
|
474
|
+
type="number"
|
|
475
|
+
id="node-input-failRatio"
|
|
476
|
+
min="0"
|
|
477
|
+
max="1"
|
|
478
|
+
step="0.001"
|
|
479
|
+
style="width:80px;"
|
|
480
|
+
/>
|
|
481
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
482
|
+
>applies to both print and background checks</span
|
|
483
|
+
>
|
|
484
|
+
</div>
|
|
485
|
+
<div class="form-row">
|
|
486
|
+
<label for="node-input-outputPrintHeatmap"
|
|
487
|
+
><i class="fa fa-picture-o"></i> Output heat maps</label
|
|
488
|
+
>
|
|
489
|
+
<input
|
|
490
|
+
type="checkbox"
|
|
491
|
+
id="node-input-outputPrintHeatmap"
|
|
492
|
+
style="display:inline-block; width:auto; vertical-align:middle;"
|
|
493
|
+
/>
|
|
494
|
+
<span style="margin-left:4px;">print</span>
|
|
495
|
+
<input
|
|
496
|
+
type="checkbox"
|
|
497
|
+
id="node-input-outputBackgroundHeatmap"
|
|
498
|
+
style="display:inline-block; width:auto; vertical-align:middle; margin-left:16px;"
|
|
499
|
+
/>
|
|
500
|
+
<span style="margin-left:4px;">background</span>
|
|
501
|
+
</div>
|
|
502
|
+
<div class="form-row">
|
|
503
|
+
<label for="node-input-debugStages"
|
|
504
|
+
><i class="fa fa-eye"></i> Output pipeline stages</label
|
|
505
|
+
>
|
|
506
|
+
<input
|
|
507
|
+
type="checkbox"
|
|
508
|
+
id="node-input-debugStages"
|
|
509
|
+
style="display:inline-block; width:auto; vertical-align:middle;"
|
|
510
|
+
/>
|
|
511
|
+
<span class="node-input-hint" style="margin-left:10px; color:#999;"
|
|
512
|
+
>adds <code>msg.stages</code> for debugging a bad match - leave off in
|
|
513
|
+
production</span
|
|
514
|
+
>
|
|
515
|
+
</div>
|
|
516
|
+
</script>
|
|
517
|
+
|
|
518
|
+
<script type="text/html" data-help-name="golden-compare">
|
|
519
|
+
<p>
|
|
520
|
+
Golden-template AOI: a
|
|
521
|
+
<b>position check</b> (deviation of a matched crop window from the center of
|
|
522
|
+
the available margin, vs. a tolerance band - a real position defect fails on
|
|
523
|
+
its own, it isn't silently corrected away) plus two independent
|
|
524
|
+
<b>blemish checks</b> - print (golden has ink the target is missing) and
|
|
525
|
+
background (target has ink the golden never has) - each with its own
|
|
526
|
+
tolerance. Built for speed - the golden reference is decoded and
|
|
527
|
+
preprocessed once and cached, not redone per frame; decode/resize uses
|
|
528
|
+
<code>sharp</code> (native).
|
|
529
|
+
</p>
|
|
530
|
+
<p>
|
|
531
|
+
The target is decoded preserving its own aspect ratio (never stretched to
|
|
532
|
+
fit), then a sliding-window search finds the golden-sized window within it
|
|
533
|
+
that best matches - a raw camera capture with extra background/margin
|
|
534
|
+
doesn't need a manual crop step.
|
|
535
|
+
<b>Only translation is corrected this way</b> (bounded by the target's
|
|
536
|
+
actual size plus <i>Align search</i> px of extra slack) - rotation and scale
|
|
537
|
+
drift are not. This assumes a mechanically- fixtured camera/part. The found
|
|
538
|
+
window position is also what the position check measures.
|
|
539
|
+
</p>
|
|
540
|
+
|
|
541
|
+
<h3>Input</h3>
|
|
542
|
+
<p><code>msg.payload</code> can be:</p>
|
|
543
|
+
<ul>
|
|
544
|
+
<li>a <b>Buffer</b> / Uint8Array with the image bytes</li>
|
|
545
|
+
<li>a <b>string path</b> to an image file on the container's filesystem</li>
|
|
546
|
+
<li>
|
|
547
|
+
an object with <code>data</code> / <code>buffer</code> or
|
|
548
|
+
<code>path</code>
|
|
549
|
+
</li>
|
|
550
|
+
</ul>
|
|
551
|
+
<p>
|
|
552
|
+
Optional per-message overrides: <code>msg.golden</code> (path or Buffer -
|
|
553
|
+
swaps and re-caches the golden reference if it differs from what's cached),
|
|
554
|
+
<code>msg.threshold</code>, <code>msg.printTolerance</code>,
|
|
555
|
+
<code>msg.backgroundTolerance</code>, <code>msg.alignSearch</code>,
|
|
556
|
+
<code>msg.thresholdMode</code>, <code>msg.sauvolaRadius</code>,
|
|
557
|
+
<code>msg.sauvolaK</code>, <code>msg.inkMargin</code>,
|
|
558
|
+
<code>msg.trainTransform</code>, <code>msg.nativeAlignSeed</code>,
|
|
559
|
+
<code>msg.nativeFastAlign</code>, <code>msg.workers</code>,
|
|
560
|
+
<code>msg.localAlign</code>, <code>msg.localAlignTile</code>,
|
|
561
|
+
<code>msg.localAlignMax</code>, <code>msg.scaleSearchMin</code>,
|
|
562
|
+
<code>msg.scaleSearchMax</code>, <code>msg.scaleSearchSteps</code>,
|
|
563
|
+
<code>msg.maxAspect</code>, <code>msg.aspectSteps</code>,
|
|
564
|
+
<code>msg.maxAngleDeg</code>, <code>msg.angleSteps</code>,
|
|
565
|
+
<code>msg.alignCandidates</code>, <code>msg.mismatchScore</code>,
|
|
566
|
+
<code>msg.positionToleranceAngleDeg</code>,
|
|
567
|
+
<code>msg.positionToleranceXMm</code
|
|
568
|
+
>/<code>YMm</code>/<code>XPx</code>/<code>YPx</code>,
|
|
569
|
+
<code>msg.blockSize</code>, <code>msg.blockThreshold</code>,
|
|
570
|
+
<code>msg.failThreshold</code>, <code>msg.failRatio</code>,
|
|
571
|
+
<code>msg.outputPrintHeatmap</code>,
|
|
572
|
+
<code>msg.outputBackgroundHeatmap</code>, <code>msg.debugStages</code>.
|
|
573
|
+
</p>
|
|
574
|
+
|
|
575
|
+
<h3>Output</h3>
|
|
576
|
+
<ul>
|
|
577
|
+
<li>
|
|
578
|
+
<code>msg.payload</code> — <code>true</code>/<code>false</code> overall
|
|
579
|
+
pass
|
|
580
|
+
</li>
|
|
581
|
+
<li>
|
|
582
|
+
<code>msg.result</code> —
|
|
583
|
+
<code
|
|
584
|
+
>{ pass, position: { dxPx, dyPx, dxMm, dyMm, angleDeg, anglePass, scale,
|
|
585
|
+
scaleX, scaleY, stretchPercent, pass }, transform: { pinned, native, nativeFallback?, seeded, pinRefused?, scaleX,
|
|
586
|
+
scaleY, scale, stretchPercent, angleDeg, ox, oy, score }, match: {
|
|
587
|
+
score, grade, mismatchSuspected, reason }, thresholds: { golden, target
|
|
588
|
+
}, localAlign: { tiles, localised, meanPx, medianPx, maxPx },
|
|
589
|
+
printBlemish: { pass, defectRatio, regions }, backgroundBlemish: { pass,
|
|
590
|
+
defectRatio, regions } }</code
|
|
591
|
+
>
|
|
592
|
+
(region coordinates in the working-resolution image, same size as the heat
|
|
593
|
+
maps)
|
|
594
|
+
</li>
|
|
595
|
+
<li>
|
|
596
|
+
<code>msg.printHeatmap</code> / <code>msg.backgroundHeatmap</code> — PNG
|
|
597
|
+
<code>Buffer</code> overlays (only if the matching
|
|
598
|
+
<i>Output heat maps</i> checkbox is on)
|
|
599
|
+
</li>
|
|
600
|
+
<li>
|
|
601
|
+
<code>msg.timings</code> —
|
|
602
|
+
<code>{ decodeMs, alignMs, diffMs, heatmapMs, stagesMs, totalMs }</code>
|
|
603
|
+
</li>
|
|
604
|
+
<li>
|
|
605
|
+
<code>msg.stages</code> — only if <i>Output pipeline stages</i> is on: PNG
|
|
606
|
+
<code>Buffer</code>s for each pipeline step (<code>goldenGray</code>,
|
|
607
|
+
<code>goldenFg</code>, <code>goldenFgDilatedBackground</code>,
|
|
608
|
+
<code>targetGray</code>, <code>targetFg</code>,
|
|
609
|
+
<code>targetGrayAligned</code>, <code>targetFgAligned</code>,
|
|
610
|
+
<code>targetFgDilatedPrint</code>, <code>printDefect</code>,
|
|
611
|
+
<code>backgroundDefect</code>)
|
|
612
|
+
</li>
|
|
613
|
+
<li>
|
|
614
|
+
the node logs the same breakdown plus the position/print/background
|
|
615
|
+
verdicts at info level
|
|
616
|
+
</li>
|
|
617
|
+
</ul>
|
|
618
|
+
|
|
619
|
+
<h3>Calibration</h3>
|
|
620
|
+
<p>
|
|
621
|
+
<i>Scale file path</i> points at the JSON baseline written by
|
|
622
|
+
<code>checkerboard-calibrate</code> (<code
|
|
623
|
+
>{ mmPerPixelNative, nativeWidth, nativeHeight, calibratedAt }</code
|
|
624
|
+
>). When set and the file exists, the position check converts the measured
|
|
625
|
+
shift to mm (rescaled from the calibration photo's native resolution to this
|
|
626
|
+
node's <i>Working size</i>) and checks it against the mm tolerances. Without
|
|
627
|
+
it, the px tolerances are used instead - position is still checked, just not
|
|
628
|
+
in physical units.
|
|
629
|
+
</p>
|
|
630
|
+
|
|
631
|
+
<h3>Notes</h3>
|
|
632
|
+
<p>
|
|
633
|
+
<i>Golden image path</i> is a path <b>inside the container</b> - use
|
|
634
|
+
<code>/data</code> for anything that should survive a rebuild. The golden
|
|
635
|
+
reference is only re-decoded when its source (path/Buffer fingerprint),
|
|
636
|
+
<i>Working size</i>/<i>Threshold</i>/<i>Background tolerance</i>, or the
|
|
637
|
+
calibrated scale changes - <i>Print tolerance</i>/<i>Align search</i>/etc.
|
|
638
|
+
are applied fresh per frame and don't need a re-decode.
|
|
639
|
+
</p>
|
|
640
|
+
</script>
|
|
641
|
+
|
|
642
|
+
<script type="text/javascript">
|
|
643
|
+
RED.nodes.registerType("golden-compare", {
|
|
644
|
+
category: "vision",
|
|
645
|
+
color: "#B7950B",
|
|
646
|
+
defaults: {
|
|
647
|
+
// number(true) = blank allowed. Node-RED does not backfill new
|
|
648
|
+
// defaults into node instances that were saved before the property
|
|
649
|
+
// existed, so a strict validator marks every such node "invalid
|
|
650
|
+
// properties" the moment a setting is added. The runtime clamps a
|
|
651
|
+
// missing or blank value to the same default anyway, so accepting
|
|
652
|
+
// blank here is both safe and what the user means by it.
|
|
653
|
+
name: { value: "" },
|
|
654
|
+
goldenPath: { value: "" },
|
|
655
|
+
workingSize: { value: 1024, validate: RED.validators.number(true) },
|
|
656
|
+
threshold: { value: 128, validate: RED.validators.number(true) },
|
|
657
|
+
thresholdMode: { value: "otsu" },
|
|
658
|
+
sauvolaRadius: { value: 24, validate: RED.validators.number(true) },
|
|
659
|
+
sauvolaK: { value: 0.2, validate: RED.validators.number(true) },
|
|
660
|
+
inkMargin: { value: 8, validate: RED.validators.number(true) },
|
|
661
|
+
alignCandidates: { value: 5, validate: RED.validators.number(true) },
|
|
662
|
+
workers: { value: 0, validate: RED.validators.number(true) },
|
|
663
|
+
nativeFastAlign: { value: false },
|
|
664
|
+
mismatchScore: { value: 0.15, validate: RED.validators.number(true) },
|
|
665
|
+
localAlign: { value: true },
|
|
666
|
+
localAlignTile: { value: 96, validate: RED.validators.number(true) },
|
|
667
|
+
localAlignMax: { value: 3, validate: RED.validators.number(true) },
|
|
668
|
+
transformFilePath: { value: "" },
|
|
669
|
+
trainTransform: { value: false },
|
|
670
|
+
scaleSearchMin: { value: 0.6, validate: RED.validators.number(true) },
|
|
671
|
+
scaleSearchMax: { value: 2.5, validate: RED.validators.number(true) },
|
|
672
|
+
scaleSearchSteps: { value: 19, validate: RED.validators.number(true) },
|
|
673
|
+
maxAspect: { value: 0.06, validate: RED.validators.number(true) },
|
|
674
|
+
aspectSteps: { value: 7, validate: RED.validators.number(true) },
|
|
675
|
+
maxAngleDeg: { value: 2, validate: RED.validators.number(true) },
|
|
676
|
+
angleSteps: { value: 5, validate: RED.validators.number(true) },
|
|
677
|
+
positionToleranceAngleDeg: {
|
|
678
|
+
value: 1,
|
|
679
|
+
validate: RED.validators.number(true),
|
|
680
|
+
},
|
|
681
|
+
printTolerance: { value: 2, validate: RED.validators.number(true) },
|
|
682
|
+
backgroundTolerance: { value: 1, validate: RED.validators.number(true) },
|
|
683
|
+
alignSearch: { value: 16, validate: RED.validators.number(true) },
|
|
684
|
+
scaleFilePath: { value: "" },
|
|
685
|
+
positionToleranceXMm: { value: 2, validate: RED.validators.number(true) },
|
|
686
|
+
positionToleranceYMm: { value: 2, validate: RED.validators.number(true) },
|
|
687
|
+
positionToleranceXPx: {
|
|
688
|
+
value: 16,
|
|
689
|
+
validate: RED.validators.number(true),
|
|
690
|
+
},
|
|
691
|
+
positionToleranceYPx: {
|
|
692
|
+
value: 16,
|
|
693
|
+
validate: RED.validators.number(true),
|
|
694
|
+
},
|
|
695
|
+
blockSize: { value: 16, validate: RED.validators.number(true) },
|
|
696
|
+
blockThreshold: { value: 0.15, validate: RED.validators.number(true) },
|
|
697
|
+
failThreshold: { value: 0.3, validate: RED.validators.number(true) },
|
|
698
|
+
failRatio: { value: 0.002, validate: RED.validators.number(true) },
|
|
699
|
+
outputPrintHeatmap: { value: true },
|
|
700
|
+
outputBackgroundHeatmap: { value: true },
|
|
701
|
+
debugStages: { value: false },
|
|
702
|
+
},
|
|
703
|
+
inputs: 1,
|
|
704
|
+
outputs: 1,
|
|
705
|
+
icon: "golden-compare.svg",
|
|
706
|
+
label: function () {
|
|
707
|
+
return this.name || "golden compare";
|
|
708
|
+
},
|
|
709
|
+
paletteLabel: function () {
|
|
710
|
+
return "golden compare";
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
</script>
|