rn-opencv-doc-perspective-correction 1.0.1
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 +107 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +125 -0
- package/package.json +27 -0
- package/src/index.ts +150 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# rn-opencv-doc-perspective-correction
|
|
2
|
+
|
|
3
|
+
Thư viện React Native chuyên biệt cho **Page Corner Detection** và **Perspective Correction** — chạy hoàn toàn offline (on-device) thông qua `react-native-fast-opencv` và OpenCV C++ JSI.
|
|
4
|
+
|
|
5
|
+
## Tính năng
|
|
6
|
+
- **Page Corner Detection:** Tự động nhận diện 4 góc của tờ giấy/tài liệu trong ảnh bất kỳ.
|
|
7
|
+
- **Perspective Correction:** Bóp phẳng ảnh nghiêng dựa trên 4 góc nhận diện, xuất ra ảnh tài liệu chữ nhật hoàn hảo.
|
|
8
|
+
|
|
9
|
+
## Cài đặt
|
|
10
|
+
|
|
11
|
+
Thư viện này là wrapper của `react-native-fast-opencv`. Đảm bảo peer dependency đã được cài trong dự án chính.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Cài thư viện (local development)
|
|
15
|
+
npm install react-native-fast-opencv
|
|
16
|
+
npm install file:../rn-opencv-doc-perspective-correction
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Hoặc thêm trực tiếp vào `package.json`:
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"rn-opencv-doc-perspective-correction": "file:../rn-opencv-doc-perspective-correction",
|
|
24
|
+
"react-native-fast-opencv": "^0.4.8"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Cách sử dụng
|
|
30
|
+
|
|
31
|
+
### Luồng hoàn chỉnh (Auto-detect + Warp)
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { DocumentScanner } from 'rn-opencv-doc-perspective-correction';
|
|
35
|
+
import RNFS from 'react-native-fs';
|
|
36
|
+
|
|
37
|
+
// 1. Đọc ảnh thành Base64
|
|
38
|
+
const imageBase64 = await RNFS.readFile('/path/to/image.jpg', 'base64');
|
|
39
|
+
|
|
40
|
+
// 2. Auto-detect 4 góc giấy
|
|
41
|
+
const corners = DocumentScanner.detectPageCorners(imageBase64);
|
|
42
|
+
|
|
43
|
+
if (corners) {
|
|
44
|
+
console.log('Tìm thấy góc:', corners);
|
|
45
|
+
// corners = [
|
|
46
|
+
// { x: 45, y: 120 }, // Top-Left
|
|
47
|
+
// { x: 940, y: 100 }, // Top-Right
|
|
48
|
+
// { x: 960, y: 1280 }, // Bottom-Right
|
|
49
|
+
// { x: 30, y: 1300 }, // Bottom-Left
|
|
50
|
+
// ]
|
|
51
|
+
|
|
52
|
+
// 3. Bóp phẳng ảnh theo 4 góc tìm được (hoặc góc sau khi user chỉnh tay)
|
|
53
|
+
const scannedBase64 = DocumentScanner.applyPerspectiveCorrection(imageBase64, corners);
|
|
54
|
+
|
|
55
|
+
if (scannedBase64) {
|
|
56
|
+
// 4. Lưu lại file kết quả
|
|
57
|
+
await RNFS.writeFile('/path/to/scanned.jpg', scannedBase64, 'base64');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Kết hợp với UI tuỳ chỉnh (Bán tự động)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { DocumentScanner, Point } from 'rn-opencv-doc-perspective-correction';
|
|
66
|
+
|
|
67
|
+
// i. Máy đoán góc trước
|
|
68
|
+
const autoCorners = DocumentScanner.detectPageCorners(imageBase64);
|
|
69
|
+
|
|
70
|
+
// ii. Hiển thị ảnh + 4 nút draggable tại vị trí autoCorners (ví dụ dùng rn-image-crop-skew)
|
|
71
|
+
// Người dùng có thể kéo thả để tinh chỉnh
|
|
72
|
+
|
|
73
|
+
// iii. Sau khi người dùng ấn Done, lấy finalCorners từ UI component
|
|
74
|
+
const finalCorners: Point[] = getFinalCornersFromUI();
|
|
75
|
+
|
|
76
|
+
// iv. Warp với góc đã tinh chỉnh
|
|
77
|
+
const result = DocumentScanner.applyPerspectiveCorrection(imageBase64, finalCorners);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Thuật toán OpenCV bên dưới
|
|
81
|
+
|
|
82
|
+
### Phase 1 — Detect Corners:
|
|
83
|
+
```
|
|
84
|
+
Input Image
|
|
85
|
+
→ cvtColor (BGR2GRAY)
|
|
86
|
+
→ GaussianBlur (5x5 kernel)
|
|
87
|
+
→ Canny Edge Detection (threshold: 75, 200)
|
|
88
|
+
→ findContours (RETR_LIST, CHAIN_APPROX_SIMPLE)
|
|
89
|
+
→ Lọc contour diện tích lớn nhất có đúng 4 đỉnh (approxPolyDP)
|
|
90
|
+
→ Sắp xếp 4 góc theo hướng kim đồng hồ (TL → TR → BR → BL)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Phase 2 — Perspective Correction:
|
|
94
|
+
```
|
|
95
|
+
[4 góc nguồn] + Input Image
|
|
96
|
+
→ Tính maxWidth & maxHeight bằng khoảng cách Euclidean
|
|
97
|
+
→ Tạo srcPoints (góc nghiêng gốc) và dstPoints (hình chữ nhật phẳng)
|
|
98
|
+
→ getPerspectiveTransform → Ma trận biến dạng
|
|
99
|
+
→ warpPerspective → Ảnh đã phẳng
|
|
100
|
+
→ toBase64 → Output
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Lưu ý quan trọng
|
|
104
|
+
|
|
105
|
+
- Hàm **`OpenCV.clearBuffers()`** được gọi tự động trong `finally` block của mỗi hàm để giải phóng bộ nhớ C++ JSI, tránh memory leak.
|
|
106
|
+
- Kết quả `detectPageCorners` trả về **`undefined`** nếu không tìm thấy vật thể 4 góc rõ ràng — trong trường hợp đó, bạn nên yêu cầu User chỉnh tay.
|
|
107
|
+
- Nên dùng ảnh **độ phân giải vừa phải** (1000-2000px) để thuật toán hoạt động chính xác nhất. Ảnh quá nhỏ sẽ mất chi tiết viền; ảnh quá lớn sẽ tốn RAM.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type Point = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
};
|
|
5
|
+
export declare class DocumentScanner {
|
|
6
|
+
/**
|
|
7
|
+
* Tính khoảng cách Euclidean giữa 2 điểm
|
|
8
|
+
*/
|
|
9
|
+
private static getDistance;
|
|
10
|
+
/**
|
|
11
|
+
* Sắp xếp 4 điểm thành chuỗi TL, TR, BR, BL
|
|
12
|
+
*/
|
|
13
|
+
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;
|
|
22
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DocumentScanner = void 0;
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
const react_native_fast_opencv_1 = require("react-native-fast-opencv");
|
|
6
|
+
class DocumentScanner {
|
|
7
|
+
/**
|
|
8
|
+
* Tính khoảng cách Euclidean giữa 2 điểm
|
|
9
|
+
*/
|
|
10
|
+
static getDistance(p1, p2) {
|
|
11
|
+
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Sắp xếp 4 điểm thành chuỗi TL, TR, BR, BL
|
|
15
|
+
*/
|
|
16
|
+
static sortCorners(corners) {
|
|
17
|
+
if (corners.length !== 4)
|
|
18
|
+
return corners;
|
|
19
|
+
const center = corners.reduce((acc, cur) => ({ x: acc.x + cur.x / 4, y: acc.y + cur.y / 4 }), { x: 0, y: 0 });
|
|
20
|
+
return corners.sort((a, b) => {
|
|
21
|
+
const angleA = Math.atan2(a.y - center.y, a.x - center.x);
|
|
22
|
+
const angleB = Math.atan2(b.y - center.y, b.x - center.x);
|
|
23
|
+
return angleA - angleB;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Bước 1: Page Corner Detection (Auto-detect góc tài liệu)
|
|
28
|
+
*/
|
|
29
|
+
static detectPageCorners(imageBase64) {
|
|
30
|
+
let src = null;
|
|
31
|
+
let gray = null;
|
|
32
|
+
let blurred = null;
|
|
33
|
+
let edges = null;
|
|
34
|
+
let contoursObj = null;
|
|
35
|
+
let hierarchyObj = null;
|
|
36
|
+
try {
|
|
37
|
+
src = react_native_fast_opencv_1.OpenCV.invoke('Mat', react_native_fast_opencv_1.OpenCV.base64ToMat(imageBase64));
|
|
38
|
+
gray = react_native_fast_opencv_1.OpenCV.invoke('Mat');
|
|
39
|
+
blurred = react_native_fast_opencv_1.OpenCV.invoke('Mat');
|
|
40
|
+
edges = react_native_fast_opencv_1.OpenCV.invoke('Mat');
|
|
41
|
+
react_native_fast_opencv_1.OpenCV.invoke('cvtColor', src, gray, 6); // 6 is BGR2GRAY
|
|
42
|
+
const ksize = react_native_fast_opencv_1.OpenCV.createObject('Size', 5, 5);
|
|
43
|
+
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);
|
|
45
|
+
contoursObj = react_native_fast_opencv_1.OpenCV.createObject('MatVector');
|
|
46
|
+
hierarchyObj = react_native_fast_opencv_1.OpenCV.invoke('Mat');
|
|
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;
|
|
51
|
+
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.invoke('Mat');
|
|
57
|
+
react_native_fast_opencv_1.OpenCV.invoke('approxPolyDP', contour, approx, 0.02 * peri, true);
|
|
58
|
+
const vertices = react_native_fast_opencv_1.OpenCV.invoke('rows', approx);
|
|
59
|
+
if (vertices === 4) {
|
|
60
|
+
maxArea = area;
|
|
61
|
+
const points = [];
|
|
62
|
+
for (let v = 0; v < 4; v++) {
|
|
63
|
+
try {
|
|
64
|
+
const pt = react_native_fast_opencv_1.OpenCV.invoke('row', approx, v);
|
|
65
|
+
if (pt && typeof pt === 'object' && 'x' in pt)
|
|
66
|
+
points.push(pt);
|
|
67
|
+
}
|
|
68
|
+
catch (err) { }
|
|
69
|
+
}
|
|
70
|
+
if (points.length === 4)
|
|
71
|
+
largestPoly = points;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (largestPoly && largestPoly.length === 4) {
|
|
76
|
+
return this.sortCorners(largestPoly);
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
console.error('Lỗi khi dò tìm góc tài liệu (OpenCV):', e);
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
react_native_fast_opencv_1.OpenCV.clearBuffers();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Bước 2: Perspective Correction
|
|
90
|
+
*/
|
|
91
|
+
static applyPerspectiveCorrection(imageBase64, corners) {
|
|
92
|
+
let src = null;
|
|
93
|
+
let dst = null;
|
|
94
|
+
try {
|
|
95
|
+
if (!corners || corners.length !== 4)
|
|
96
|
+
throw new Error("Cần truyền vào đúng 4 điểm góc.");
|
|
97
|
+
const sortedCorners = this.sortCorners([...corners]);
|
|
98
|
+
const [tl, tr, br, bl] = sortedCorners;
|
|
99
|
+
const widthA = this.getDistance(br, bl);
|
|
100
|
+
const widthB = this.getDistance(tr, tl);
|
|
101
|
+
const maxWidth = Math.max(Math.round(widthA), Math.round(widthB));
|
|
102
|
+
const heightA = this.getDistance(tr, br);
|
|
103
|
+
const heightB = this.getDistance(tl, bl);
|
|
104
|
+
const maxHeight = Math.max(Math.round(heightA), Math.round(heightB));
|
|
105
|
+
if (maxWidth === 0 || maxHeight === 0)
|
|
106
|
+
return undefined;
|
|
107
|
+
src = react_native_fast_opencv_1.OpenCV.invoke('Mat', react_native_fast_opencv_1.OpenCV.base64ToMat(imageBase64));
|
|
108
|
+
dst = react_native_fast_opencv_1.OpenCV.invoke('Mat');
|
|
109
|
+
const srcPoints = react_native_fast_opencv_1.OpenCV.createObject('Point2fVector', tl.x, tl.y, tr.x, tr.y, br.x, br.y, bl.x, bl.y);
|
|
110
|
+
const dstPoints = react_native_fast_opencv_1.OpenCV.createObject('Point2fVector', 0, 0, maxWidth - 1, 0, maxWidth - 1, maxHeight - 1, 0, maxHeight - 1);
|
|
111
|
+
const perspectiveMatrix = react_native_fast_opencv_1.OpenCV.invoke('getPerspectiveTransform', srcPoints, dstPoints);
|
|
112
|
+
const size = react_native_fast_opencv_1.OpenCV.createObject('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);
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
console.error('Lỗi khi bóp phối cảnh tài liệu (OpenCV):', e);
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
react_native_fast_opencv_1.OpenCV.clearBuffers();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.DocumentScanner = DocumentScanner;
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rn-opencv-doc-perspective-correction",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A React Native library for document corner detection and perspective correction using react-native-fast-opencv",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"clean": "rm -rf dist"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react-native",
|
|
13
|
+
"opencv",
|
|
14
|
+
"document-scanner",
|
|
15
|
+
"perspective-correction"
|
|
16
|
+
],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": "*",
|
|
21
|
+
"react-native": "*",
|
|
22
|
+
"react-native-fast-opencv": "*"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "^5.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { OpenCV, OpenCVMat } from 'react-native-fast-opencv';
|
|
3
|
+
|
|
4
|
+
export type Point = { x: number; y: number };
|
|
5
|
+
|
|
6
|
+
export class DocumentScanner {
|
|
7
|
+
/**
|
|
8
|
+
* Tính khoảng cách Euclidean giữa 2 điểm
|
|
9
|
+
*/
|
|
10
|
+
private static getDistance(p1: Point, p2: Point) {
|
|
11
|
+
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Sắp xếp 4 điểm thành chuỗi TL, TR, BR, BL
|
|
16
|
+
*/
|
|
17
|
+
private static sortCorners(corners: Point[]): Point[] {
|
|
18
|
+
if (corners.length !== 4) return corners;
|
|
19
|
+
|
|
20
|
+
const center = corners.reduce(
|
|
21
|
+
(acc, cur) => ({ x: acc.x + cur.x / 4, y: acc.y + cur.y / 4 }),
|
|
22
|
+
{ x: 0, y: 0 }
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return corners.sort((a, b) => {
|
|
26
|
+
const angleA = Math.atan2(a.y - center.y, a.x - center.x);
|
|
27
|
+
const angleB = Math.atan2(b.y - center.y, b.x - center.x);
|
|
28
|
+
return angleA - angleB;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
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 {
|
|
36
|
+
let src: OpenCVMat | null = null;
|
|
37
|
+
let gray: OpenCVMat | null = null;
|
|
38
|
+
let blurred: OpenCVMat | null = null;
|
|
39
|
+
let edges: OpenCVMat | null = null;
|
|
40
|
+
let contoursObj: any = null;
|
|
41
|
+
let hierarchyObj: any = null;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
src = OpenCV.invoke('Mat', OpenCV.base64ToMat(imageBase64));
|
|
45
|
+
gray = OpenCV.invoke('Mat');
|
|
46
|
+
blurred = OpenCV.invoke('Mat');
|
|
47
|
+
edges = OpenCV.invoke('Mat');
|
|
48
|
+
|
|
49
|
+
OpenCV.invoke('cvtColor', src, gray, 6); // 6 is BGR2GRAY
|
|
50
|
+
|
|
51
|
+
const ksize = OpenCV.createObject('Size', 5, 5);
|
|
52
|
+
OpenCV.invoke('GaussianBlur', gray, blurred, ksize, 0);
|
|
53
|
+
|
|
54
|
+
OpenCV.invoke('Canny', blurred, edges, 75, 200, 3, false);
|
|
55
|
+
|
|
56
|
+
contoursObj = OpenCV.createObject('MatVector');
|
|
57
|
+
hierarchyObj = OpenCV.invoke('Mat');
|
|
58
|
+
|
|
59
|
+
OpenCV.invoke('findContours', edges, contoursObj, hierarchyObj, 1, 2);
|
|
60
|
+
|
|
61
|
+
const contoursSize = OpenCV.invoke('size', contoursObj) || 0;
|
|
62
|
+
let maxArea = 0;
|
|
63
|
+
let largestPoly: Point[] | undefined = undefined;
|
|
64
|
+
|
|
65
|
+
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.invoke('Mat');
|
|
72
|
+
OpenCV.invoke('approxPolyDP', contour, approx, 0.02 * peri, true);
|
|
73
|
+
|
|
74
|
+
const vertices = OpenCV.invoke('rows', approx);
|
|
75
|
+
if (vertices === 4) {
|
|
76
|
+
maxArea = area;
|
|
77
|
+
|
|
78
|
+
const points: Point[] = [];
|
|
79
|
+
for (let v = 0; v < 4; v++) {
|
|
80
|
+
try {
|
|
81
|
+
const pt = OpenCV.invoke('row', approx, v);
|
|
82
|
+
if (pt && typeof pt === 'object' && 'x' in pt) points.push(pt as Point);
|
|
83
|
+
} catch (err) { }
|
|
84
|
+
}
|
|
85
|
+
if (points.length === 4) largestPoly = points;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (largestPoly && largestPoly.length === 4) {
|
|
91
|
+
return this.sortCorners(largestPoly);
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error('Lỗi khi dò tìm góc tài liệu (OpenCV):', e);
|
|
96
|
+
return undefined;
|
|
97
|
+
} finally {
|
|
98
|
+
OpenCV.clearBuffers();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Bước 2: Perspective Correction
|
|
104
|
+
*/
|
|
105
|
+
public static applyPerspectiveCorrection(imageBase64: string, corners: Point[]): string | undefined {
|
|
106
|
+
let src: OpenCVMat | null = null;
|
|
107
|
+
let dst: OpenCVMat | null = null;
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
if (!corners || corners.length !== 4) throw new Error("Cần truyền vào đúng 4 điểm góc.");
|
|
111
|
+
|
|
112
|
+
const sortedCorners = this.sortCorners([...corners]);
|
|
113
|
+
const [tl, tr, br, bl] = sortedCorners;
|
|
114
|
+
|
|
115
|
+
const widthA = this.getDistance(br, bl);
|
|
116
|
+
const widthB = this.getDistance(tr, tl);
|
|
117
|
+
const maxWidth = Math.max(Math.round(widthA), Math.round(widthB));
|
|
118
|
+
|
|
119
|
+
const heightA = this.getDistance(tr, br);
|
|
120
|
+
const heightB = this.getDistance(tl, bl);
|
|
121
|
+
const maxHeight = Math.max(Math.round(heightA), Math.round(heightB));
|
|
122
|
+
|
|
123
|
+
if (maxWidth === 0 || maxHeight === 0) return undefined;
|
|
124
|
+
|
|
125
|
+
src = OpenCV.invoke('Mat', OpenCV.base64ToMat(imageBase64));
|
|
126
|
+
dst = OpenCV.invoke('Mat');
|
|
127
|
+
|
|
128
|
+
const srcPoints = OpenCV.createObject('Point2fVector', tl.x, tl.y, tr.x, tr.y, br.x, br.y, bl.x, bl.y);
|
|
129
|
+
const dstPoints = OpenCV.createObject(
|
|
130
|
+
'Point2fVector',
|
|
131
|
+
0, 0,
|
|
132
|
+
maxWidth - 1, 0,
|
|
133
|
+
maxWidth - 1, maxHeight - 1,
|
|
134
|
+
0, maxHeight - 1
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const perspectiveMatrix = OpenCV.invoke('getPerspectiveTransform', srcPoints, dstPoints);
|
|
138
|
+
|
|
139
|
+
const size = OpenCV.createObject('Size', maxWidth, maxHeight);
|
|
140
|
+
OpenCV.invoke('warpPerspective', src, dst, perspectiveMatrix, size);
|
|
141
|
+
|
|
142
|
+
return OpenCV.invoke('toBase64', dst);
|
|
143
|
+
} catch (e) {
|
|
144
|
+
console.error('Lỗi khi bóp phối cảnh tài liệu (OpenCV):', e);
|
|
145
|
+
return undefined;
|
|
146
|
+
} finally {
|
|
147
|
+
OpenCV.clearBuffers();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2015",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true
|
|
11
|
+
},
|
|
12
|
+
"include": [
|
|
13
|
+
"src"
|
|
14
|
+
]
|
|
15
|
+
}
|