jscanify 1.2.0 → 1.3.1
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/README.md +7 -4
- package/docs/images/test/test10.jpg +0 -0
- package/docs/images/test/test3.jpg +0 -0
- package/docs/images/test/test4.jpg +0 -0
- package/docs/images/test/test5.jpg +0 -0
- package/docs/images/test/test6.jpg +0 -0
- package/docs/images/test/test7.jpg +0 -0
- package/docs/images/test/test8.jpg +0 -0
- package/docs/images/test/test9.jpg +0 -0
- package/docs/index.css +141 -141
- package/docs/index.html +123 -124
- package/docs/script.js +41 -41
- package/docs/tester.html +148 -0
- package/package.json +32 -32
- package/src/jscanify-node.js +6 -5
- package/src/jscanify.js +255 -254
- package/src/opencv.js +47 -47
- package/test/tests.js +55 -38
package/src/jscanify.js
CHANGED
|
@@ -1,254 +1,255 @@
|
|
|
1
|
-
/*! jscanify v1.
|
|
2
|
-
|
|
3
|
-
(function (global, factory) {
|
|
4
|
-
typeof exports === "object" && typeof module !== "undefined"
|
|
5
|
-
? (module.exports = factory())
|
|
6
|
-
: typeof define === "function" && define.amd
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
})(this, function () {
|
|
10
|
-
"use strict";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Calculates distance between two points. Each point must have `x` and `y` property
|
|
14
|
-
* @param {*} p1 point 1
|
|
15
|
-
* @param {*} p2 point 2
|
|
16
|
-
* @returns distance between two points
|
|
17
|
-
*/
|
|
18
|
-
function distance(p1, p2) {
|
|
19
|
-
return Math.
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
class jscanify {
|
|
23
|
-
constructor() {}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Finds the contour of the paper within the image
|
|
27
|
-
* @param {*} img image to process (cv.Mat)
|
|
28
|
-
* @returns the biggest contour inside the image
|
|
29
|
-
*/
|
|
30
|
-
findPaperContour(img) {
|
|
31
|
-
const imgGray = new cv.Mat();
|
|
32
|
-
cv.
|
|
33
|
-
|
|
34
|
-
const imgBlur = new cv.Mat();
|
|
35
|
-
cv.GaussianBlur(
|
|
36
|
-
imgGray,
|
|
37
|
-
imgBlur,
|
|
38
|
-
new cv.Size(
|
|
39
|
-
0,
|
|
40
|
-
0,
|
|
41
|
-
cv.BORDER_DEFAULT
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const imgThresh = new cv.Mat();
|
|
45
|
-
cv.threshold(
|
|
46
|
-
imgBlur,
|
|
47
|
-
imgThresh,
|
|
48
|
-
0,
|
|
49
|
-
255,
|
|
50
|
-
cv.
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
let contours = new cv.MatVector();
|
|
54
|
-
let hierarchy = new cv.Mat();
|
|
55
|
-
|
|
56
|
-
cv.findContours(
|
|
57
|
-
imgThresh,
|
|
58
|
-
contours,
|
|
59
|
-
hierarchy,
|
|
60
|
-
cv.RETR_CCOMP,
|
|
61
|
-
cv.CHAIN_APPROX_SIMPLE
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
*
|
|
86
|
-
* @param {*}
|
|
87
|
-
* @
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
options
|
|
92
|
-
options.
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
ctx.
|
|
115
|
-
ctx.
|
|
116
|
-
ctx.
|
|
117
|
-
ctx.
|
|
118
|
-
ctx.lineTo(...Object.values(
|
|
119
|
-
ctx.lineTo(...Object.values(
|
|
120
|
-
ctx.lineTo(...Object.values(
|
|
121
|
-
ctx.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
*
|
|
132
|
-
* @param {*}
|
|
133
|
-
* @param {*}
|
|
134
|
-
* @param {*}
|
|
135
|
-
* @param {*} cornerPoints optional custom corner points, in case automatic corner points are incorrect
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
let
|
|
154
|
-
|
|
155
|
-
topLeftCorner.
|
|
156
|
-
|
|
157
|
-
topRightCorner.
|
|
158
|
-
|
|
159
|
-
bottomLeftCorner.
|
|
160
|
-
|
|
161
|
-
bottomRightCorner.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
0,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
0,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
cv.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
cv.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
*
|
|
196
|
-
* @
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
let
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
let
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
let
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
let
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
1
|
+
/*! jscanify v1.3.1 | (c) ColonelParrot and other contributors | MIT License */
|
|
2
|
+
|
|
3
|
+
(function (global, factory) {
|
|
4
|
+
typeof exports === "object" && typeof module !== "undefined"
|
|
5
|
+
? (module.exports = factory())
|
|
6
|
+
: typeof define === "function" && define.amd
|
|
7
|
+
? define(factory)
|
|
8
|
+
: (global.jscanify = factory());
|
|
9
|
+
})(this, function () {
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Calculates distance between two points. Each point must have `x` and `y` property
|
|
14
|
+
* @param {*} p1 point 1
|
|
15
|
+
* @param {*} p2 point 2
|
|
16
|
+
* @returns distance between two points
|
|
17
|
+
*/
|
|
18
|
+
function distance(p1, p2) {
|
|
19
|
+
return Math.hypot(p1.x - p2.x, p1.y - p2.y);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class jscanify {
|
|
23
|
+
constructor() { }
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Finds the contour of the paper within the image
|
|
27
|
+
* @param {*} img image to process (cv.Mat)
|
|
28
|
+
* @returns the biggest contour inside the image
|
|
29
|
+
*/
|
|
30
|
+
findPaperContour(img) {
|
|
31
|
+
const imgGray = new cv.Mat();
|
|
32
|
+
cv.Canny(img, imgGray, 50, 200);
|
|
33
|
+
|
|
34
|
+
const imgBlur = new cv.Mat();
|
|
35
|
+
cv.GaussianBlur(
|
|
36
|
+
imgGray,
|
|
37
|
+
imgBlur,
|
|
38
|
+
new cv.Size(3, 3),
|
|
39
|
+
0,
|
|
40
|
+
0,
|
|
41
|
+
cv.BORDER_DEFAULT
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const imgThresh = new cv.Mat();
|
|
45
|
+
cv.threshold(
|
|
46
|
+
imgBlur,
|
|
47
|
+
imgThresh,
|
|
48
|
+
0,
|
|
49
|
+
255,
|
|
50
|
+
cv.THRESH_OTSU
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
let contours = new cv.MatVector();
|
|
54
|
+
let hierarchy = new cv.Mat();
|
|
55
|
+
|
|
56
|
+
cv.findContours(
|
|
57
|
+
imgThresh,
|
|
58
|
+
contours,
|
|
59
|
+
hierarchy,
|
|
60
|
+
cv.RETR_CCOMP,
|
|
61
|
+
cv.CHAIN_APPROX_SIMPLE
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
let maxArea = 0;
|
|
65
|
+
let maxContourIndex = -1;
|
|
66
|
+
for (let i = 0; i < contours.size(); ++i) {
|
|
67
|
+
let contourArea = cv.contourArea(contours.get(i));
|
|
68
|
+
if (contourArea > maxArea) {
|
|
69
|
+
maxArea = contourArea;
|
|
70
|
+
maxContourIndex = i;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const maxContour = contours.get(maxContourIndex);
|
|
75
|
+
|
|
76
|
+
imgGray.delete();
|
|
77
|
+
imgBlur.delete();
|
|
78
|
+
imgThresh.delete();
|
|
79
|
+
contours.delete();
|
|
80
|
+
hierarchy.delete();
|
|
81
|
+
return maxContour;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Highlights the paper detected inside the image.
|
|
86
|
+
* @param {*} image image to process
|
|
87
|
+
* @param {*} options options for highlighting. Accepts `color` and `thickness` parameter
|
|
88
|
+
* @returns `HTMLCanvasElement` with original image and paper highlighted
|
|
89
|
+
*/
|
|
90
|
+
highlightPaper(image, options) {
|
|
91
|
+
options = options || {};
|
|
92
|
+
options.color = options.color || "orange";
|
|
93
|
+
options.thickness = options.thickness || 10;
|
|
94
|
+
const canvas = document.createElement("canvas");
|
|
95
|
+
const ctx = canvas.getContext("2d");
|
|
96
|
+
const img = cv.imread(image);
|
|
97
|
+
|
|
98
|
+
const maxContour = this.findPaperContour(img);
|
|
99
|
+
cv.imshow(canvas, img);
|
|
100
|
+
if (maxContour) {
|
|
101
|
+
const {
|
|
102
|
+
topLeftCorner,
|
|
103
|
+
topRightCorner,
|
|
104
|
+
bottomLeftCorner,
|
|
105
|
+
bottomRightCorner,
|
|
106
|
+
} = this.getCornerPoints(maxContour, img);
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
topLeftCorner &&
|
|
110
|
+
topRightCorner &&
|
|
111
|
+
bottomLeftCorner &&
|
|
112
|
+
bottomRightCorner
|
|
113
|
+
) {
|
|
114
|
+
ctx.strokeStyle = options.color;
|
|
115
|
+
ctx.lineWidth = options.thickness;
|
|
116
|
+
ctx.beginPath();
|
|
117
|
+
ctx.moveTo(...Object.values(topLeftCorner));
|
|
118
|
+
ctx.lineTo(...Object.values(topRightCorner));
|
|
119
|
+
ctx.lineTo(...Object.values(bottomRightCorner));
|
|
120
|
+
ctx.lineTo(...Object.values(bottomLeftCorner));
|
|
121
|
+
ctx.lineTo(...Object.values(topLeftCorner));
|
|
122
|
+
ctx.stroke();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
img.delete();
|
|
127
|
+
return canvas;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Extracts and undistorts the image detected within the frame.
|
|
132
|
+
* @param {*} image image to process
|
|
133
|
+
* @param {*} resultWidth desired result paper width
|
|
134
|
+
* @param {*} resultHeight desired result paper height
|
|
135
|
+
* @param {*} cornerPoints optional custom corner points, in case automatic corner points are incorrect
|
|
136
|
+
* @returns `HTMLCanvasElement` containing undistorted image
|
|
137
|
+
*/
|
|
138
|
+
extractPaper(image, resultWidth, resultHeight, cornerPoints) {
|
|
139
|
+
const canvas = document.createElement("canvas");
|
|
140
|
+
|
|
141
|
+
const img = cv.imread(image);
|
|
142
|
+
|
|
143
|
+
const maxContour = this.findPaperContour(img);
|
|
144
|
+
|
|
145
|
+
const {
|
|
146
|
+
topLeftCorner,
|
|
147
|
+
topRightCorner,
|
|
148
|
+
bottomLeftCorner,
|
|
149
|
+
bottomRightCorner,
|
|
150
|
+
} = cornerPoints || this.getCornerPoints(maxContour, img);
|
|
151
|
+
let warpedDst = new cv.Mat();
|
|
152
|
+
|
|
153
|
+
let dsize = new cv.Size(resultWidth, resultHeight);
|
|
154
|
+
let srcTri = cv.matFromArray(4, 1, cv.CV_32FC2, [
|
|
155
|
+
topLeftCorner.x,
|
|
156
|
+
topLeftCorner.y,
|
|
157
|
+
topRightCorner.x,
|
|
158
|
+
topRightCorner.y,
|
|
159
|
+
bottomLeftCorner.x,
|
|
160
|
+
bottomLeftCorner.y,
|
|
161
|
+
bottomRightCorner.x,
|
|
162
|
+
bottomRightCorner.y,
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
let dstTri = cv.matFromArray(4, 1, cv.CV_32FC2, [
|
|
166
|
+
0,
|
|
167
|
+
0,
|
|
168
|
+
resultWidth,
|
|
169
|
+
0,
|
|
170
|
+
0,
|
|
171
|
+
resultHeight,
|
|
172
|
+
resultWidth,
|
|
173
|
+
resultHeight,
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
let M = cv.getPerspectiveTransform(srcTri, dstTri);
|
|
177
|
+
cv.warpPerspective(
|
|
178
|
+
img,
|
|
179
|
+
warpedDst,
|
|
180
|
+
M,
|
|
181
|
+
dsize,
|
|
182
|
+
cv.INTER_LINEAR,
|
|
183
|
+
cv.BORDER_CONSTANT,
|
|
184
|
+
new cv.Scalar()
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
cv.imshow(canvas, warpedDst);
|
|
188
|
+
|
|
189
|
+
img.delete()
|
|
190
|
+
warpedDst.delete()
|
|
191
|
+
return canvas;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Calculates the corner points of a contour.
|
|
196
|
+
* @param {*} contour contour from {@link findPaperContour}
|
|
197
|
+
* @returns object with properties `topLeftCorner`, `topRightCorner`, `bottomLeftCorner`, `bottomRightCorner`, each with `x` and `y` property
|
|
198
|
+
*/
|
|
199
|
+
getCornerPoints(contour) {
|
|
200
|
+
let rect = cv.minAreaRect(contour);
|
|
201
|
+
const center = rect.center;
|
|
202
|
+
|
|
203
|
+
let topLeftCorner;
|
|
204
|
+
let topLeftCornerDist = 0;
|
|
205
|
+
|
|
206
|
+
let topRightCorner;
|
|
207
|
+
let topRightCornerDist = 0;
|
|
208
|
+
|
|
209
|
+
let bottomLeftCorner;
|
|
210
|
+
let bottomLeftCornerDist = 0;
|
|
211
|
+
|
|
212
|
+
let bottomRightCorner;
|
|
213
|
+
let bottomRightCornerDist = 0;
|
|
214
|
+
|
|
215
|
+
for (let i = 0; i < contour.data32S.length; i += 2) {
|
|
216
|
+
const point = { x: contour.data32S[i], y: contour.data32S[i + 1] };
|
|
217
|
+
const dist = distance(point, center);
|
|
218
|
+
if (point.x < center.x && point.y < center.y) {
|
|
219
|
+
// top left
|
|
220
|
+
if (dist > topLeftCornerDist) {
|
|
221
|
+
topLeftCorner = point;
|
|
222
|
+
topLeftCornerDist = dist;
|
|
223
|
+
}
|
|
224
|
+
} else if (point.x > center.x && point.y < center.y) {
|
|
225
|
+
// top right
|
|
226
|
+
if (dist > topRightCornerDist) {
|
|
227
|
+
topRightCorner = point;
|
|
228
|
+
topRightCornerDist = dist;
|
|
229
|
+
}
|
|
230
|
+
} else if (point.x < center.x && point.y > center.y) {
|
|
231
|
+
// bottom left
|
|
232
|
+
if (dist > bottomLeftCornerDist) {
|
|
233
|
+
bottomLeftCorner = point;
|
|
234
|
+
bottomLeftCornerDist = dist;
|
|
235
|
+
}
|
|
236
|
+
} else if (point.x > center.x && point.y > center.y) {
|
|
237
|
+
// bottom right
|
|
238
|
+
if (dist > bottomRightCornerDist) {
|
|
239
|
+
bottomRightCorner = point;
|
|
240
|
+
bottomRightCornerDist = dist;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
topLeftCorner,
|
|
247
|
+
topRightCorner,
|
|
248
|
+
bottomLeftCorner,
|
|
249
|
+
bottomRightCorner,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return jscanify;
|
|
255
|
+
});
|