react-native-rectangle-doc-scanner 0.28.0 → 0.30.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 +45 -22
- package/package.json +1 -1
- package/src/DocScanner.tsx +49 -24
package/dist/DocScanner.js
CHANGED
|
@@ -150,29 +150,20 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
150
150
|
step = 'cvtColor';
|
|
151
151
|
reportStage(step);
|
|
152
152
|
react_native_fast_opencv_1.OpenCV.invoke('cvtColor', mat, mat, react_native_fast_opencv_1.ColorConversionCodes.COLOR_BGR2GRAY);
|
|
153
|
-
|
|
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
|
+
const morphologyKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 5, 5);
|
|
160
154
|
step = 'getStructuringElement';
|
|
161
155
|
reportStage(step);
|
|
162
156
|
const element = react_native_fast_opencv_1.OpenCV.invoke('getStructuringElement', react_native_fast_opencv_1.MorphShapes.MORPH_RECT, morphologyKernel);
|
|
163
157
|
step = 'morphologyEx';
|
|
164
158
|
reportStage(step);
|
|
165
|
-
react_native_fast_opencv_1.OpenCV.invoke('morphologyEx', mat, mat, react_native_fast_opencv_1.MorphTypes.
|
|
166
|
-
|
|
159
|
+
react_native_fast_opencv_1.OpenCV.invoke('morphologyEx', mat, mat, react_native_fast_opencv_1.MorphTypes.MORPH_OPEN, element);
|
|
160
|
+
const gaussianKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 5, 5);
|
|
161
|
+
step = 'GaussianBlur';
|
|
162
|
+
reportStage(step);
|
|
163
|
+
react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
|
|
167
164
|
step = 'Canny';
|
|
168
165
|
reportStage(step);
|
|
169
166
|
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);
|
|
176
167
|
step = 'createContours';
|
|
177
168
|
reportStage(step);
|
|
178
169
|
const contours = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVectorOfVectors);
|
|
@@ -188,16 +179,23 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
188
179
|
step = `contour_${i}_copy`;
|
|
189
180
|
reportStage(step);
|
|
190
181
|
const contour = react_native_fast_opencv_1.OpenCV.copyObjectFromVector(contours, i);
|
|
191
|
-
|
|
182
|
+
// Compute absolute area first
|
|
183
|
+
step = `contour_${i}_area_abs`;
|
|
192
184
|
reportStage(step);
|
|
193
185
|
const { value: area } = react_native_fast_opencv_1.OpenCV.invoke('contourArea', contour, false);
|
|
186
|
+
// Skip extremely small contours below an absolute pixel threshold
|
|
187
|
+
if (area < 500) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
step = `contour_${i}_area`; // ratio stage
|
|
191
|
+
reportStage(step);
|
|
194
192
|
const areaRatio = area / frameArea;
|
|
195
193
|
if (__DEV__) {
|
|
196
|
-
console.log('[DocScanner] area ratio', areaRatio);
|
|
194
|
+
console.log('[DocScanner] area', area, 'ratio', areaRatio);
|
|
197
195
|
}
|
|
198
|
-
// Filter by area: document should be at least
|
|
196
|
+
// Filter by area: document should be at least 0.01% and at most 99% of frame
|
|
199
197
|
// This prevents detecting tiny noise or the entire frame
|
|
200
|
-
if (areaRatio < 0.
|
|
198
|
+
if (areaRatio < 0.0001 || areaRatio > 0.99) {
|
|
201
199
|
continue;
|
|
202
200
|
}
|
|
203
201
|
step = `contour_${i}_arcLength`;
|
|
@@ -206,8 +204,8 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
206
204
|
const approx = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVector);
|
|
207
205
|
let approxArray = [];
|
|
208
206
|
// Start with smaller epsilon for more accurate corner detection
|
|
209
|
-
// Try epsilon values from 0.
|
|
210
|
-
const epsilonValues = [0.
|
|
207
|
+
// Try epsilon values from 0.4% up to 6% of perimeter
|
|
208
|
+
const epsilonValues = [0.004, 0.006, 0.008, 0.01, 0.012, 0.015, 0.02, 0.03, 0.04, 0.05, 0.06];
|
|
211
209
|
for (let attempt = 0; attempt < epsilonValues.length; attempt += 1) {
|
|
212
210
|
const epsilon = epsilonValues[attempt] * perimeter;
|
|
213
211
|
step = `contour_${i}_approxPolyDP_attempt_${attempt}`;
|
|
@@ -225,7 +223,32 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
|
|
|
225
223
|
break;
|
|
226
224
|
}
|
|
227
225
|
}
|
|
228
|
-
|
|
226
|
+
if (approxArray.length !== 4) {
|
|
227
|
+
// fallback: boundingRect (axis-aligned) so we always have 4 points
|
|
228
|
+
try {
|
|
229
|
+
const rect = react_native_fast_opencv_1.OpenCV.invoke('boundingRect', contour);
|
|
230
|
+
const rectValue = rect?.value ?? rect;
|
|
231
|
+
const rectX = rectValue.x ?? rectValue?.topLeft?.x ?? 0;
|
|
232
|
+
const rectY = rectValue.y ?? rectValue?.topLeft?.y ?? 0;
|
|
233
|
+
const rectW = rectValue.width ?? rectValue?.size?.width ?? 0;
|
|
234
|
+
const rectH = rectValue.height ?? rectValue?.size?.height ?? 0;
|
|
235
|
+
approxArray = [
|
|
236
|
+
{ x: rectX, y: rectY },
|
|
237
|
+
{ x: rectX + rectW, y: rectY },
|
|
238
|
+
{ x: rectX + rectW, y: rectY + rectH },
|
|
239
|
+
{ x: rectX, y: rectY + rectH },
|
|
240
|
+
];
|
|
241
|
+
if (__DEV__) {
|
|
242
|
+
console.log('[DocScanner] boundingRect fallback', approxArray);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
if (__DEV__) {
|
|
247
|
+
console.warn('[DocScanner] boundingRect fallback failed', err);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Only proceed if we found exactly 4 corners after fallback
|
|
229
252
|
if (approxArray.length !== 4) {
|
|
230
253
|
continue;
|
|
231
254
|
}
|
package/package.json
CHANGED
package/src/DocScanner.tsx
CHANGED
|
@@ -167,33 +167,22 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
167
167
|
reportStage(step);
|
|
168
168
|
OpenCV.invoke('cvtColor', mat, mat, ColorConversionCodes.COLOR_BGR2GRAY);
|
|
169
169
|
|
|
170
|
-
|
|
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
|
+
const morphologyKernel = OpenCV.createObject(ObjectType.Size, 5, 5);
|
|
178
171
|
step = 'getStructuringElement';
|
|
179
172
|
reportStage(step);
|
|
180
173
|
const element = OpenCV.invoke('getStructuringElement', MorphShapes.MORPH_RECT, morphologyKernel);
|
|
181
174
|
step = 'morphologyEx';
|
|
182
175
|
reportStage(step);
|
|
183
|
-
OpenCV.invoke('morphologyEx', mat, mat, MorphTypes.
|
|
176
|
+
OpenCV.invoke('morphologyEx', mat, mat, MorphTypes.MORPH_OPEN, element);
|
|
184
177
|
|
|
185
|
-
|
|
178
|
+
const gaussianKernel = OpenCV.createObject(ObjectType.Size, 5, 5);
|
|
179
|
+
step = 'GaussianBlur';
|
|
180
|
+
reportStage(step);
|
|
181
|
+
OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
|
|
186
182
|
step = 'Canny';
|
|
187
183
|
reportStage(step);
|
|
188
184
|
OpenCV.invoke('Canny', mat, mat, 50, 150);
|
|
189
185
|
|
|
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);
|
|
196
|
-
|
|
197
186
|
step = 'createContours';
|
|
198
187
|
reportStage(step);
|
|
199
188
|
const contours = OpenCV.createObject(ObjectType.PointVectorOfVectors);
|
|
@@ -213,18 +202,27 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
213
202
|
reportStage(step);
|
|
214
203
|
const contour = OpenCV.copyObjectFromVector(contours, i);
|
|
215
204
|
|
|
216
|
-
|
|
205
|
+
// Compute absolute area first
|
|
206
|
+
step = `contour_${i}_area_abs`;
|
|
217
207
|
reportStage(step);
|
|
218
208
|
const { value: area } = OpenCV.invoke('contourArea', contour, false);
|
|
209
|
+
|
|
210
|
+
// Skip extremely small contours below an absolute pixel threshold
|
|
211
|
+
if (area < 500) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
step = `contour_${i}_area`; // ratio stage
|
|
216
|
+
reportStage(step);
|
|
219
217
|
const areaRatio = area / frameArea;
|
|
220
218
|
|
|
221
219
|
if (__DEV__) {
|
|
222
|
-
console.log('[DocScanner] area ratio', areaRatio);
|
|
220
|
+
console.log('[DocScanner] area', area, 'ratio', areaRatio);
|
|
223
221
|
}
|
|
224
222
|
|
|
225
|
-
// Filter by area: document should be at least
|
|
223
|
+
// Filter by area: document should be at least 0.01% and at most 99% of frame
|
|
226
224
|
// This prevents detecting tiny noise or the entire frame
|
|
227
|
-
if (areaRatio < 0.
|
|
225
|
+
if (areaRatio < 0.0001 || areaRatio > 0.99) {
|
|
228
226
|
continue;
|
|
229
227
|
}
|
|
230
228
|
|
|
@@ -236,8 +234,8 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
236
234
|
let approxArray: Array<{ x: number; y: number }> = [];
|
|
237
235
|
|
|
238
236
|
// Start with smaller epsilon for more accurate corner detection
|
|
239
|
-
// Try epsilon values from 0.
|
|
240
|
-
const epsilonValues = [0.
|
|
237
|
+
// Try epsilon values from 0.4% up to 6% of perimeter
|
|
238
|
+
const epsilonValues = [0.004, 0.006, 0.008, 0.01, 0.012, 0.015, 0.02, 0.03, 0.04, 0.05, 0.06];
|
|
241
239
|
|
|
242
240
|
for (let attempt = 0; attempt < epsilonValues.length; attempt += 1) {
|
|
243
241
|
const epsilon = epsilonValues[attempt] * perimeter;
|
|
@@ -260,7 +258,34 @@ export const DocScanner: React.FC<Props> = ({
|
|
|
260
258
|
}
|
|
261
259
|
}
|
|
262
260
|
|
|
263
|
-
|
|
261
|
+
if (approxArray.length !== 4) {
|
|
262
|
+
// fallback: boundingRect (axis-aligned) so we always have 4 points
|
|
263
|
+
try {
|
|
264
|
+
const rect = OpenCV.invoke('boundingRect', contour);
|
|
265
|
+
const rectValue = rect?.value ?? rect;
|
|
266
|
+
const rectX = rectValue.x ?? rectValue?.topLeft?.x ?? 0;
|
|
267
|
+
const rectY = rectValue.y ?? rectValue?.topLeft?.y ?? 0;
|
|
268
|
+
const rectW = rectValue.width ?? rectValue?.size?.width ?? 0;
|
|
269
|
+
const rectH = rectValue.height ?? rectValue?.size?.height ?? 0;
|
|
270
|
+
|
|
271
|
+
approxArray = [
|
|
272
|
+
{ x: rectX, y: rectY },
|
|
273
|
+
{ x: rectX + rectW, y: rectY },
|
|
274
|
+
{ x: rectX + rectW, y: rectY + rectH },
|
|
275
|
+
{ x: rectX, y: rectY + rectH },
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
if (__DEV__) {
|
|
279
|
+
console.log('[DocScanner] boundingRect fallback', approxArray);
|
|
280
|
+
}
|
|
281
|
+
} catch (err) {
|
|
282
|
+
if (__DEV__) {
|
|
283
|
+
console.warn('[DocScanner] boundingRect fallback failed', err);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Only proceed if we found exactly 4 corners after fallback
|
|
264
289
|
if (approxArray.length !== 4) {
|
|
265
290
|
continue;
|
|
266
291
|
}
|