react-native-rectangle-doc-scanner 0.26.0 → 0.28.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/dist/DocScanner.js +41 -62
- package/package.json +1 -1
- package/src/DocScanner.tsx +43 -65
package/dist/DocScanner.js
CHANGED
|
@@ -133,7 +133,8 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
133
133
|
try {
|
|
134
134
|
// Report frame size for coordinate transformation
|
|
135
135
|
updateFrameSize(frame.width, frame.height);
|
|
136
|
-
|
|
136
|
+
// Use higher resolution for better accuracy - 720p instead of 480p
|
|
137
|
+
const ratio = 720 / frame.width;
|
|
137
138
|
const width = Math.floor(frame.width * ratio);
|
|
138
139
|
const height = Math.floor(frame.height * ratio);
|
|
139
140
|
step = 'resize';
|
|
@@ -149,26 +150,36 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
149
150
|
step = 'cvtColor';
|
|
150
151
|
reportStage(step);
|
|
151
152
|
react_native_fast_opencv_1.OpenCV.invoke('cvtColor', mat, mat, react_native_fast_opencv_1.ColorConversionCodes.COLOR_BGR2GRAY);
|
|
152
|
-
|
|
153
|
+
// Apply Gaussian blur to reduce noise
|
|
154
|
+
const gaussianKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 5, 5);
|
|
155
|
+
step = 'GaussianBlur';
|
|
156
|
+
reportStage(step);
|
|
157
|
+
react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
|
|
158
|
+
// Morphological operations to enhance edges
|
|
159
|
+
const morphologyKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 3, 3);
|
|
153
160
|
step = 'getStructuringElement';
|
|
154
161
|
reportStage(step);
|
|
155
162
|
const element = react_native_fast_opencv_1.OpenCV.invoke('getStructuringElement', react_native_fast_opencv_1.MorphShapes.MORPH_RECT, morphologyKernel);
|
|
156
163
|
step = 'morphologyEx';
|
|
157
164
|
reportStage(step);
|
|
158
|
-
react_native_fast_opencv_1.OpenCV.invoke('morphologyEx', mat, mat, react_native_fast_opencv_1.MorphTypes.
|
|
159
|
-
|
|
160
|
-
step = 'GaussianBlur';
|
|
161
|
-
reportStage(step);
|
|
162
|
-
react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
|
|
165
|
+
react_native_fast_opencv_1.OpenCV.invoke('morphologyEx', mat, mat, react_native_fast_opencv_1.MorphTypes.MORPH_CLOSE, element);
|
|
166
|
+
// Canny edge detection with optimized thresholds
|
|
163
167
|
step = 'Canny';
|
|
164
168
|
reportStage(step);
|
|
165
|
-
react_native_fast_opencv_1.OpenCV.invoke('Canny', mat, mat,
|
|
169
|
+
react_native_fast_opencv_1.OpenCV.invoke('Canny', mat, mat, 50, 150);
|
|
170
|
+
// Dilate edges slightly to connect nearby contours
|
|
171
|
+
step = 'dilate';
|
|
172
|
+
reportStage(step);
|
|
173
|
+
const dilateKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 2, 2);
|
|
174
|
+
const dilateElement = react_native_fast_opencv_1.OpenCV.invoke('getStructuringElement', react_native_fast_opencv_1.MorphShapes.MORPH_RECT, dilateKernel);
|
|
175
|
+
react_native_fast_opencv_1.OpenCV.invoke('dilate', mat, mat, dilateElement);
|
|
166
176
|
step = 'createContours';
|
|
167
177
|
reportStage(step);
|
|
168
178
|
const contours = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVectorOfVectors);
|
|
169
|
-
react_native_fast_opencv_1.OpenCV.invoke('findContours', mat, contours, react_native_fast_opencv_1.RetrievalModes.
|
|
179
|
+
react_native_fast_opencv_1.OpenCV.invoke('findContours', mat, contours, react_native_fast_opencv_1.RetrievalModes.RETR_EXTERNAL, react_native_fast_opencv_1.ContourApproximationModes.CHAIN_APPROX_SIMPLE);
|
|
170
180
|
let best = null;
|
|
171
181
|
let maxArea = 0;
|
|
182
|
+
const frameArea = width * height;
|
|
172
183
|
step = 'toJSValue';
|
|
173
184
|
reportStage(step);
|
|
174
185
|
const contourVector = react_native_fast_opencv_1.OpenCV.toJSValue(contours);
|
|
@@ -180,11 +191,13 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
180
191
|
step = `contour_${i}_area`;
|
|
181
192
|
reportStage(step);
|
|
182
193
|
const { value: area } = react_native_fast_opencv_1.OpenCV.invoke('contourArea', contour, false);
|
|
194
|
+
const areaRatio = area / frameArea;
|
|
183
195
|
if (__DEV__) {
|
|
184
|
-
console.log('[DocScanner] area ratio',
|
|
196
|
+
console.log('[DocScanner] area ratio', areaRatio);
|
|
185
197
|
}
|
|
186
|
-
//
|
|
187
|
-
|
|
198
|
+
// Filter by area: document should be at least 5% and at most 95% of frame
|
|
199
|
+
// This prevents detecting tiny noise or the entire frame
|
|
200
|
+
if (areaRatio < 0.05 || areaRatio > 0.95) {
|
|
188
201
|
continue;
|
|
189
202
|
}
|
|
190
203
|
step = `contour_${i}_arcLength`;
|
|
@@ -192,10 +205,11 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
192
205
|
const { value: perimeter } = react_native_fast_opencv_1.OpenCV.invoke('arcLength', contour, true);
|
|
193
206
|
const approx = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVector);
|
|
194
207
|
let approxArray = [];
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
208
|
+
// Start with smaller epsilon for more accurate corner detection
|
|
209
|
+
// Try epsilon values from 0.5% to 5% of perimeter
|
|
210
|
+
const epsilonValues = [0.005, 0.01, 0.015, 0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.05];
|
|
211
|
+
for (let attempt = 0; attempt < epsilonValues.length; attempt += 1) {
|
|
212
|
+
const epsilon = epsilonValues[attempt] * perimeter;
|
|
199
213
|
step = `contour_${i}_approxPolyDP_attempt_${attempt}`;
|
|
200
214
|
reportStage(step);
|
|
201
215
|
react_native_fast_opencv_1.OpenCV.invoke('approxPolyDP', contour, approx, epsilon, true);
|
|
@@ -210,41 +224,8 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
210
224
|
approxArray = candidate;
|
|
211
225
|
break;
|
|
212
226
|
}
|
|
213
|
-
if (approxArray.length === 0 || Math.abs(candidate.length - 4) < Math.abs(approxArray.length - 4)) {
|
|
214
|
-
approxArray = candidate;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (approxArray.length !== 4) {
|
|
218
|
-
// fallback: boundingRect (axis-aligned) so we always have 4 points
|
|
219
|
-
try {
|
|
220
|
-
const rect = react_native_fast_opencv_1.OpenCV.invoke('boundingRect', contour);
|
|
221
|
-
// Convert the rect object to JS value to get actual coordinates
|
|
222
|
-
const rectJS = react_native_fast_opencv_1.OpenCV.toJSValue(rect);
|
|
223
|
-
const rectValue = rectJS?.value ?? rectJS;
|
|
224
|
-
const rectX = rectValue?.x ?? 0;
|
|
225
|
-
const rectY = rectValue?.y ?? 0;
|
|
226
|
-
const rectW = rectValue?.width ?? 0;
|
|
227
|
-
const rectH = rectValue?.height ?? 0;
|
|
228
|
-
// Validate that we have a valid rectangle
|
|
229
|
-
if (rectW > 0 && rectH > 0) {
|
|
230
|
-
approxArray = [
|
|
231
|
-
{ x: rectX, y: rectY },
|
|
232
|
-
{ x: rectX + rectW, y: rectY },
|
|
233
|
-
{ x: rectX + rectW, y: rectY + rectH },
|
|
234
|
-
{ x: rectX, y: rectY + rectH },
|
|
235
|
-
];
|
|
236
|
-
usedBoundingRect = true;
|
|
237
|
-
if (__DEV__) {
|
|
238
|
-
console.log('[DocScanner] using boundingRect fallback:', approxArray);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
catch (err) {
|
|
243
|
-
if (__DEV__) {
|
|
244
|
-
console.warn('[DocScanner] boundingRect fallback failed:', err);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
227
|
}
|
|
228
|
+
// Only proceed if we found exactly 4 corners
|
|
248
229
|
if (approxArray.length !== 4) {
|
|
249
230
|
continue;
|
|
250
231
|
}
|
|
@@ -266,23 +247,21 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
266
247
|
x: pt.x / ratio,
|
|
267
248
|
y: pt.y / ratio,
|
|
268
249
|
}));
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (!isConvexQuadrilateral(points)) {
|
|
273
|
-
if (__DEV__) {
|
|
274
|
-
console.log('[DocScanner] not convex, skipping:', points);
|
|
275
|
-
}
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
catch (err) {
|
|
250
|
+
// Verify the quadrilateral is convex (valid document shape)
|
|
251
|
+
try {
|
|
252
|
+
if (!isConvexQuadrilateral(points)) {
|
|
280
253
|
if (__DEV__) {
|
|
281
|
-
console.
|
|
254
|
+
console.log('[DocScanner] not convex, skipping:', points);
|
|
282
255
|
}
|
|
283
256
|
continue;
|
|
284
257
|
}
|
|
285
258
|
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
if (__DEV__) {
|
|
261
|
+
console.warn('[DocScanner] convex check error:', err, 'points:', points);
|
|
262
|
+
}
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
286
265
|
if (area > maxArea) {
|
|
287
266
|
best = points;
|
|
288
267
|
maxArea = area;
|
package/package.json
CHANGED
package/src/DocScanner.tsx
CHANGED
|
@@ -147,7 +147,8 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
147
147
|
// Report frame size for coordinate transformation
|
|
148
148
|
updateFrameSize(frame.width, frame.height);
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
// Use higher resolution for better accuracy - 720p instead of 480p
|
|
151
|
+
const ratio = 720 / frame.width;
|
|
151
152
|
const width = Math.floor(frame.width * ratio);
|
|
152
153
|
const height = Math.floor(frame.height * ratio);
|
|
153
154
|
step = 'resize';
|
|
@@ -166,29 +167,41 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
166
167
|
reportStage(step);
|
|
167
168
|
OpenCV.invoke('cvtColor', mat, mat, ColorConversionCodes.COLOR_BGR2GRAY);
|
|
168
169
|
|
|
169
|
-
|
|
170
|
+
// Apply Gaussian blur to reduce noise
|
|
171
|
+
const gaussianKernel = OpenCV.createObject(ObjectType.Size, 5, 5);
|
|
172
|
+
step = 'GaussianBlur';
|
|
173
|
+
reportStage(step);
|
|
174
|
+
OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
|
|
175
|
+
|
|
176
|
+
// Morphological operations to enhance edges
|
|
177
|
+
const morphologyKernel = OpenCV.createObject(ObjectType.Size, 3, 3);
|
|
170
178
|
step = 'getStructuringElement';
|
|
171
179
|
reportStage(step);
|
|
172
180
|
const element = OpenCV.invoke('getStructuringElement', MorphShapes.MORPH_RECT, morphologyKernel);
|
|
173
181
|
step = 'morphologyEx';
|
|
174
182
|
reportStage(step);
|
|
175
|
-
OpenCV.invoke('morphologyEx', mat, mat, MorphTypes.
|
|
183
|
+
OpenCV.invoke('morphologyEx', mat, mat, MorphTypes.MORPH_CLOSE, element);
|
|
176
184
|
|
|
177
|
-
|
|
178
|
-
step = 'GaussianBlur';
|
|
179
|
-
reportStage(step);
|
|
180
|
-
OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
|
|
185
|
+
// Canny edge detection with optimized thresholds
|
|
181
186
|
step = 'Canny';
|
|
182
187
|
reportStage(step);
|
|
183
|
-
OpenCV.invoke('Canny', mat, mat,
|
|
188
|
+
OpenCV.invoke('Canny', mat, mat, 50, 150);
|
|
189
|
+
|
|
190
|
+
// Dilate edges slightly to connect nearby contours
|
|
191
|
+
step = 'dilate';
|
|
192
|
+
reportStage(step);
|
|
193
|
+
const dilateKernel = OpenCV.createObject(ObjectType.Size, 2, 2);
|
|
194
|
+
const dilateElement = OpenCV.invoke('getStructuringElement', MorphShapes.MORPH_RECT, dilateKernel);
|
|
195
|
+
OpenCV.invoke('dilate', mat, mat, dilateElement);
|
|
184
196
|
|
|
185
197
|
step = 'createContours';
|
|
186
198
|
reportStage(step);
|
|
187
199
|
const contours = OpenCV.createObject(ObjectType.PointVectorOfVectors);
|
|
188
|
-
OpenCV.invoke('findContours', mat, contours, RetrievalModes.
|
|
200
|
+
OpenCV.invoke('findContours', mat, contours, RetrievalModes.RETR_EXTERNAL, ContourApproximationModes.CHAIN_APPROX_SIMPLE);
|
|
189
201
|
|
|
190
202
|
let best: Point[] | null = null;
|
|
191
203
|
let maxArea = 0;
|
|
204
|
+
const frameArea = width * height;
|
|
192
205
|
|
|
193
206
|
step = 'toJSValue';
|
|
194
207
|
reportStage(step);
|
|
@@ -203,13 +216,15 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
203
216
|
step = `contour_${i}_area`;
|
|
204
217
|
reportStage(step);
|
|
205
218
|
const { value: area } = OpenCV.invoke('contourArea', contour, false);
|
|
219
|
+
const areaRatio = area / frameArea;
|
|
206
220
|
|
|
207
221
|
if (__DEV__) {
|
|
208
|
-
console.log('[DocScanner] area ratio',
|
|
222
|
+
console.log('[DocScanner] area ratio', areaRatio);
|
|
209
223
|
}
|
|
210
224
|
|
|
211
|
-
//
|
|
212
|
-
|
|
225
|
+
// Filter by area: document should be at least 5% and at most 95% of frame
|
|
226
|
+
// This prevents detecting tiny noise or the entire frame
|
|
227
|
+
if (areaRatio < 0.05 || areaRatio > 0.95) {
|
|
213
228
|
continue;
|
|
214
229
|
}
|
|
215
230
|
|
|
@@ -219,11 +234,13 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
219
234
|
const approx = OpenCV.createObject(ObjectType.PointVector);
|
|
220
235
|
|
|
221
236
|
let approxArray: Array<{ x: number; y: number }> = [];
|
|
222
|
-
let usedBoundingRect = false;
|
|
223
|
-
let epsilonBase = 0.006 * perimeter;
|
|
224
237
|
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
// Start with smaller epsilon for more accurate corner detection
|
|
239
|
+
// Try epsilon values from 0.5% to 5% of perimeter
|
|
240
|
+
const epsilonValues = [0.005, 0.01, 0.015, 0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.05];
|
|
241
|
+
|
|
242
|
+
for (let attempt = 0; attempt < epsilonValues.length; attempt += 1) {
|
|
243
|
+
const epsilon = epsilonValues[attempt] * perimeter;
|
|
227
244
|
step = `contour_${i}_approxPolyDP_attempt_${attempt}`;
|
|
228
245
|
reportStage(step);
|
|
229
246
|
OpenCV.invoke('approxPolyDP', contour, approx, epsilon, true);
|
|
@@ -241,46 +258,9 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
241
258
|
approxArray = candidate as Array<{ x: number; y: number }>;
|
|
242
259
|
break;
|
|
243
260
|
}
|
|
244
|
-
|
|
245
|
-
if (approxArray.length === 0 || Math.abs(candidate.length - 4) < Math.abs(approxArray.length - 4)) {
|
|
246
|
-
approxArray = candidate as Array<{ x: number; y: number }>;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (approxArray.length !== 4) {
|
|
251
|
-
// fallback: boundingRect (axis-aligned) so we always have 4 points
|
|
252
|
-
try {
|
|
253
|
-
const rect = OpenCV.invoke('boundingRect', contour);
|
|
254
|
-
// Convert the rect object to JS value to get actual coordinates
|
|
255
|
-
const rectJS = OpenCV.toJSValue(rect);
|
|
256
|
-
const rectValue = rectJS?.value ?? rectJS;
|
|
257
|
-
|
|
258
|
-
const rectX = rectValue?.x ?? 0;
|
|
259
|
-
const rectY = rectValue?.y ?? 0;
|
|
260
|
-
const rectW = rectValue?.width ?? 0;
|
|
261
|
-
const rectH = rectValue?.height ?? 0;
|
|
262
|
-
|
|
263
|
-
// Validate that we have a valid rectangle
|
|
264
|
-
if (rectW > 0 && rectH > 0) {
|
|
265
|
-
approxArray = [
|
|
266
|
-
{ x: rectX, y: rectY },
|
|
267
|
-
{ x: rectX + rectW, y: rectY },
|
|
268
|
-
{ x: rectX + rectW, y: rectY + rectH },
|
|
269
|
-
{ x: rectX, y: rectY + rectH },
|
|
270
|
-
];
|
|
271
|
-
usedBoundingRect = true;
|
|
272
|
-
|
|
273
|
-
if (__DEV__) {
|
|
274
|
-
console.log('[DocScanner] using boundingRect fallback:', approxArray);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
} catch (err) {
|
|
278
|
-
if (__DEV__) {
|
|
279
|
-
console.warn('[DocScanner] boundingRect fallback failed:', err);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
261
|
}
|
|
283
262
|
|
|
263
|
+
// Only proceed if we found exactly 4 corners
|
|
284
264
|
if (approxArray.length !== 4) {
|
|
285
265
|
continue;
|
|
286
266
|
}
|
|
@@ -307,21 +287,19 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
307
287
|
y: pt.y / ratio,
|
|
308
288
|
}));
|
|
309
289
|
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (!isConvexQuadrilateral(points)) {
|
|
314
|
-
if (__DEV__) {
|
|
315
|
-
console.log('[DocScanner] not convex, skipping:', points);
|
|
316
|
-
}
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
} catch (err) {
|
|
290
|
+
// Verify the quadrilateral is convex (valid document shape)
|
|
291
|
+
try {
|
|
292
|
+
if (!isConvexQuadrilateral(points)) {
|
|
320
293
|
if (__DEV__) {
|
|
321
|
-
console.
|
|
294
|
+
console.log('[DocScanner] not convex, skipping:', points);
|
|
322
295
|
}
|
|
323
296
|
continue;
|
|
324
297
|
}
|
|
298
|
+
} catch (err) {
|
|
299
|
+
if (__DEV__) {
|
|
300
|
+
console.warn('[DocScanner] convex check error:', err, 'points:', points);
|
|
301
|
+
}
|
|
302
|
+
continue;
|
|
325
303
|
}
|
|
326
304
|
|
|
327
305
|
if (area > maxArea) {
|