react-native-rectangle-doc-scanner 0.43.0 → 0.45.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/README.md CHANGED
@@ -5,6 +5,10 @@ VisionCamera + Fast-OpenCV powered document scanner template built for React Nat
5
5
  ## Features
6
6
  - Real-time quad detection using `react-native-fast-opencv`
7
7
  - Frame processor worklet executed on the UI thread via `react-native-vision-camera`
8
+ - High-resolution processing (1280p) for accurate corner detection
9
+ - Advanced anchor locking system maintains corner positions during camera movement
10
+ - Intelligent edge detection with optimized Canny parameters (50/150 thresholds)
11
+ - Adaptive smoothing with weighted averaging across multiple frames
8
12
  - Resize plugin keeps frame processing fast on lower-end devices
9
13
  - Skia overlay visualises detected document contours
10
14
  - Stability tracker enables auto-capture once the document is steady
@@ -107,6 +111,37 @@ Passing `children` lets you render any UI on top of the camera preview, so you c
107
111
  - The internal frame processor handles document detection; do not override `frameProcessor` in `cameraProps`.
108
112
  - Adjust `minStableFrames` or tweak lighting conditions if auto capture is too sensitive or too slow.
109
113
 
114
+ ## Detection Algorithm
115
+
116
+ The scanner uses a sophisticated multi-stage pipeline:
117
+
118
+ 1. **Pre-processing** (1280p resolution for accuracy)
119
+ - Converts frame to grayscale
120
+ - Applies morphological opening to reduce noise
121
+ - Gaussian blur for smoother edges
122
+ - Canny edge detection with 50/150 thresholds
123
+
124
+ 2. **Contour Detection**
125
+ - Finds external contours using CHAIN_APPROX_SIMPLE
126
+ - Applies convex hull for better corner detection
127
+ - Tests multiple epsilon values (0.1%-10%) for approxPolyDP
128
+ - Validates quadrilaterals for convexity
129
+
130
+ 3. **Anchor Locking System**
131
+ - Once corners are detected, they "snap" to stable positions
132
+ - Maintains lock even when camera moves (up to 200px center delta)
133
+ - Holds anchor for up to 20 missed detections
134
+ - Adaptive blending between old and new positions for smooth transitions
135
+ - Builds confidence over time (max 30 frames) for stronger locking
136
+
137
+ 4. **Quad Validation**
138
+ - Checks area ratio (0.02%-90% of frame)
139
+ - Validates minimum edge lengths
140
+ - Ensures reasonable aspect ratios
141
+ - Filters out non-convex shapes
142
+
143
+ This approach ensures corners remain locked once detected, allowing you to move the camera slightly without losing the document boundary.
144
+
110
145
  ## Build
111
146
  ```sh
112
147
  yarn build
@@ -100,19 +100,19 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
100
100
  const lastMeasurementRef = (0, react_1.useRef)(null);
101
101
  const frameSizeRef = (0, react_1.useRef)(null);
102
102
  const MAX_HISTORY = 5;
103
- const SNAP_DISTANCE = 5; // pixels; keep corners locked when movement is tiny
104
- const SNAP_CENTER_DISTANCE = 12;
105
- const BLEND_DISTANCE = 65; // pixels; softly ease between similar shapes
106
- const MAX_CENTER_DELTA = 85;
107
- const REJECT_CENTER_DELTA = 145;
108
- const MAX_AREA_SHIFT = 0.45;
109
- const HISTORY_RESET_DISTANCE = 70;
110
- const MIN_AREA_RATIO = 0.0035;
103
+ const SNAP_DISTANCE = 8; // pixels; keep corners locked when movement is tiny (increased for stronger lock)
104
+ const SNAP_CENTER_DISTANCE = 18;
105
+ const BLEND_DISTANCE = 80; // pixels; softly ease between similar shapes (increased)
106
+ const MAX_CENTER_DELTA = 120; // increased to allow more camera movement
107
+ const REJECT_CENTER_DELTA = 200; // increased to maintain lock during camera movement
108
+ const MAX_AREA_SHIFT = 0.55; // more tolerance for perspective changes
109
+ const HISTORY_RESET_DISTANCE = 90;
110
+ const MIN_AREA_RATIO = 0.0002;
111
111
  const MAX_AREA_RATIO = 0.9;
112
- const MIN_EDGE_RATIO = 0.025;
113
- const MAX_ANCHOR_MISSES = 12;
112
+ const MIN_EDGE_RATIO = 0.015;
113
+ const MAX_ANCHOR_MISSES = 20; // increased to hold anchor longer when detection temporarily fails
114
114
  const MIN_CONFIDENCE_TO_HOLD = 2;
115
- const MAX_ANCHOR_CONFIDENCE = 20;
115
+ const MAX_ANCHOR_CONFIDENCE = 30; // increased max confidence for stronger anchoring
116
116
  const updateQuad = (0, react_native_worklets_core_1.useRunOnJS)((value) => {
117
117
  if (__DEV__) {
118
118
  console.log('[DocScanner] quad', value);
@@ -189,10 +189,13 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
189
189
  const centerDelta = Math.hypot(candidateCenter.x - anchorCenter.x, candidateCenter.y - anchorCenter.y);
190
190
  const areaShift = anchorArea > 0 ? Math.abs(anchorArea - candidateArea) / anchorArea : 0;
191
191
  if (centerDelta >= REJECT_CENTER_DELTA || areaShift > 1.2) {
192
- const handled = fallbackToAnchor(true);
193
- if (handled) {
194
- return;
195
- }
192
+ smoothingBufferRef.current = [sanitized];
193
+ lastMeasurementRef.current = sanitized;
194
+ anchorQuadRef.current = candidate;
195
+ anchorConfidenceRef.current = 1;
196
+ anchorMissesRef.current = 0;
197
+ lastQuadRef.current = candidate;
198
+ setQuad(candidate);
196
199
  return;
197
200
  }
198
201
  if (delta <= SNAP_DISTANCE && centerDelta <= SNAP_CENTER_DISTANCE && areaShift <= 0.08) {
@@ -246,8 +249,8 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
246
249
  try {
247
250
  // Report frame size for coordinate transformation
248
251
  updateFrameSize(frame.width, frame.height);
249
- // Use higher resolution for better accuracy - 960p
250
- const ratio = 960 / frame.width;
252
+ // Use higher resolution for better accuracy - 1280p for improved corner detection
253
+ const ratio = 1280 / frame.width;
251
254
  const width = Math.floor(frame.width * ratio);
252
255
  const height = Math.floor(frame.height * ratio);
253
256
  step = 'resize';
@@ -276,7 +279,9 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
276
279
  react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
277
280
  step = 'Canny';
278
281
  reportStage(step);
279
- react_native_fast_opencv_1.OpenCV.invoke('Canny', mat, mat, 30, 100);
282
+ // Improved Canny parameters for better edge detection accuracy
283
+ // Lower threshold (50 -> more edges detected) and higher threshold (150 -> stronger edges kept)
284
+ react_native_fast_opencv_1.OpenCV.invoke('Canny', mat, mat, 50, 150);
280
285
  step = 'createContours';
281
286
  reportStage(step);
282
287
  const contours = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVectorOfVectors);
@@ -310,7 +315,7 @@ const DocScanner = ({ onCapture, overlayColor = '#e7a649', autoCapture = true, m
310
315
  console.log('[DocScanner] area', area, 'ratio', areaRatio);
311
316
  }
312
317
  // Skip if area ratio is too small or too large
313
- if (areaRatio < 0.005 || areaRatio > 0.95) {
318
+ if (areaRatio < 0.0002 || areaRatio > 0.99) {
314
319
  continue;
315
320
  }
316
321
  // Try to use convex hull for better corner detection
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "0.43.0",
3
+ "version": "0.45.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {
@@ -116,19 +116,19 @@ export const DocScanner: React.FC<Props> = ({
116
116
  const frameSizeRef = useRef<{ width: number; height: number } | null>(null);
117
117
 
118
118
  const MAX_HISTORY = 5;
119
- const SNAP_DISTANCE = 5; // pixels; keep corners locked when movement is tiny
120
- const SNAP_CENTER_DISTANCE = 12;
121
- const BLEND_DISTANCE = 65; // pixels; softly ease between similar shapes
122
- const MAX_CENTER_DELTA = 85;
123
- const REJECT_CENTER_DELTA = 145;
124
- const MAX_AREA_SHIFT = 0.45;
125
- const HISTORY_RESET_DISTANCE = 70;
126
- const MIN_AREA_RATIO = 0.0035;
119
+ const SNAP_DISTANCE = 8; // pixels; keep corners locked when movement is tiny (increased for stronger lock)
120
+ const SNAP_CENTER_DISTANCE = 18;
121
+ const BLEND_DISTANCE = 80; // pixels; softly ease between similar shapes (increased)
122
+ const MAX_CENTER_DELTA = 120; // increased to allow more camera movement
123
+ const REJECT_CENTER_DELTA = 200; // increased to maintain lock during camera movement
124
+ const MAX_AREA_SHIFT = 0.55; // more tolerance for perspective changes
125
+ const HISTORY_RESET_DISTANCE = 90;
126
+ const MIN_AREA_RATIO = 0.0002;
127
127
  const MAX_AREA_RATIO = 0.9;
128
- const MIN_EDGE_RATIO = 0.025;
129
- const MAX_ANCHOR_MISSES = 12;
128
+ const MIN_EDGE_RATIO = 0.015;
129
+ const MAX_ANCHOR_MISSES = 20; // increased to hold anchor longer when detection temporarily fails
130
130
  const MIN_CONFIDENCE_TO_HOLD = 2;
131
- const MAX_ANCHOR_CONFIDENCE = 20;
131
+ const MAX_ANCHOR_CONFIDENCE = 30; // increased max confidence for stronger anchoring
132
132
 
133
133
  const updateQuad = useRunOnJS((value: Point[] | null) => {
134
134
  if (__DEV__) {
@@ -224,10 +224,13 @@ export const DocScanner: React.FC<Props> = ({
224
224
  const areaShift = anchorArea > 0 ? Math.abs(anchorArea - candidateArea) / anchorArea : 0;
225
225
 
226
226
  if (centerDelta >= REJECT_CENTER_DELTA || areaShift > 1.2) {
227
- const handled = fallbackToAnchor(true);
228
- if (handled) {
229
- return;
230
- }
227
+ smoothingBufferRef.current = [sanitized];
228
+ lastMeasurementRef.current = sanitized;
229
+ anchorQuadRef.current = candidate;
230
+ anchorConfidenceRef.current = 1;
231
+ anchorMissesRef.current = 0;
232
+ lastQuadRef.current = candidate;
233
+ setQuad(candidate);
231
234
  return;
232
235
  }
233
236
 
@@ -287,8 +290,8 @@ export const DocScanner: React.FC<Props> = ({
287
290
  // Report frame size for coordinate transformation
288
291
  updateFrameSize(frame.width, frame.height);
289
292
 
290
- // Use higher resolution for better accuracy - 960p
291
- const ratio = 960 / frame.width;
293
+ // Use higher resolution for better accuracy - 1280p for improved corner detection
294
+ const ratio = 1280 / frame.width;
292
295
  const width = Math.floor(frame.width * ratio);
293
296
  const height = Math.floor(frame.height * ratio);
294
297
  step = 'resize';
@@ -321,7 +324,9 @@ export const DocScanner: React.FC<Props> = ({
321
324
  OpenCV.invoke('GaussianBlur', mat, mat, gaussianKernel, 0);
322
325
  step = 'Canny';
323
326
  reportStage(step);
324
- OpenCV.invoke('Canny', mat, mat, 30, 100);
327
+ // Improved Canny parameters for better edge detection accuracy
328
+ // Lower threshold (50 -> more edges detected) and higher threshold (150 -> stronger edges kept)
329
+ OpenCV.invoke('Canny', mat, mat, 50, 150);
325
330
 
326
331
  step = 'createContours';
327
332
  reportStage(step);
@@ -365,7 +370,7 @@ export const DocScanner: React.FC<Props> = ({
365
370
  }
366
371
 
367
372
  // Skip if area ratio is too small or too large
368
- if (areaRatio < 0.005 || areaRatio > 0.95) {
373
+ if (areaRatio < 0.0002 || areaRatio > 0.99) {
369
374
  continue;
370
375
  }
371
376