rn-opencv-doc-perspective-correction 1.0.4 → 1.0.6

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/index.d.ts CHANGED
@@ -3,20 +3,8 @@ export type Point = {
3
3
  y: number;
4
4
  };
5
5
  export declare class DocumentScanner {
6
- /**
7
- * Tính khoảng cách Euclidean giữa 2 điểm
8
- */
9
6
  private static getDistance;
10
- /**
11
- * Sắp xếp 4 điểm thành chuỗi TL, TR, BR, BL
12
- */
13
7
  private static sortCorners;
14
- /**
15
- * Bước 1: Page Corner Detection (Auto-detect góc tài liệu)
16
- */
17
- static detectPageCorners(imageBase64: string): Point[] | undefined;
18
- /**
19
- * Bước 2: Perspective Correction
20
- */
21
- static applyPerspectiveCorrection(imageBase64: string, corners: Point[]): string | undefined;
8
+ static detectPageCorners(imageBase64: string, onLog?: (msg: string) => void): Point[] | undefined;
9
+ static applyPerspectiveCorrection(imageBase64: string, corners: Point[], onLog?: (msg: string) => void): string | undefined;
22
10
  }
package/dist/index.js CHANGED
@@ -4,15 +4,9 @@ exports.DocumentScanner = void 0;
4
4
  // @ts-nocheck
5
5
  const react_native_fast_opencv_1 = require("react-native-fast-opencv");
6
6
  class DocumentScanner {
7
- /**
8
- * Tính khoảng cách Euclidean giữa 2 điểm
9
- */
10
7
  static getDistance(p1, p2) {
11
8
  return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
12
9
  }
13
- /**
14
- * Sắp xếp 4 điểm thành chuỗi TL, TR, BR, BL
15
- */
16
10
  static sortCorners(corners) {
17
11
  if (corners.length !== 4)
18
12
  return corners;
@@ -23,10 +17,7 @@ class DocumentScanner {
23
17
  return angleA - angleB;
24
18
  });
25
19
  }
26
- /**
27
- * Bước 1: Page Corner Detection (Auto-detect góc tài liệu)
28
- */
29
- static detectPageCorners(imageBase64) {
20
+ static detectPageCorners(imageBase64, onLog) {
30
21
  let src = null;
31
22
  let gray = null;
32
23
  let blurred = null;
@@ -37,31 +28,58 @@ class DocumentScanner {
37
28
  src = react_native_fast_opencv_1.OpenCV.base64ToMat(imageBase64);
38
29
  gray = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8U);
39
30
  blurred = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8U);
40
- edges = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8U);
31
+ edges = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8U); // This will hold the threshold output
41
32
  react_native_fast_opencv_1.OpenCV.invoke('cvtColor', src, gray, react_native_fast_opencv_1.ColorConversionCodes.COLOR_BGR2GRAY);
42
33
  const ksize = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 5, 5);
43
34
  react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', gray, blurred, ksize, 0);
44
- react_native_fast_opencv_1.OpenCV.invoke('Canny', blurred, edges, 75, 200, 3, false);
35
+ // Python uses: cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
36
+ // THRESH_BINARY = 0
37
+ // THRESH_OTSU = 8
38
+ // THRESH_BINARY + THRESH_OTSU = 8
39
+ react_native_fast_opencv_1.OpenCV.invoke('threshold', blurred, edges, 0, 255, 8);
45
40
  contoursObj = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.MatVector);
46
41
  hierarchyObj = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8U);
47
- react_native_fast_opencv_1.OpenCV.invoke('findContours', edges, contoursObj, hierarchyObj, 1, 2);
48
- const contoursSize = react_native_fast_opencv_1.OpenCV.invoke('size', contoursObj) || 0;
49
- let maxArea = 0;
50
- let largestPoly = undefined;
42
+ // Using RETR_EXTERNAL similar to the Python script for outer contours
43
+ react_native_fast_opencv_1.OpenCV.invoke('findContoursWithHierarchy', edges, contoursObj, hierarchyObj, 0 /* RETR_EXTERNAL */, 2 /* CHAIN_APPROX_SIMPLE */);
44
+ const contoursJS = react_native_fast_opencv_1.OpenCV.toJSValue(contoursObj);
45
+ const contoursArray = (contoursJS === null || contoursJS === void 0 ? void 0 : contoursJS.array) || [];
46
+ const contoursSize = contoursArray.length;
47
+ if (contoursSize === 0) {
48
+ if (onLog)
49
+ onLog(`[OpenCV] Không tìm thấy contours.`);
50
+ return undefined;
51
+ }
52
+ // First pass: extract all areas to sort them and minimize JSI calls
53
+ let contourMetrics = [];
51
54
  for (let i = 0; i < contoursSize; i++) {
52
- const contour = react_native_fast_opencv_1.OpenCV.invoke('get', contoursObj, i);
53
- const area = react_native_fast_opencv_1.OpenCV.invoke('contourArea', contour);
54
- if (area > maxArea) {
55
- const peri = react_native_fast_opencv_1.OpenCV.invoke('arcLength', contour, true);
56
- const approx = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVector);
57
- react_native_fast_opencv_1.OpenCV.invoke('approxPolyDP', contour, approx, 0.02 * peri, true);
58
- const approxJS = react_native_fast_opencv_1.OpenCV.toJSValue(approx);
59
- if (approxJS && approxJS.array && approxJS.array.length === 4) {
60
- maxArea = area;
61
- largestPoly = approxJS.array;
62
- }
55
+ const contour = react_native_fast_opencv_1.OpenCV.copyObjectFromVector(contoursObj, i);
56
+ const areaObj = react_native_fast_opencv_1.OpenCV.invoke('contourArea', contour);
57
+ const area = areaObj ? areaObj.value : 0;
58
+ if (area > 5000) { // filter very small artifacts
59
+ contourMetrics.push({ index: i, area, contour });
63
60
  }
64
61
  }
62
+ // Sort contours by area in descending order
63
+ contourMetrics.sort((a, b) => b.area - a.area);
64
+ let largestPoly = undefined;
65
+ // Second pass: only check approxPolyDP for the largest ones
66
+ for (let i = 0; i < contourMetrics.length; i++) {
67
+ const metric = contourMetrics[i];
68
+ const contour = metric.contour;
69
+ const periObj = react_native_fast_opencv_1.OpenCV.invoke('arcLength', contour, true);
70
+ const peri = periObj ? periObj.value : 0;
71
+ const approx = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVector);
72
+ react_native_fast_opencv_1.OpenCV.invoke('approxPolyDP', contour, approx, 0.02 * peri, true);
73
+ const approxJS = react_native_fast_opencv_1.OpenCV.toJSValue(approx);
74
+ if (approxJS && approxJS.array && approxJS.array.length === 4) {
75
+ largestPoly = approxJS.array;
76
+ break; // Stop at the first 4-point polygon like python script
77
+ }
78
+ }
79
+ const logMsg = `[OpenCV] Contours: ${contoursSize}. Metrics pass: ${contourMetrics.length}. Poly detect: ${largestPoly ? 'Thành công' : 'Thất bại'}.`;
80
+ console.log(logMsg);
81
+ if (onLog)
82
+ onLog(logMsg);
65
83
  if (largestPoly && largestPoly.length === 4) {
66
84
  return this.sortCorners(largestPoly);
67
85
  }
@@ -69,16 +87,15 @@ class DocumentScanner {
69
87
  }
70
88
  catch (e) {
71
89
  console.error('Lỗi khi dò tìm góc tài liệu (OpenCV):', e);
72
- return undefined;
90
+ if (onLog)
91
+ onLog(`[OpenCV Corner Detection Error]: ${e.message}`);
92
+ throw new Error(`[OpenCV Corner Detection Error]: ${e.message}`);
73
93
  }
74
94
  finally {
75
95
  react_native_fast_opencv_1.OpenCV.clearBuffers();
76
96
  }
77
97
  }
78
- /**
79
- * Bước 2: Perspective Correction
80
- */
81
- static applyPerspectiveCorrection(imageBase64, corners) {
98
+ static applyPerspectiveCorrection(imageBase64, corners, onLog) {
82
99
  let src = null;
83
100
  let dst = null;
84
101
  try {
@@ -108,13 +125,21 @@ class DocumentScanner {
108
125
  react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2f, maxWidth - 1, maxHeight - 1),
109
126
  react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2f, 0, maxHeight - 1)
110
127
  ]);
111
- const perspectiveMatrix = react_native_fast_opencv_1.OpenCV.invoke('getPerspectiveTransform', srcPoints, dstPoints);
128
+ const perspectiveMatrix = react_native_fast_opencv_1.OpenCV.invoke('getPerspectiveTransform', srcPoints, dstPoints, 0);
112
129
  const size = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, maxWidth, maxHeight);
113
- react_native_fast_opencv_1.OpenCV.invoke('warpPerspective', src, dst, perspectiveMatrix, size);
114
- return react_native_fast_opencv_1.OpenCV.invoke('toBase64', dst);
130
+ const borderValue = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Scalar, 0);
131
+ react_native_fast_opencv_1.OpenCV.invoke('warpPerspective', src, dst, perspectiveMatrix, size, 1 /* INTER_LINEAR */, 0 /* BORDER_CONSTANT */, borderValue);
132
+ const dstValue = react_native_fast_opencv_1.OpenCV.toJSValue(dst);
133
+ // Fix "writeFile got an object" by guaranteeing string type
134
+ if (dstValue && dstValue.base64) {
135
+ return typeof dstValue.base64 === 'string' ? dstValue.base64 : String(dstValue.base64);
136
+ }
137
+ return undefined;
115
138
  }
116
139
  catch (e) {
117
140
  console.error('Lỗi khi bóp phối cảnh tài liệu (OpenCV):', e);
141
+ if (onLog)
142
+ onLog(`[OpenCV Perspective Correction Error]: ${e.message || e}`);
118
143
  return undefined;
119
144
  }
120
145
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-opencv-doc-perspective-correction",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "A React Native library for document corner detection and perspective correction using react-native-fast-opencv",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -4,16 +4,10 @@ import { OpenCV, OpenCVMat, ObjectType, DataTypes, ColorConversionCodes } from '
4
4
  export type Point = { x: number; y: number };
5
5
 
6
6
  export class DocumentScanner {
7
- /**
8
- * Tính khoảng cách Euclidean giữa 2 điểm
9
- */
10
7
  private static getDistance(p1: Point, p2: Point) {
11
8
  return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
12
9
  }
13
10
 
14
- /**
15
- * Sắp xếp 4 điểm thành chuỗi TL, TR, BR, BL
16
- */
17
11
  private static sortCorners(corners: Point[]): Point[] {
18
12
  if (corners.length !== 4) return corners;
19
13
 
@@ -29,10 +23,7 @@ export class DocumentScanner {
29
23
  });
30
24
  }
31
25
 
32
- /**
33
- * Bước 1: Page Corner Detection (Auto-detect góc tài liệu)
34
- */
35
- public static detectPageCorners(imageBase64: string): Point[] | undefined {
26
+ public static detectPageCorners(imageBase64: string, onLog?: (msg: string) => void): Point[] | undefined {
36
27
  let src: OpenCVMat | null = null;
37
28
  let gray: OpenCVMat | null = null;
38
29
  let blurred: OpenCVMat | null = null;
@@ -44,57 +35,85 @@ export class DocumentScanner {
44
35
  src = OpenCV.base64ToMat(imageBase64);
45
36
  gray = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
46
37
  blurred = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
47
- edges = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
38
+ edges = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U); // This will hold the threshold output
48
39
 
49
40
  OpenCV.invoke('cvtColor', src, gray, ColorConversionCodes.COLOR_BGR2GRAY);
50
41
 
51
42
  const ksize = OpenCV.createObject(ObjectType.Size, 5, 5);
52
43
  OpenCV.invoke('GaussianBlur', gray, blurred, ksize, 0);
53
44
 
54
- OpenCV.invoke('Canny', blurred, edges, 75, 200, 3, false);
45
+ // Python uses: cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
46
+ // THRESH_BINARY = 0
47
+ // THRESH_OTSU = 8
48
+ // THRESH_BINARY + THRESH_OTSU = 8
49
+ OpenCV.invoke('threshold', blurred, edges, 0, 255, 8);
55
50
 
56
51
  contoursObj = OpenCV.createObject(ObjectType.MatVector);
57
52
  hierarchyObj = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
58
53
 
59
- OpenCV.invoke('findContours', edges, contoursObj, hierarchyObj, 1, 2);
54
+ // Using RETR_EXTERNAL similar to the Python script for outer contours
55
+ OpenCV.invoke('findContoursWithHierarchy', edges, contoursObj, hierarchyObj, 0 /* RETR_EXTERNAL */, 2 /* CHAIN_APPROX_SIMPLE */);
60
56
 
61
- const contoursSize = OpenCV.invoke('size', contoursObj) || 0;
62
- let maxArea = 0;
63
- let largestPoly: Point[] | undefined = undefined;
57
+ const contoursJS = OpenCV.toJSValue(contoursObj);
58
+ const contoursArray = contoursJS?.array || [];
59
+ const contoursSize = contoursArray.length;
60
+
61
+ if (contoursSize === 0) {
62
+ if (onLog) onLog(`[OpenCV] Không tìm thấy contours.`);
63
+ return undefined;
64
+ }
64
65
 
66
+ // First pass: extract all areas to sort them and minimize JSI calls
67
+ let contourMetrics = [];
65
68
  for (let i = 0; i < contoursSize; i++) {
66
- const contour = OpenCV.invoke('get', contoursObj, i);
67
- const area = OpenCV.invoke('contourArea', contour);
68
-
69
- if (area > maxArea) {
70
- const peri = OpenCV.invoke('arcLength', contour, true);
71
- const approx = OpenCV.createObject(ObjectType.PointVector);
72
- OpenCV.invoke('approxPolyDP', contour, approx, 0.02 * peri, true);
73
-
74
- const approxJS = OpenCV.toJSValue(approx);
75
- if (approxJS && approxJS.array && approxJS.array.length === 4) {
76
- maxArea = area;
77
- largestPoly = approxJS.array as Point[];
78
- }
69
+ const contour = OpenCV.copyObjectFromVector(contoursObj, i);
70
+ const areaObj = OpenCV.invoke('contourArea', contour);
71
+ const area = areaObj ? areaObj.value : 0;
72
+ if (area > 5000) { // filter very small artifacts
73
+ contourMetrics.push({ index: i, area, contour });
79
74
  }
80
75
  }
81
76
 
77
+ // Sort contours by area in descending order
78
+ contourMetrics.sort((a, b) => b.area - a.area);
79
+
80
+ let largestPoly: Point[] | undefined = undefined;
81
+
82
+ // Second pass: only check approxPolyDP for the largest ones
83
+ for (let i = 0; i < contourMetrics.length; i++) {
84
+ const metric = contourMetrics[i];
85
+ const contour = metric.contour;
86
+
87
+ const periObj = OpenCV.invoke('arcLength', contour, true);
88
+ const peri = periObj ? periObj.value : 0;
89
+ const approx = OpenCV.createObject(ObjectType.PointVector);
90
+ OpenCV.invoke('approxPolyDP', contour, approx, 0.02 * peri, true);
91
+
92
+ const approxJS = OpenCV.toJSValue(approx);
93
+ if (approxJS && approxJS.array && approxJS.array.length === 4) {
94
+ largestPoly = approxJS.array as Point[];
95
+ break; // Stop at the first 4-point polygon like python script
96
+ }
97
+ }
98
+
99
+ const logMsg = `[OpenCV] Contours: ${contoursSize}. Metrics pass: ${contourMetrics.length}. Poly detect: ${largestPoly ? 'Thành công' : 'Thất bại'}.`;
100
+ console.log(logMsg);
101
+ if (onLog) onLog(logMsg);
102
+
82
103
  if (largestPoly && largestPoly.length === 4) {
83
104
  return this.sortCorners(largestPoly);
84
105
  }
85
106
  return undefined;
86
- } catch (e) {
107
+ } catch (e: any) {
87
108
  console.error('Lỗi khi dò tìm góc tài liệu (OpenCV):', e);
88
- return undefined;
109
+ if (onLog) onLog(`[OpenCV Corner Detection Error]: ${e.message}`);
110
+ throw new Error(`[OpenCV Corner Detection Error]: ${e.message}`);
89
111
  } finally {
90
112
  OpenCV.clearBuffers();
91
113
  }
92
114
  }
93
115
 
94
- /**
95
- * Bước 2: Perspective Correction
96
- */
97
- public static applyPerspectiveCorrection(imageBase64: string, corners: Point[]): string | undefined {
116
+ public static applyPerspectiveCorrection(imageBase64: string, corners: Point[], onLog?: (msg: string) => void): string | undefined {
98
117
  let src: OpenCVMat | null = null;
99
118
  let dst: OpenCVMat | null = null;
100
119
 
@@ -134,17 +153,27 @@ export class DocumentScanner {
134
153
  ]
135
154
  );
136
155
 
137
- const perspectiveMatrix = OpenCV.invoke('getPerspectiveTransform', srcPoints, dstPoints);
156
+ const perspectiveMatrix = OpenCV.invoke('getPerspectiveTransform', srcPoints, dstPoints, 0);
138
157
 
139
158
  const size = OpenCV.createObject(ObjectType.Size, maxWidth, maxHeight);
140
- OpenCV.invoke('warpPerspective', src, dst, perspectiveMatrix, size);
159
+ const borderValue = OpenCV.createObject(ObjectType.Scalar, 0);
160
+
161
+ OpenCV.invoke('warpPerspective', src, dst, perspectiveMatrix, size, 1 /* INTER_LINEAR */, 0 /* BORDER_CONSTANT */, borderValue);
141
162
 
142
- return OpenCV.invoke('toBase64', dst);
163
+ const dstValue = OpenCV.toJSValue(dst);
164
+
165
+ // Fix "writeFile got an object" by guaranteeing string type
166
+ if (dstValue && dstValue.base64) {
167
+ return typeof dstValue.base64 === 'string' ? dstValue.base64 : String(dstValue.base64);
168
+ }
169
+ return undefined;
143
170
  } catch (e) {
144
171
  console.error('Lỗi khi bóp phối cảnh tài liệu (OpenCV):', e);
172
+ if (onLog) onLog(`[OpenCV Perspective Correction Error]: ${(e as Error).message || e}`);
145
173
  return undefined;
146
174
  } finally {
147
175
  OpenCV.clearBuffers();
148
176
  }
149
177
  }
150
178
  }
179
+