rn-opencv-doc-perspective-correction 1.0.13 → 1.0.15

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.
Files changed (3) hide show
  1. package/dist/index.js +45 -43
  2. package/package.json +1 -1
  3. package/src/index.ts +74 -71
package/dist/index.js CHANGED
@@ -19,73 +19,70 @@ class DocumentScanner {
19
19
  }
20
20
  static detectPageCorners(imageBase64) {
21
21
  let src = null;
22
+ let resized = null;
22
23
  let gray = null;
23
24
  let blurred = null;
25
+ let closed = null;
24
26
  let edges = null;
27
+ let kernelDims = null;
25
28
  let contoursObj = null;
26
29
  let hierarchyObj = null;
27
30
  try {
28
31
  src = react_native_fast_opencv_1.OpenCV.base64ToMat(imageBase64);
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);
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);
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);
32
- // BƯỚC 1: Chuyển sang ảnh xám (Grayscale)
33
- react_native_fast_opencv_1.OpenCV.invoke('cvtColor', src, gray, react_native_fast_opencv_1.ColorConversionCodes.COLOR_BGR2GRAY);
34
- // BƯỚC 2: Làm mờ để loại bỏ nhiễu hạt nhỏ (Gaussian Blur)
35
- const ksize = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 5, 5);
36
- react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', gray, blurred, ksize, 0);
37
- // BƯỚC 3: Đo sáng môi trường để thiết lập Canny thông minh (Auto-Canny)
38
- // Lợi dụng Otsu Threshold để dò ra "Ngưỡng sáng sinh thái" của bức ảnh đó
39
- const dummyEdges = 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
- const otsuResult = react_native_fast_opencv_1.OpenCV.invoke('threshold', blurred, dummyEdges, 0, 255, 8 /* THRESH_BINARY | THRESH_OTSU */);
41
- // Xử lý cẩn thận kiểu dữ liệu trả về từ Bridge (Primitive vs Object)
42
- let highThresh = 200;
43
- if (otsuResult !== undefined && otsuResult !== null) {
44
- highThresh = typeof otsuResult === 'number' ? otsuResult : (otsuResult.value || 200);
45
- }
46
- let lowThresh = highThresh * 0.5;
47
- // BƯỚC 4: Dò tìm cạnh viền Canny với cảm biến tự động
48
- react_native_fast_opencv_1.OpenCV.invoke('Canny', blurred, edges, lowThresh, highThresh);
49
- // BƯỚC 5: Liền sẹo nét viền (Morphology Dilate)
50
- // Giúp nối liền các đứt gãy do bóng đổ chia cắt nét viền
51
- const dilatedEdges = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8U);
32
+ const jsSrc = react_native_fast_opencv_1.OpenCV.toJSValue(src);
33
+ const origCols = jsSrc.cols || 1000;
34
+ const origRows = jsSrc.rows || 1000;
35
+ const targetHeight = 500.0;
36
+ const ratio = origRows / targetHeight;
37
+ const targetWidth = Math.max(1, Math.round(origCols / ratio));
38
+ resized = 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
+ const dstSize = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, targetWidth, targetHeight);
52
40
  try {
53
- const emptyKernel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8U);
54
- react_native_fast_opencv_1.OpenCV.invoke('dilate', edges, dilatedEdges, emptyKernel);
41
+ react_native_fast_opencv_1.OpenCV.invoke('resize', src, resized, dstSize, 0, 0, 1 /* INTER_LINEAR */);
55
42
  }
56
- catch (dilateErr) {
57
- react_native_fast_opencv_1.OpenCV.invoke('copyTo', edges, dilatedEdges); // Fallback an toàn nếu dilate tàng hình fail
43
+ catch (err) {
44
+ resized = src; // Fallback an toàn
58
45
  }
59
- // BƯỚC 6: Tìm khối đa giác liên kết (Contours)
46
+ // BƯỚC 1: Chuyển sang ảnh xám (Grayscale)
47
+ 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);
48
+ react_native_fast_opencv_1.OpenCV.invoke('cvtColor', resized, gray, react_native_fast_opencv_1.ColorConversionCodes.COLOR_BGR2GRAY);
49
+ // BƯỚC 2: Làm mờ để loại bỏ nhiễu hạt (Gaussian Blur 7x7)
50
+ 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);
51
+ const ksize = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 7, 7);
52
+ react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', gray, blurred, ksize, 0);
53
+ // BƯỚC 3: Dùng hình thái học lấp các chữ/chi tiết trên CCCD bằng MORPH_CLOSE
54
+ // Giúp mảng CCCD thành khối liền (mặt chữ lặn đi), Canny sẽ bắt vòng viền chuẩn xác
55
+ closed = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8U);
56
+ const kernelSize = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 9, 9);
57
+ kernelDims = react_native_fast_opencv_1.OpenCV.invoke('getStructuringElement', 0 /* MORPH_RECT */, kernelSize);
58
+ react_native_fast_opencv_1.OpenCV.invoke('morphologyEx', blurred, closed, 3 /* MORPH_CLOSE */, kernelDims);
59
+ // BƯỚC 4: Dò tìm cạnh viền Canny
60
+ // Không cần Canny-Auto nữa vì MORPH_CLOSE đã dọn dẹp mặt thẻ quá mượt.
61
+ 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);
62
+ react_native_fast_opencv_1.OpenCV.invoke('Canny', closed, edges, 0, 84);
63
+ // BƯỚC 5: Tìm khối đa giác liên kết (Contours)
64
+ // Lệnh bắt External sẽ hoạt động hoàn hảo khi Canny chỉ thấy 1 nét vành ngoài.
60
65
  contoursObj = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.MatVector);
61
66
  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);
62
- // BẮT BUỘC DÙNG RETR_EXTERNAL để ngăn OOM (Out of Memory):
63
- // Canny làm lộ ra TẤT CẢ mép bao gồm cả hàng trăm chữ cái trên CCCD.
64
- // Nếu dùng RETR_LIST, JS Bridge sẽ phải vòng lặp cấp phát 5000 Mats gây Crash App ngay lập tức!
65
- // RETR_EXTERNAL với Canny sẽ chỉ bắt Vòng khép kín ngoài cùng (Chính xác là mép CCCD).
66
- react_native_fast_opencv_1.OpenCV.invoke('findContoursWithHierarchy', dilatedEdges, contoursObj, hierarchyObj, 0 /* RETR_EXTERNAL */, 2 /* CHAIN_APPROX_SIMPLE */);
67
+ react_native_fast_opencv_1.OpenCV.invoke('findContoursWithHierarchy', edges, contoursObj, hierarchyObj, 0 /* RETR_EXTERNAL */, 2 /* CHAIN_APPROX_SIMPLE */);
67
68
  const contoursJS = react_native_fast_opencv_1.OpenCV.toJSValue(contoursObj);
68
69
  const contoursArray = (contoursJS === null || contoursJS === void 0 ? void 0 : contoursJS.array) || [];
69
70
  const contoursSize = contoursArray.length;
70
71
  if (contoursSize === 0) {
71
72
  return undefined;
72
73
  }
73
- // Thu thập và đo đạc diện tích
74
74
  let contourMetrics = [];
75
75
  for (let i = 0; i < contoursSize; i++) {
76
76
  const contour = react_native_fast_opencv_1.OpenCV.copyObjectFromVector(contoursObj, i);
77
77
  const areaObj = react_native_fast_opencv_1.OpenCV.invoke('contourArea', contour);
78
78
  const area = areaObj ? areaObj.value : 0;
79
- // Mở rộng bộ lọc diện tích: Loại bỏ đi vùng nhiễu (5000 khoảng diện tích tối thiểu cho giấy tờ)
80
- if (area > 5000) {
79
+ // Mở rộng bộ lọc diện tích: Loại bỏ đi vùng nhiễu vụn vặt (< 5% diện tích mặt quét)
80
+ if (area > (targetWidth * targetHeight * 0.05)) {
81
81
  contourMetrics.push({ index: i, area, contour });
82
82
  }
83
83
  }
84
- // Sắp xếp diện tích lớn nhất lên đầu (Ưu tiên thẻ bài đang chiếm lớn nhất khung hình)
85
84
  contourMetrics.sort((a, b) => b.area - a.area);
86
85
  let largestPoly = undefined;
87
- // BƯỚC 7: Thẩm định hình học
88
- // Quét qua max 5 contour lớn nhất để tiết kiệm thao tác mảng
89
86
  const maxChecks = Math.min(contourMetrics.length, 5);
90
87
  for (let i = 0; i < maxChecks; i++) {
91
88
  const metric = contourMetrics[i];
@@ -93,12 +90,11 @@ class DocumentScanner {
93
90
  const periObj = react_native_fast_opencv_1.OpenCV.invoke('arcLength', contour, true);
94
91
  const peri = periObj ? (periObj.value || 0) : 0;
95
92
  const approx = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.PointVector);
96
- // Tính nội suy đa giác (Epsilon = 2% của chu vi)
93
+ // Nội suy (approx) giảm bớt gấp khúc với độ dung sai epsilon 0.02 chu vi
97
94
  react_native_fast_opencv_1.OpenCV.invoke('approxPolyDP', contour, approx, 0.02 * peri, true);
98
95
  const approxJS = react_native_fast_opencv_1.OpenCV.toJSValue(approx);
99
- // Nếu quy chiếu thành công ra đúng 4 góc
96
+ // Nếu quy chiếu thành công ra đúng 4 đỉnh
100
97
  if (approxJS && approxJS.array && approxJS.array.length === 4) {
101
- // Thẩm định: Phải là đa giác lồi Convex (Loại bóng râm)
102
98
  try {
103
99
  const isConvex = react_native_fast_opencv_1.OpenCV.invoke('isContourConvex', approx);
104
100
  const convexValue = (typeof isConvex === 'object' && isConvex !== null) ? isConvex.value : isConvex;
@@ -111,8 +107,14 @@ class DocumentScanner {
111
107
  break;
112
108
  }
113
109
  }
110
+ // BƯỚC 6: Trả về góc và phóng trả lại tỷ lệ kích thước cũ
114
111
  if (largestPoly && largestPoly.length === 4) {
115
- return this.sortCorners(largestPoly);
112
+ const actualRatio = (resized === src) ? 1.0 : ratio;
113
+ const originalCorners = largestPoly.map(p => ({
114
+ x: Math.round(p.x * actualRatio),
115
+ y: Math.round(p.y * actualRatio)
116
+ }));
117
+ return this.sortCorners(originalCorners);
116
118
  }
117
119
  return undefined;
118
120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-opencv-doc-perspective-correction",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
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
@@ -32,59 +32,61 @@ export class DocumentScanner {
32
32
 
33
33
  public static detectPageCorners(imageBase64: string): Point[] | undefined {
34
34
  let src: OpenCVMat | null = null;
35
+ let resized: OpenCVMat | null = null;
35
36
  let gray: OpenCVMat | null = null;
36
37
  let blurred: OpenCVMat | null = null;
38
+ let closed: OpenCVMat | null = null;
37
39
  let edges: OpenCVMat | null = null;
40
+ let kernelDims: any = null;
38
41
  let contoursObj: any = null;
39
42
  let hierarchyObj: any = null;
40
43
 
41
44
  try {
42
45
  src = OpenCV.base64ToMat(imageBase64);
43
- gray = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
44
- blurred = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
45
- edges = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
46
-
47
- // BƯỚC 1: Chuyển sang ảnh xám (Grayscale)
48
- OpenCV.invoke('cvtColor', src, gray, ColorConversionCodes.COLOR_BGR2GRAY);
46
+
47
+ const jsSrc = OpenCV.toJSValue(src);
48
+ const origCols = jsSrc.cols || 1000;
49
+ const origRows = jsSrc.rows || 1000;
49
50
 
50
- // BƯỚC 2: Làm mờ để loại bỏ nhiễu hạt nhỏ (Gaussian Blur)
51
- const ksize = OpenCV.createObject(ObjectType.Size, 5, 5);
52
- OpenCV.invoke('GaussianBlur', gray, blurred, ksize, 0);
51
+ const targetHeight = 500.0;
52
+ const ratio = origRows / targetHeight;
53
+ const targetWidth = Math.max(1, Math.round(origCols / ratio));
53
54
 
54
- // BƯỚC 3: Đo sáng môi trường để thiết lập Canny thông minh (Auto-Canny)
55
- // Lợi dụng Otsu Threshold để dò ra "Ngưỡng sáng sinh thái" của bức ảnh đó
56
- const dummyEdges = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
57
- const otsuResult = OpenCV.invoke('threshold', blurred, dummyEdges, 0, 255, 8 /* THRESH_BINARY | THRESH_OTSU */);
55
+ resized = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
56
+ const dstSize = OpenCV.createObject(ObjectType.Size, targetWidth, targetHeight);
58
57
 
59
- // Xử lý cẩn thận kiểu dữ liệu trả về từ Bridge (Primitive vs Object)
60
- let highThresh = 200;
61
- if (otsuResult !== undefined && otsuResult !== null) {
62
- highThresh = typeof otsuResult === 'number' ? otsuResult : (otsuResult.value || 200);
58
+ try {
59
+ OpenCV.invoke('resize', src, resized, dstSize, 0, 0, 1 /* INTER_LINEAR */);
60
+ } catch (err) {
61
+ resized = src; // Fallback an toàn
63
62
  }
64
- let lowThresh = highThresh * 0.5;
65
63
 
66
- // BƯỚC 4: tìm cạnh viền Canny với cảm biến tự động
67
- OpenCV.invoke('Canny', blurred, edges, lowThresh, highThresh);
64
+ // BƯỚC 1: Chuyển sang ảnh xám (Grayscale)
65
+ gray = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
66
+ OpenCV.invoke('cvtColor', resized, gray, ColorConversionCodes.COLOR_BGR2GRAY);
68
67
 
69
- // BƯỚC 5: Liền sẹo nét viền (Morphology Dilate)
70
- // Giúp nối liền các đứt gãy do bóng đổ chia cắt nét viền
71
- const dilatedEdges = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
72
- try {
73
- const emptyKernel = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
74
- OpenCV.invoke('dilate', edges, dilatedEdges, emptyKernel);
75
- } catch (dilateErr) {
76
- OpenCV.invoke('copyTo', edges, dilatedEdges); // Fallback an toàn nếu dilate tàng hình fail
77
- }
68
+ // BƯỚC 2: Làm mờ để loại bỏ nhiễu hạt (Gaussian Blur 7x7)
69
+ blurred = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
70
+ const ksize = OpenCV.createObject(ObjectType.Size, 7, 7);
71
+ OpenCV.invoke('GaussianBlur', gray, blurred, ksize, 0);
72
+
73
+ // BƯỚC 3: Dùng hình thái học lấp các chữ/chi tiết trên CCCD bằng MORPH_CLOSE
74
+ // Giúp mảng CCCD thành khối liền (mặt chữ lặn đi), Canny sẽ bắt vòng viền chuẩn xác
75
+ closed = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
76
+ const kernelSize = OpenCV.createObject(ObjectType.Size, 9, 9);
77
+ kernelDims = OpenCV.invoke('getStructuringElement', 0 /* MORPH_RECT */, kernelSize);
78
+ OpenCV.invoke('morphologyEx', blurred, closed, 3 /* MORPH_CLOSE */, kernelDims);
79
+
80
+ // BƯỚC 4: Dò tìm cạnh viền Canny
81
+ // Không cần Canny-Auto nữa vì MORPH_CLOSE đã dọn dẹp mặt thẻ quá mượt.
82
+ edges = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
83
+ OpenCV.invoke('Canny', closed, edges, 0, 84);
78
84
 
79
- // BƯỚC 6: Tìm khối đa giác liên kết (Contours)
85
+ // BƯỚC 5: Tìm khối đa giác liên kết (Contours)
86
+ // Lệnh bắt External sẽ hoạt động hoàn hảo khi Canny chỉ thấy 1 nét vành ngoài.
80
87
  contoursObj = OpenCV.createObject(ObjectType.MatVector);
81
88
  hierarchyObj = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8U);
82
-
83
- // BẮT BUỘC DÙNG RETR_EXTERNAL để ngăn OOM (Out of Memory):
84
- // Canny làm lộ ra TẤT CẢ mép bao gồm cả hàng trăm chữ cái trên CCCD.
85
- // Nếu dùng RETR_LIST, JS Bridge sẽ phải vòng lặp cấp phát 5000 Mats gây Crash App ngay lập tức!
86
- // RETR_EXTERNAL với Canny sẽ chỉ bắt Vòng khép kín ngoài cùng (Chính xác là mép CCCD).
87
- OpenCV.invoke('findContoursWithHierarchy', dilatedEdges, contoursObj, hierarchyObj, 0 /* RETR_EXTERNAL */, 2 /* CHAIN_APPROX_SIMPLE */);
89
+ OpenCV.invoke('findContoursWithHierarchy', edges, contoursObj, hierarchyObj, 0 /* RETR_EXTERNAL */, 2 /* CHAIN_APPROX_SIMPLE */);
88
90
 
89
91
  const contoursJS = OpenCV.toJSValue(contoursObj);
90
92
  const contoursArray = contoursJS?.array || [];
@@ -94,59 +96,60 @@ export class DocumentScanner {
94
96
  return undefined;
95
97
  }
96
98
 
97
- // Thu thập và đo đạc diện tích
98
99
  let contourMetrics = [];
99
100
  for (let i = 0; i < contoursSize; i++) {
100
101
  const contour = OpenCV.copyObjectFromVector(contoursObj, i);
101
102
  const areaObj = OpenCV.invoke('contourArea', contour);
102
103
  const area = areaObj ? areaObj.value : 0;
103
104
 
104
- // Mở rộng bộ lọc diện tích: Loại bỏ đi vùng nhiễu (5000 khoảng diện tích tối thiểu cho giấy tờ)
105
- if (area > 5000) {
105
+ // Mở rộng bộ lọc diện tích: Loại bỏ đi vùng nhiễu vụn vặt (< 5% diện tích mặt quét)
106
+ if (area > (targetWidth * targetHeight * 0.05)) {
106
107
  contourMetrics.push({ index: i, area, contour });
107
108
  }
108
109
  }
109
110
 
110
- // Sắp xếp diện tích lớn nhất lên đầu (Ưu tiên thẻ bài đang chiếm lớn nhất khung hình)
111
111
  contourMetrics.sort((a, b) => b.area - a.area);
112
112
 
113
113
  let largestPoly: Point[] | undefined = undefined;
114
114
 
115
- // BƯỚC 7: Thẩm định hình học
116
- // Quét qua max 5 contour lớn nhất để tiết kiệm thao tác mảng
117
- const maxChecks = Math.min(contourMetrics.length, 5);
118
- for (let i = 0; i < maxChecks; i++) {
119
- const metric = contourMetrics[i];
120
- const contour = metric.contour;
121
-
122
- const periObj = OpenCV.invoke('arcLength', contour, true);
123
- const peri = periObj ? (periObj.value || 0) : 0;
124
- const approx = OpenCV.createObject(ObjectType.PointVector);
125
-
126
- // Tính nội suy đa giác (Epsilon = 2% của chu vi)
127
- OpenCV.invoke('approxPolyDP', contour, approx, 0.02 * peri, true);
128
-
129
- const approxJS = OpenCV.toJSValue(approx);
115
+ const maxChecks = Math.min(contourMetrics.length, 5);
116
+ for (let i = 0; i < maxChecks; i++) {
117
+ const metric = contourMetrics[i];
118
+ const contour = metric.contour;
119
+
120
+ const periObj = OpenCV.invoke('arcLength', contour, true);
121
+ const peri = periObj ? (periObj.value || 0) : 0;
122
+ const approx = OpenCV.createObject(ObjectType.PointVector);
123
+
124
+ // Nội suy (approx) giảm bớt gấp khúc với độ dung sai epsilon 0.02 chu vi
125
+ OpenCV.invoke('approxPolyDP', contour, approx, 0.02 * peri, true);
126
+
127
+ const approxJS = OpenCV.toJSValue(approx);
128
+
129
+ // Nếu quy chiếu thành công ra đúng 4 đỉnh
130
+ if (approxJS && approxJS.array && approxJS.array.length === 4) {
130
131
 
131
- // Nếu quy chiếu thành công ra đúng 4 góc
132
- if (approxJS && approxJS.array && approxJS.array.length === 4) {
133
-
134
- // Thẩm định: Phải là đa giác lồi Convex (Loại bóng râm)
135
- try {
136
- const isConvex = OpenCV.invoke('isContourConvex', approx);
137
- const convexValue = (typeof isConvex === 'object' && isConvex !== null) ? isConvex.value : isConvex;
138
- if (convexValue === false) {
139
- continue;
140
- }
141
- } catch (convexErr) {}
142
-
143
- largestPoly = approxJS.array as Point[];
144
- break;
145
- }
132
+ try {
133
+ const isConvex = OpenCV.invoke('isContourConvex', approx);
134
+ const convexValue = (typeof isConvex === 'object' && isConvex !== null) ? isConvex.value : isConvex;
135
+ if (convexValue === false) {
136
+ continue;
137
+ }
138
+ } catch (convexErr) {}
139
+
140
+ largestPoly = approxJS.array as Point[];
141
+ break;
146
142
  }
143
+ }
147
144
 
145
+ // BƯỚC 6: Trả về góc và phóng trả lại tỷ lệ kích thước cũ
148
146
  if (largestPoly && largestPoly.length === 4) {
149
- return this.sortCorners(largestPoly);
147
+ const actualRatio = (resized === src) ? 1.0 : ratio;
148
+ const originalCorners = largestPoly.map(p => ({
149
+ x: Math.round(p.x * actualRatio),
150
+ y: Math.round(p.y * actualRatio)
151
+ }));
152
+ return this.sortCorners(originalCorners);
150
153
  }
151
154
  return undefined;
152
155
  } catch (e: any) {