rn-opencv-doc-perspective-correction 1.0.9 → 1.0.12
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 +20 -0
- package/dist/index.js +114 -11
- package/package.json +1 -1
- package/src/index.ts +36 -6
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ export type Point = {
|
|
|
2
2
|
x: number;
|
|
3
3
|
y: number;
|
|
4
4
|
};
|
|
5
|
+
/**
|
|
6
|
+
* Gợi ý phương thức OCR nên dùng dựa trên phân tích màu sắc.
|
|
7
|
+
* 'G' = ML Kit (nhiều màu, ảnh thẻ, màu sắc phong phú)
|
|
8
|
+
* 'S' = Tesseract (ít màu, bản scan, giấy trắng)
|
|
9
|
+
*/
|
|
10
|
+
export type OcrMethodHint = 'G' | 'S';
|
|
5
11
|
export declare class DocumentScanner {
|
|
6
12
|
private static getDistance;
|
|
7
13
|
private static sortCorners;
|
|
@@ -11,4 +17,18 @@ export declare class DocumentScanner {
|
|
|
11
17
|
* Xoay ảnh 90, -90 hoặc 180 độ
|
|
12
18
|
*/
|
|
13
19
|
static rotateImage(imageBase64: string, angle: 90 | -90 | 180): string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Phân tích độ phức tạp màu sắc để gợi ý phương thức OCR phù hợp.
|
|
22
|
+
*
|
|
23
|
+
* - Nếu corners phát hiện thành công → crop vùng tài liệu và phân tích vùng đó
|
|
24
|
+
* - Nếu không có corners (phát hiện thất bại) → phân tích toàn bộ ảnh
|
|
25
|
+
* - Dùng không gian màu Lab, đo độ lệch chuẩn kênh a* (xanh↔đỏ) và b* (lam↔vàng)
|
|
26
|
+
* - stdA + stdB > threshold → nhiều màu sắc → 'G' (ML Kit, phù hợp thẻ, ảnh màu)
|
|
27
|
+
* - stdA + stdB ≤ threshold → ít màu sắc → 'S' (Tesseract, phù hợp giấy trắng, bản scan)
|
|
28
|
+
*
|
|
29
|
+
* @param imageBase64 Ảnh gốc base64 JPEG
|
|
30
|
+
* @param corners 4 góc tài liệu đã phát hiện (tuỳ chọn)
|
|
31
|
+
* @param colorThreshold Ngưỡng phân biệt (mặc định 18.0, điều chỉnh nếu cần tinh chỉnh)
|
|
32
|
+
*/
|
|
33
|
+
static analyzeColorComplexity(imageBase64: string, corners?: Point[], colorThreshold?: number): OcrMethodHint;
|
|
14
34
|
}
|
package/dist/index.js
CHANGED
|
@@ -28,18 +28,14 @@ class DocumentScanner {
|
|
|
28
28
|
src = react_native_fast_opencv_1.OpenCV.base64ToMat(imageBase64);
|
|
29
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
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);
|
|
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
32
|
react_native_fast_opencv_1.OpenCV.invoke('cvtColor', src, gray, react_native_fast_opencv_1.ColorConversionCodes.COLOR_BGR2GRAY);
|
|
33
33
|
const ksize = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, 5, 5);
|
|
34
34
|
react_native_fast_opencv_1.OpenCV.invoke('GaussianBlur', gray, blurred, ksize, 0);
|
|
35
|
-
// Python uses: cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
|
36
|
-
// THRESH_BINARY = 0
|
|
37
|
-
// THRESH_OTSU = 8
|
|
38
35
|
// THRESH_BINARY + THRESH_OTSU = 8
|
|
39
36
|
react_native_fast_opencv_1.OpenCV.invoke('threshold', blurred, edges, 0, 255, 8);
|
|
40
37
|
contoursObj = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.MatVector);
|
|
41
38
|
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);
|
|
42
|
-
// Using RETR_EXTERNAL similar to the Python script for outer contours
|
|
43
39
|
react_native_fast_opencv_1.OpenCV.invoke('findContoursWithHierarchy', edges, contoursObj, hierarchyObj, 0 /* RETR_EXTERNAL */, 2 /* CHAIN_APPROX_SIMPLE */);
|
|
44
40
|
const contoursJS = react_native_fast_opencv_1.OpenCV.toJSValue(contoursObj);
|
|
45
41
|
const contoursArray = (contoursJS === null || contoursJS === void 0 ? void 0 : contoursJS.array) || [];
|
|
@@ -47,20 +43,17 @@ class DocumentScanner {
|
|
|
47
43
|
if (contoursSize === 0) {
|
|
48
44
|
return undefined;
|
|
49
45
|
}
|
|
50
|
-
// First pass: extract all areas to sort them and minimize JSI calls
|
|
51
46
|
let contourMetrics = [];
|
|
52
47
|
for (let i = 0; i < contoursSize; i++) {
|
|
53
48
|
const contour = react_native_fast_opencv_1.OpenCV.copyObjectFromVector(contoursObj, i);
|
|
54
49
|
const areaObj = react_native_fast_opencv_1.OpenCV.invoke('contourArea', contour);
|
|
55
50
|
const area = areaObj ? areaObj.value : 0;
|
|
56
|
-
if (area > 5000) {
|
|
51
|
+
if (area > 5000) {
|
|
57
52
|
contourMetrics.push({ index: i, area, contour });
|
|
58
53
|
}
|
|
59
54
|
}
|
|
60
|
-
// Sort contours by area in descending order
|
|
61
55
|
contourMetrics.sort((a, b) => b.area - a.area);
|
|
62
56
|
let largestPoly = undefined;
|
|
63
|
-
// Second pass: only check approxPolyDP for the largest ones
|
|
64
57
|
for (let i = 0; i < contourMetrics.length; i++) {
|
|
65
58
|
const metric = contourMetrics[i];
|
|
66
59
|
const contour = metric.contour;
|
|
@@ -71,7 +64,7 @@ class DocumentScanner {
|
|
|
71
64
|
const approxJS = react_native_fast_opencv_1.OpenCV.toJSValue(approx);
|
|
72
65
|
if (approxJS && approxJS.array && approxJS.array.length === 4) {
|
|
73
66
|
largestPoly = approxJS.array;
|
|
74
|
-
break;
|
|
67
|
+
break;
|
|
75
68
|
}
|
|
76
69
|
}
|
|
77
70
|
if (largestPoly && largestPoly.length === 4) {
|
|
@@ -122,7 +115,6 @@ class DocumentScanner {
|
|
|
122
115
|
const borderValue = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Scalar, 0);
|
|
123
116
|
react_native_fast_opencv_1.OpenCV.invoke('warpPerspective', src, dst, perspectiveMatrix, size, 1 /* INTER_LINEAR */, 0 /* BORDER_CONSTANT */, borderValue);
|
|
124
117
|
const dstValue = react_native_fast_opencv_1.OpenCV.toJSValue(dst);
|
|
125
|
-
// Fix "writeFile got an object" by guaranteeing string type
|
|
126
118
|
if (dstValue && dstValue.base64) {
|
|
127
119
|
return typeof dstValue.base64 === 'string' ? dstValue.base64 : String(dstValue.base64);
|
|
128
120
|
}
|
|
@@ -165,5 +157,116 @@ class DocumentScanner {
|
|
|
165
157
|
react_native_fast_opencv_1.OpenCV.clearBuffers();
|
|
166
158
|
}
|
|
167
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Phân tích độ phức tạp màu sắc để gợi ý phương thức OCR phù hợp.
|
|
162
|
+
*
|
|
163
|
+
* - Nếu corners phát hiện thành công → crop vùng tài liệu và phân tích vùng đó
|
|
164
|
+
* - Nếu không có corners (phát hiện thất bại) → phân tích toàn bộ ảnh
|
|
165
|
+
* - Dùng không gian màu Lab, đo độ lệch chuẩn kênh a* (xanh↔đỏ) và b* (lam↔vàng)
|
|
166
|
+
* - stdA + stdB > threshold → nhiều màu sắc → 'G' (ML Kit, phù hợp thẻ, ảnh màu)
|
|
167
|
+
* - stdA + stdB ≤ threshold → ít màu sắc → 'S' (Tesseract, phù hợp giấy trắng, bản scan)
|
|
168
|
+
*
|
|
169
|
+
* @param imageBase64 Ảnh gốc base64 JPEG
|
|
170
|
+
* @param corners 4 góc tài liệu đã phát hiện (tuỳ chọn)
|
|
171
|
+
* @param colorThreshold Ngưỡng phân biệt (mặc định 18.0, điều chỉnh nếu cần tinh chỉnh)
|
|
172
|
+
*/
|
|
173
|
+
static analyzeColorComplexity(imageBase64, corners, colorThreshold = 18.0) {
|
|
174
|
+
var _a, _b, _c, _d, _e, _f;
|
|
175
|
+
let src = null;
|
|
176
|
+
let roi = null;
|
|
177
|
+
let lab = null;
|
|
178
|
+
let color3Channel = null;
|
|
179
|
+
try {
|
|
180
|
+
src = react_native_fast_opencv_1.OpenCV.base64ToMat(imageBase64);
|
|
181
|
+
// Nếu có corners hợp lệ → crop vùng tài liệu để phân tích chính xác hơn
|
|
182
|
+
if (corners && corners.length === 4) {
|
|
183
|
+
try {
|
|
184
|
+
const sortedCorners = this.sortCorners([...corners]);
|
|
185
|
+
const [tl, tr, br, bl] = sortedCorners;
|
|
186
|
+
const widthA = this.getDistance(br, bl);
|
|
187
|
+
const widthB = this.getDistance(tr, tl);
|
|
188
|
+
const maxWidth = Math.max(Math.round(widthA), Math.round(widthB));
|
|
189
|
+
const heightA = this.getDistance(tr, br);
|
|
190
|
+
const heightB = this.getDistance(tl, bl);
|
|
191
|
+
const maxHeight = Math.max(Math.round(heightA), Math.round(heightB));
|
|
192
|
+
if (maxWidth > 0 && maxHeight > 0) {
|
|
193
|
+
const srcPts = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2fVector, [
|
|
194
|
+
react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2f, tl.x, tl.y),
|
|
195
|
+
react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2f, tr.x, tr.y),
|
|
196
|
+
react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2f, br.x, br.y),
|
|
197
|
+
react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2f, bl.x, bl.y),
|
|
198
|
+
]);
|
|
199
|
+
const dstPts = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2fVector, [
|
|
200
|
+
react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2f, 0, 0),
|
|
201
|
+
react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2f, maxWidth - 1, 0),
|
|
202
|
+
react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2f, maxWidth - 1, maxHeight - 1),
|
|
203
|
+
react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Point2f, 0, maxHeight - 1),
|
|
204
|
+
]);
|
|
205
|
+
const perspM = react_native_fast_opencv_1.OpenCV.invoke('getPerspectiveTransform', srcPts, dstPts, 0);
|
|
206
|
+
const sz = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Size, maxWidth, maxHeight);
|
|
207
|
+
const borderVal = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Scalar, 0);
|
|
208
|
+
roi = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8UC3);
|
|
209
|
+
react_native_fast_opencv_1.OpenCV.invoke('warpPerspective', src, roi, perspM, sz, 1, 0, borderVal);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (_) {
|
|
213
|
+
roi = null; // Crop lỗi → dùng toàn ảnh
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const target = roi !== null && roi !== void 0 ? roi : src; // Dùng vùng crop nếu có, ngược lại toàn ảnh
|
|
217
|
+
// Bắt buộc chuyển đổi về 3 kênh màu BGR để tránh lỗi khi dùng COLOR_BGR2Lab
|
|
218
|
+
// Trường hợp file ảnh là Grayscale (1 kênh) hoặc BGRA (4 kênh trong suốt)
|
|
219
|
+
color3Channel = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8UC3);
|
|
220
|
+
const channelsObj = react_native_fast_opencv_1.OpenCV.invoke('channels', target);
|
|
221
|
+
if (channelsObj && channelsObj.value === 1) {
|
|
222
|
+
react_native_fast_opencv_1.OpenCV.invoke('cvtColor', target, color3Channel, react_native_fast_opencv_1.ColorConversionCodes.COLOR_GRAY2BGR);
|
|
223
|
+
}
|
|
224
|
+
else if (channelsObj && channelsObj.value === 4) {
|
|
225
|
+
react_native_fast_opencv_1.OpenCV.invoke('cvtColor', target, color3Channel, react_native_fast_opencv_1.ColorConversionCodes.COLOR_BGRA2BGR);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
color3Channel = target; // Đã là 3 kênh
|
|
229
|
+
}
|
|
230
|
+
// Chuyển sang Lab (L=sáng/tối, a*=màu xanh↔đỏ, b*=màu lam↔vàng)
|
|
231
|
+
lab = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_8UC3);
|
|
232
|
+
react_native_fast_opencv_1.OpenCV.invoke('cvtColor', color3Channel, lab, react_native_fast_opencv_1.ColorConversionCodes.COLOR_BGR2Lab);
|
|
233
|
+
// Đo mean và stddev từng kênh
|
|
234
|
+
const meanMat = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_64F);
|
|
235
|
+
const stdMat = react_native_fast_opencv_1.OpenCV.createObject(react_native_fast_opencv_1.ObjectType.Mat, 0, 0, react_native_fast_opencv_1.DataTypes.CV_64F);
|
|
236
|
+
react_native_fast_opencv_1.OpenCV.invoke('meanStdDev', lab, meanMat, stdMat);
|
|
237
|
+
// Trích xuất stdDev một cách an toàn
|
|
238
|
+
const stdJS = react_native_fast_opencv_1.OpenCV.toJSValue(stdMat);
|
|
239
|
+
// react-native-fast-opencv thường chuyển `Mat` thành object JS có cấu trúc mảng 1 chiều trong prop `array` hoặc `data`
|
|
240
|
+
// hoặc trả về dưới dạng mảng lồng nhau
|
|
241
|
+
let stdA = 0;
|
|
242
|
+
let stdB = 0;
|
|
243
|
+
if (stdJS) {
|
|
244
|
+
if (Array.isArray(stdJS)) {
|
|
245
|
+
// Nếu nó đã là array
|
|
246
|
+
stdA = Array.isArray(stdJS[1]) ? stdJS[1][0] : (_a = stdJS[1]) !== null && _a !== void 0 ? _a : 0;
|
|
247
|
+
stdB = Array.isArray(stdJS[2]) ? stdJS[2][0] : (_b = stdJS[2]) !== null && _b !== void 0 ? _b : 0;
|
|
248
|
+
}
|
|
249
|
+
else if (stdJS.array && Array.isArray(stdJS.array)) {
|
|
250
|
+
stdA = (_c = stdJS.array[1]) !== null && _c !== void 0 ? _c : 0;
|
|
251
|
+
stdB = (_d = stdJS.array[2]) !== null && _d !== void 0 ? _d : 0;
|
|
252
|
+
}
|
|
253
|
+
else if (stdJS.data && Array.isArray(stdJS.data)) {
|
|
254
|
+
// Có một số version có định dạng `data`
|
|
255
|
+
stdA = (_e = stdJS.data[1]) !== null && _e !== void 0 ? _e : 0;
|
|
256
|
+
stdB = (_f = stdJS.data[2]) !== null && _f !== void 0 ? _f : 0;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const colorScore = stdA + stdB;
|
|
260
|
+
console.log(`[OCR Auto] colorScore=${colorScore.toFixed(2)} threshold=${colorThreshold} => ${colorScore > colorThreshold ? 'G (ML Kit)' : 'S (Tesseract)'}`);
|
|
261
|
+
return colorScore > colorThreshold ? 'G' : 'S';
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
console.error('[OpenCV] analyzeColorComplexity lỗi:', e === null || e === void 0 ? void 0 : e.message);
|
|
265
|
+
throw new Error(`[OpenCV analyzeColorComplexity Error]: ${e === null || e === void 0 ? void 0 : e.message}`);
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
react_native_fast_opencv_1.OpenCV.clearBuffers();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
168
271
|
}
|
|
169
272
|
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.
|
|
3
|
+
"version": "1.0.12",
|
|
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
|
@@ -218,6 +218,7 @@ export class DocumentScanner {
|
|
|
218
218
|
let src: OpenCVMat | null = null;
|
|
219
219
|
let roi: OpenCVMat | null = null;
|
|
220
220
|
let lab: OpenCVMat | null = null;
|
|
221
|
+
let color3Channel: OpenCVMat | null = null;
|
|
221
222
|
|
|
222
223
|
try {
|
|
223
224
|
src = OpenCV.base64ToMat(imageBase64);
|
|
@@ -260,27 +261,56 @@ export class DocumentScanner {
|
|
|
260
261
|
|
|
261
262
|
const target = roi ?? src; // Dùng vùng crop nếu có, ngược lại toàn ảnh
|
|
262
263
|
|
|
264
|
+
// Bắt buộc chuyển đổi về 3 kênh màu BGR để tránh lỗi khi dùng COLOR_BGR2Lab
|
|
265
|
+
// Trường hợp file ảnh là Grayscale (1 kênh) hoặc BGRA (4 kênh trong suốt)
|
|
266
|
+
color3Channel = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8UC3);
|
|
267
|
+
const channelsObj = OpenCV.invoke('channels', target);
|
|
268
|
+
if (channelsObj && channelsObj.value === 1) {
|
|
269
|
+
OpenCV.invoke('cvtColor', target, color3Channel, ColorConversionCodes.COLOR_GRAY2BGR);
|
|
270
|
+
} else if (channelsObj && channelsObj.value === 4) {
|
|
271
|
+
OpenCV.invoke('cvtColor', target, color3Channel, ColorConversionCodes.COLOR_BGRA2BGR);
|
|
272
|
+
} else {
|
|
273
|
+
color3Channel = target; // Đã là 3 kênh
|
|
274
|
+
}
|
|
275
|
+
|
|
263
276
|
// Chuyển sang Lab (L=sáng/tối, a*=màu xanh↔đỏ, b*=màu lam↔vàng)
|
|
264
277
|
lab = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_8UC3);
|
|
265
|
-
OpenCV.invoke('cvtColor',
|
|
278
|
+
OpenCV.invoke('cvtColor', color3Channel, lab, ColorConversionCodes.COLOR_BGR2Lab);
|
|
266
279
|
|
|
267
280
|
// Đo mean và stddev từng kênh
|
|
268
281
|
const meanMat = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_64F);
|
|
269
282
|
const stdMat = OpenCV.createObject(ObjectType.Mat, 0, 0, DataTypes.CV_64F);
|
|
270
283
|
OpenCV.invoke('meanStdDev', lab, meanMat, stdMat);
|
|
271
284
|
|
|
285
|
+
// Trích xuất stdDev một cách an toàn
|
|
272
286
|
const stdJS = OpenCV.toJSValue(stdMat);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
287
|
+
// react-native-fast-opencv thường chuyển `Mat` thành object JS có cấu trúc mảng 1 chiều trong prop `array` hoặc `data`
|
|
288
|
+
// hoặc trả về dưới dạng mảng lồng nhau
|
|
289
|
+
let stdA = 0;
|
|
290
|
+
let stdB = 0;
|
|
291
|
+
|
|
292
|
+
if (stdJS) {
|
|
293
|
+
if (Array.isArray(stdJS)) {
|
|
294
|
+
// Nếu nó đã là array
|
|
295
|
+
stdA = Array.isArray(stdJS[1]) ? stdJS[1][0] : stdJS[1] ?? 0;
|
|
296
|
+
stdB = Array.isArray(stdJS[2]) ? stdJS[2][0] : stdJS[2] ?? 0;
|
|
297
|
+
} else if (stdJS.array && Array.isArray(stdJS.array)) {
|
|
298
|
+
stdA = stdJS.array[1] ?? 0;
|
|
299
|
+
stdB = stdJS.array[2] ?? 0;
|
|
300
|
+
} else if (stdJS.data && Array.isArray(stdJS.data)) {
|
|
301
|
+
// Có một số version có định dạng `data`
|
|
302
|
+
stdA = stdJS.data[1] ?? 0;
|
|
303
|
+
stdB = stdJS.data[2] ?? 0;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
276
306
|
|
|
277
307
|
const colorScore = stdA + stdB;
|
|
278
308
|
console.log(`[OCR Auto] colorScore=${colorScore.toFixed(2)} threshold=${colorThreshold} => ${colorScore > colorThreshold ? 'G (ML Kit)' : 'S (Tesseract)'}`);
|
|
279
309
|
|
|
280
310
|
return colorScore > colorThreshold ? 'G' : 'S';
|
|
281
311
|
} catch (e: any) {
|
|
282
|
-
console.
|
|
283
|
-
|
|
312
|
+
console.error('[OpenCV] analyzeColorComplexity lỗi:', e?.message);
|
|
313
|
+
throw new Error(`[OpenCV analyzeColorComplexity Error]: ${e?.message}`);
|
|
284
314
|
} finally {
|
|
285
315
|
OpenCV.clearBuffers();
|
|
286
316
|
}
|