rn-opencv-doc-perspective-correction 1.0.5 → 1.0.7

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,12 @@ 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
8
  static detectPageCorners(imageBase64: string, onLog?: (msg: string) => void): Point[] | undefined;
9
+ static applyPerspectiveCorrection(imageBase64: string, corners: Point[], onLog?: (msg: string) => void): string | undefined;
18
10
  /**
19
- * Bước 2: Perspective Correction
11
+ * Xoay ảnh 90, -90 hoặc 180 độ
20
12
  */
21
- static applyPerspectiveCorrection(imageBase64: string, corners: Point[], onLog?: (msg: string) => void): string | undefined;
13
+ static rotateImage(imageBase64: string, angle: 90 | -90 | 180, onLog?: (msg: string) => void): string | undefined;
22
14
  }
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,9 +17,6 @@ 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
20
  static detectPageCorners(imageBase64, onLog) {
30
21
  let src = null;
31
22
  let gray = null;
@@ -37,41 +28,55 @@ 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, 50, 150);
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('findContoursWithHierarchy', edges, contoursObj, hierarchyObj, 1 /* RETR_LIST */, 2 /* CHAIN_APPROX_SIMPLE */);
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 */);
48
44
  const contoursJS = react_native_fast_opencv_1.OpenCV.toJSValue(contoursObj);
49
45
  const contoursArray = (contoursJS === null || contoursJS === void 0 ? void 0 : contoursJS.array) || [];
50
46
  const contoursSize = contoursArray.length;
51
- let maxArea = 0;
52
- let largestPoly = undefined;
53
- let foundContoursCount = 0;
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 = [];
54
54
  for (let i = 0; i < contoursSize; i++) {
55
55
  const contour = react_native_fast_opencv_1.OpenCV.copyObjectFromVector(contoursObj, i);
56
56
  const areaObj = react_native_fast_opencv_1.OpenCV.invoke('contourArea', contour);
57
57
  const area = areaObj ? areaObj.value : 0;
58
- if (area > 10000) { // Lọc bỏ các contour quá nhỏ
59
- foundContoursCount++;
58
+ if (area > 5000) { // filter very small artifacts
59
+ contourMetrics.push({ index: i, area, contour });
60
60
  }
61
- if (area > maxArea && area > 10000) {
62
- const periObj = react_native_fast_opencv_1.OpenCV.invoke('arcLength', contour, true);
63
- const peri = periObj ? periObj.value : 0;
64
- const approx = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVector);
65
- // Tăng eplison lên một chút để cho phép viền mấp mô hơn
66
- react_native_fast_opencv_1.OpenCV.invoke('approxPolyDP', contour, approx, 0.04 * peri, true);
67
- const approxJS = react_native_fast_opencv_1.OpenCV.toJSValue(approx);
68
- if (approxJS && approxJS.array && approxJS.array.length === 4) {
69
- maxArea = area;
70
- largestPoly = approxJS.array;
71
- }
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
72
77
  }
73
78
  }
74
- const logMsg = `[OpenCV] Đã tìm thấy ${contoursSize} contours. Số contour đủ lớn: ${foundContoursCount}`;
79
+ const logMsg = `[OpenCV] Contours: ${contoursSize}. Metrics pass: ${contourMetrics.length}. Poly detect: ${largestPoly ? 'Thành công' : 'Thất bại'}.`;
75
80
  console.log(logMsg);
76
81
  if (onLog)
77
82
  onLog(logMsg);
@@ -90,9 +95,6 @@ class DocumentScanner {
90
95
  react_native_fast_opencv_1.OpenCV.clearBuffers();
91
96
  }
92
97
  }
93
- /**
94
- * Bước 2: Perspective Correction
95
- */
96
98
  static applyPerspectiveCorrection(imageBase64, corners, onLog) {
97
99
  let src = null;
98
100
  let dst = null;
@@ -127,7 +129,12 @@ class DocumentScanner {
127
129
  const size = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, maxWidth, maxHeight);
128
130
  const borderValue = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Scalar, 0);
129
131
  react_native_fast_opencv_1.OpenCV.invoke('warpPerspective', src, dst, perspectiveMatrix, size, 1 /* INTER_LINEAR */, 0 /* BORDER_CONSTANT */, borderValue);
130
- return react_native_fast_opencv_1.OpenCV.invoke('toBase64', dst);
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;
131
138
  }
132
139
  catch (e) {
133
140
  console.error('Lỗi khi bóp phối cảnh tài liệu (OpenCV):', e);
@@ -139,5 +146,36 @@ class DocumentScanner {
139
146
  react_native_fast_opencv_1.OpenCV.clearBuffers();
140
147
  }
141
148
  }
149
+ /**
150
+ * Xoay ảnh 90, -90 hoặc 180 độ
151
+ */
152
+ static rotateImage(imageBase64, angle, onLog) {
153
+ let src = null;
154
+ let dst = null;
155
+ try {
156
+ src = react_native_fast_opencv_1.OpenCV.base64ToMat(imageBase64);
157
+ dst = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8U);
158
+ let rotateCode = 0; // ROTATE_90_CLOCKWISE
159
+ if (angle === -90)
160
+ rotateCode = 2; // ROTATE_90_COUNTERCLOCKWISE
161
+ else if (angle === 180)
162
+ rotateCode = 1; // ROTATE_180
163
+ react_native_fast_opencv_1.OpenCV.invoke('rotate', src, dst, rotateCode);
164
+ const dstValue = react_native_fast_opencv_1.OpenCV.toJSValue(dst);
165
+ if (dstValue && dstValue.base64) {
166
+ return typeof dstValue.base64 === 'string' ? dstValue.base64 : String(dstValue.base64);
167
+ }
168
+ return undefined;
169
+ }
170
+ catch (e) {
171
+ console.error('Lỗi khi xoay ảnh (OpenCV):', e);
172
+ if (onLog)
173
+ onLog(`[OpenCV Rotate Error]: ${e.message}`);
174
+ return undefined;
175
+ }
176
+ finally {
177
+ react_native_fast_opencv_1.OpenCV.clearBuffers();
178
+ }
179
+ }
142
180
  }
143
181
  exports.DocumentScanner = DocumentScanner;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-opencv-doc-perspective-correction",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
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,9 +23,6 @@ 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
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;
@@ -44,52 +35,68 @@ 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, 50, 150);
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('findContoursWithHierarchy', edges, contoursObj, hierarchyObj, 1 /* RETR_LIST */, 2 /* CHAIN_APPROX_SIMPLE */);
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
57
  const contoursJS = OpenCV.toJSValue(contoursObj);
62
58
  const contoursArray = contoursJS?.array || [];
63
59
  const contoursSize = contoursArray.length;
64
60
 
65
- let maxArea = 0;
66
- let largestPoly: Point[] | undefined = undefined;
67
- let foundContoursCount = 0;
61
+ if (contoursSize === 0) {
62
+ if (onLog) onLog(`[OpenCV] Không tìm thấy contours.`);
63
+ return undefined;
64
+ }
68
65
 
66
+ // First pass: extract all areas to sort them and minimize JSI calls
67
+ let contourMetrics = [];
69
68
  for (let i = 0; i < contoursSize; i++) {
70
69
  const contour = OpenCV.copyObjectFromVector(contoursObj, i);
71
70
  const areaObj = OpenCV.invoke('contourArea', contour);
72
71
  const area = areaObj ? areaObj.value : 0;
73
-
74
- if (area > 10000) { // Lọc bỏ các contour quá nhỏ
75
- foundContoursCount++;
72
+ if (area > 5000) { // filter very small artifacts
73
+ contourMetrics.push({ index: i, area, contour });
76
74
  }
75
+ }
77
76
 
78
- if (area > maxArea && area > 10000) {
79
- const periObj = OpenCV.invoke('arcLength', contour, true);
80
- const peri = periObj ? periObj.value : 0;
81
- const approx = OpenCV.createObject(ObjectType.PointVector);
82
- // Tăng eplison lên một chút để cho phép viền mấp mô hơn
83
- OpenCV.invoke('approxPolyDP', contour, approx, 0.04 * peri, true);
84
-
85
- const approxJS = OpenCV.toJSValue(approx);
86
- if (approxJS && approxJS.array && approxJS.array.length === 4) {
87
- maxArea = area;
88
- largestPoly = approxJS.array as Point[];
89
- }
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
90
96
  }
91
97
  }
92
- const logMsg = `[OpenCV] Đã tìm thấy ${contoursSize} contours. Số contour đủ lớn: ${foundContoursCount}`;
98
+
99
+ const logMsg = `[OpenCV] Contours: ${contoursSize}. Metrics pass: ${contourMetrics.length}. Poly detect: ${largestPoly ? 'Thành công' : 'Thất bại'}.`;
93
100
  console.log(logMsg);
94
101
  if (onLog) onLog(logMsg);
95
102
 
@@ -106,9 +113,6 @@ export class DocumentScanner {
106
113
  }
107
114
  }
108
115
 
109
- /**
110
- * Bước 2: Perspective Correction
111
- */
112
116
  public static applyPerspectiveCorrection(imageBase64: string, corners: Point[], onLog?: (msg: string) => void): string | undefined {
113
117
  let src: OpenCVMat | null = null;
114
118
  let dst: OpenCVMat | null = null;
@@ -156,7 +160,13 @@ export class DocumentScanner {
156
160
 
157
161
  OpenCV.invoke('warpPerspective', src, dst, perspectiveMatrix, size, 1 /* INTER_LINEAR */, 0 /* BORDER_CONSTANT */, borderValue);
158
162
 
159
- 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;
160
170
  } catch (e) {
161
171
  console.error('Lỗi khi bóp phối cảnh tài liệu (OpenCV):', e);
162
172
  if (onLog) onLog(`[OpenCV Perspective Correction Error]: ${(e as Error).message || e}`);
@@ -165,4 +175,35 @@ export class DocumentScanner {
165
175
  OpenCV.clearBuffers();
166
176
  }
167
177
  }
178
+
179
+ /**
180
+ * Xoay ảnh 90, -90 hoặc 180 độ
181
+ */
182
+ public static rotateImage(imageBase64: string, angle: 90 | -90 | 180, onLog?: (msg: string) => void): string | undefined {
183
+ let src: OpenCVMat | null = null;
184
+ let dst: OpenCVMat | null = null;
185
+ try {
186
+ src = OpenCV.base64ToMat(imageBase64);
187
+ dst = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
188
+
189
+ let rotateCode = 0; // ROTATE_90_CLOCKWISE
190
+ if (angle === -90) rotateCode = 2; // ROTATE_90_COUNTERCLOCKWISE
191
+ else if (angle === 180) rotateCode = 1; // ROTATE_180
192
+
193
+ OpenCV.invoke('rotate', src, dst, rotateCode);
194
+
195
+ const dstValue = OpenCV.toJSValue(dst);
196
+ if (dstValue && dstValue.base64) {
197
+ return typeof dstValue.base64 === 'string' ? dstValue.base64 : String(dstValue.base64);
198
+ }
199
+ return undefined;
200
+ } catch (e: any) {
201
+ console.error('Lỗi khi xoay ảnh (OpenCV):', e);
202
+ if (onLog) onLog(`[OpenCV Rotate Error]: ${e.message}`);
203
+ return undefined;
204
+ } finally {
205
+ OpenCV.clearBuffers();
206
+ }
207
+ }
168
208
  }
209
+