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.
- package/dist/index.js +45 -43
- package/package.json +1 -1
- 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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
react_native_fast_opencv_1.OpenCV.
|
|
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
|
-
|
|
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 (
|
|
57
|
-
|
|
43
|
+
catch (err) {
|
|
44
|
+
resized = src; // Fallback an toàn
|
|
58
45
|
}
|
|
59
|
-
// BƯỚC
|
|
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
|
-
|
|
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
|
|
80
|
-
if (area >
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
51
|
+
const targetHeight = 500.0;
|
|
52
|
+
const ratio = origRows / targetHeight;
|
|
53
|
+
const targetWidth = Math.max(1, Math.round(origCols / ratio));
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
67
|
-
OpenCV.
|
|
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
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
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
|
|
105
|
-
if (area >
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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) {
|