id-scanner-lib 1.6.7 → 2.0.0
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/id-scanner-lib.esm.js +994 -1139
- package/dist/id-scanner-lib.esm.js.map +1 -1
- package/dist/id-scanner-lib.js +995 -1144
- package/dist/id-scanner-lib.js.map +1 -1
- package/package.json +1 -1
- package/src/compat/index.ts +7 -0
- package/src/compat/v1-adapter.ts +84 -0
- package/src/core/camera-manager.ts +43 -76
- package/src/core/camera-stream-manager.ts +318 -0
- package/src/core/config.ts +113 -267
- package/src/core/errors.ts +68 -117
- package/src/core/logger.ts +158 -81
- package/src/core/resource-manager.ts +150 -0
- package/src/core/scanner.ts +109 -0
- package/src/core/utils/browser.ts +7 -0
- package/src/core/utils/canvas-pool.ts +171 -0
- package/src/core/utils/canvas.ts +7 -0
- package/src/core/utils/image.ts +7 -0
- package/src/core/utils/index.ts +9 -0
- package/src/core/utils/resource-manager.ts +155 -0
- package/src/core/utils/validate.ts +7 -0
- package/src/core/utils/worker.ts +130 -0
- package/src/modules/face/comparator/comparator.ts +45 -0
- package/src/modules/face/comparator/index.ts +1 -0
- package/src/modules/face/detector/detector.ts +83 -0
- package/src/modules/face/detector/index.ts +2 -0
- package/src/modules/face/detector/types.ts +80 -0
- package/src/modules/face/face-comparator.ts +150 -0
- package/src/modules/face/face-detector-options.ts +104 -0
- package/src/modules/face/face-detector.ts +121 -376
- package/src/modules/face/face-detector.ts.bak +991 -0
- package/src/modules/face/face-model-loader.ts +222 -0
- package/src/modules/face/face-result-converter.ts +225 -0
- package/src/modules/face/face-tracker.ts +207 -0
- package/src/modules/face/liveness/index.ts +7 -0
- package/src/modules/face/liveness-detector.ts +2 -2
- package/src/modules/face/tracker/index.ts +7 -0
- package/src/modules/id-card/anti-fake/index.ts +7 -0
- package/src/modules/id-card/detector/index.ts +7 -0
- package/src/modules/id-card/id-card-text-parser.ts +151 -0
- package/src/modules/id-card/ocr-processor.ts +20 -257
- package/src/modules/id-card/ocr-worker.ts +2 -183
- package/src/modules/id-card/parser/index.ts +7 -0
- package/src/modules/qr/scanner/index.ts +7 -0
- package/src/utils/canvas-pool.ts +273 -0
- package/src/utils/edge-detector.ts +232 -0
- package/src/utils/image-processing.ts +92 -419
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 边缘检测器
|
|
3
|
+
* @description 提供边缘检测算法(Sobel、Canny等)
|
|
4
|
+
* @module utils/edge-detector
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 边缘检测器类
|
|
9
|
+
* 提供各种边缘检测算法用于图像处理
|
|
10
|
+
*/
|
|
11
|
+
export class EdgeDetector {
|
|
12
|
+
/**
|
|
13
|
+
* 使用Sobel算子进行边缘检测
|
|
14
|
+
* @param imageData 灰度图像数据
|
|
15
|
+
* @param threshold 边缘阈值,默认为30
|
|
16
|
+
* @returns 检测到边缘的图像数据
|
|
17
|
+
*/
|
|
18
|
+
static detectEdges(imageData: ImageData, threshold: number = 30): ImageData {
|
|
19
|
+
const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
|
|
20
|
+
const width = grayscaleImage.width;
|
|
21
|
+
const height = grayscaleImage.height;
|
|
22
|
+
const inputData = grayscaleImage.data;
|
|
23
|
+
const outputData = new Uint8ClampedArray(inputData.length);
|
|
24
|
+
const sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
|
|
25
|
+
const sobelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
|
|
26
|
+
|
|
27
|
+
for (let y = 1; y < height - 1; y++) {
|
|
28
|
+
for (let x = 1; x < width - 1; x++) {
|
|
29
|
+
let gx = 0, gy = 0;
|
|
30
|
+
for (let ky = -1; ky <= 1; ky++) {
|
|
31
|
+
for (let kx = -1; kx <= 1; kx++) {
|
|
32
|
+
const pixelPos = ((y + ky) * width + (x + kx)) * 4;
|
|
33
|
+
const pixelVal = inputData[pixelPos];
|
|
34
|
+
const kernelIdx = (ky + 1) * 3 + (kx + 1);
|
|
35
|
+
gx += pixelVal * sobelX[kernelIdx];
|
|
36
|
+
gy += pixelVal * sobelY[kernelIdx];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
let magnitude = Math.sqrt(gx * gx + gy * gy);
|
|
40
|
+
magnitude = magnitude > threshold ? 255 : 0;
|
|
41
|
+
const pos = (y * width + x) * 4;
|
|
42
|
+
outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = magnitude;
|
|
43
|
+
outputData[pos + 3] = 255;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 处理边缘
|
|
48
|
+
for (let i = 0; i < width * 4; i++) {
|
|
49
|
+
outputData[i] = 0;
|
|
50
|
+
outputData[(height - 1) * width * 4 + i] = 0;
|
|
51
|
+
}
|
|
52
|
+
for (let i = 0; i < height; i++) {
|
|
53
|
+
const leftPos = i * width * 4;
|
|
54
|
+
const rightPos = (i * width + width - 1) * 4;
|
|
55
|
+
for (let j = 0; j < 4; j++) {
|
|
56
|
+
outputData[leftPos + j] = 0;
|
|
57
|
+
outputData[rightPos + j] = 0;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return new ImageData(outputData, width, height);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 卡尼-德里奇边缘检测
|
|
66
|
+
*/
|
|
67
|
+
static cannyEdgeDetection(imageData: ImageData, lowThreshold: number = 20, highThreshold: number = 50): ImageData {
|
|
68
|
+
const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
|
|
69
|
+
const blurredImage = this.gaussianBlur(grayscaleImage, 1.5);
|
|
70
|
+
const { gradientMagnitude, gradientDirection } = this.computeGradients(blurredImage);
|
|
71
|
+
const nonMaxSuppressed = this.nonMaxSuppression(gradientMagnitude, gradientDirection, blurredImage.width, blurredImage.height);
|
|
72
|
+
const thresholdResult = this.hysteresisThresholding(nonMaxSuppressed, blurredImage.width, blurredImage.height, lowThreshold, highThreshold);
|
|
73
|
+
|
|
74
|
+
const outputData = new Uint8ClampedArray(imageData.data.length);
|
|
75
|
+
for (let i = 0; i < thresholdResult.length; i++) {
|
|
76
|
+
const pos = i * 4;
|
|
77
|
+
const value = thresholdResult[i] ? 255 : 0;
|
|
78
|
+
outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = value;
|
|
79
|
+
outputData[pos + 3] = 255;
|
|
80
|
+
}
|
|
81
|
+
return new ImageData(outputData, blurredImage.width, blurredImage.height);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private static toGrayscale(imageData: ImageData): ImageData {
|
|
85
|
+
const srcData = imageData.data;
|
|
86
|
+
const destData = new Uint8ClampedArray(srcData);
|
|
87
|
+
for (let i = 0; i < srcData.length; i += 4) {
|
|
88
|
+
const gray = srcData[i] * 0.3 + srcData[i + 1] * 0.59 + srcData[i + 2] * 0.11;
|
|
89
|
+
destData[i] = destData[i + 1] = destData[i + 2] = gray;
|
|
90
|
+
destData[i + 3] = srcData[i + 3];
|
|
91
|
+
}
|
|
92
|
+
return new ImageData(destData, imageData.width, imageData.height);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private static gaussianBlur(imageData: ImageData, sigma: number = 1.5): ImageData {
|
|
96
|
+
const width = imageData.width, height = imageData.height;
|
|
97
|
+
const inputData = imageData.data, outputData = new Uint8ClampedArray(inputData.length);
|
|
98
|
+
const kernelSize = Math.max(3, Math.floor(sigma * 3) * 2 + 1);
|
|
99
|
+
const halfKernel = Math.floor(kernelSize / 2);
|
|
100
|
+
const kernel = this.generateGaussianKernel(kernelSize, sigma);
|
|
101
|
+
|
|
102
|
+
for (let y = 0; y < height; y++) {
|
|
103
|
+
for (let x = 0; x < width; x++) {
|
|
104
|
+
let sum = 0, weightSum = 0;
|
|
105
|
+
for (let ky = -halfKernel; ky <= halfKernel; ky++) {
|
|
106
|
+
for (let kx = -halfKernel; kx <= halfKernel; kx++) {
|
|
107
|
+
const pixelY = Math.min(Math.max(y + ky, 0), height - 1);
|
|
108
|
+
const pixelX = Math.min(Math.max(x + kx, 0), width - 1);
|
|
109
|
+
const pixelPos = (pixelY * width + pixelX) * 4;
|
|
110
|
+
const kernelY = ky + halfKernel, kernelX = kx + halfKernel;
|
|
111
|
+
const weight = kernel[kernelY * kernelSize + kernelX];
|
|
112
|
+
sum += inputData[pixelPos] * weight;
|
|
113
|
+
weightSum += weight;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const pos = (y * width + x) * 4;
|
|
117
|
+
const value = Math.round(sum / weightSum);
|
|
118
|
+
outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = value;
|
|
119
|
+
outputData[pos + 3] = 255;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return new ImageData(outputData, width, height);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private static generateGaussianKernel(size: number, sigma: number): number[] {
|
|
126
|
+
const kernel = new Array(size * size);
|
|
127
|
+
const center = Math.floor(size / 2);
|
|
128
|
+
let sum = 0;
|
|
129
|
+
for (let y = 0; y < size; y++) {
|
|
130
|
+
for (let x = 0; x < size; x++) {
|
|
131
|
+
const distance = Math.sqrt((x - center) ** 2 + (y - center) ** 2);
|
|
132
|
+
kernel[y * size + x] = Math.exp(-(distance ** 2) / (2 * sigma ** 2));
|
|
133
|
+
sum += kernel[y * size + x];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (let i = 0; i < kernel.length; i++) kernel[i] /= sum;
|
|
137
|
+
return kernel;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private static computeGradients(imageData: ImageData): { gradientMagnitude: number[], gradientDirection: number[] } {
|
|
141
|
+
const width = imageData.width, height = imageData.height;
|
|
142
|
+
const inputData = imageData.data;
|
|
143
|
+
const gradientMagnitude = new Array(width * height);
|
|
144
|
+
const gradientDirection = new Array(width * height);
|
|
145
|
+
const sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
|
|
146
|
+
const sobelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
|
|
147
|
+
|
|
148
|
+
for (let y = 1; y < height - 1; y++) {
|
|
149
|
+
for (let x = 1; x < width - 1; x++) {
|
|
150
|
+
let gx = 0, gy = 0;
|
|
151
|
+
for (let ky = -1; ky <= 1; ky++) {
|
|
152
|
+
for (let kx = -1; kx <= 1; kx++) {
|
|
153
|
+
const pixelPos = ((y + ky) * width + (x + kx)) * 4;
|
|
154
|
+
const pixelVal = inputData[pixelPos];
|
|
155
|
+
const kernelIdx = (ky + 1) * 3 + (kx + 1);
|
|
156
|
+
gx += pixelVal * sobelX[kernelIdx];
|
|
157
|
+
gy += pixelVal * sobelY[kernelIdx];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const idx = y * width + x;
|
|
161
|
+
gradientMagnitude[idx] = Math.sqrt(gx * gx + gy * gy);
|
|
162
|
+
gradientDirection[idx] = Math.atan2(gy, gx);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { gradientMagnitude, gradientDirection };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private static nonMaxSuppression(gradientMagnitude: number[], gradientDirection: number[], width: number, height: number): number[] {
|
|
169
|
+
const result = new Array(width * height).fill(0);
|
|
170
|
+
for (let y = 1; y < height - 1; y++) {
|
|
171
|
+
for (let x = 1; x < width - 1; x++) {
|
|
172
|
+
const idx = y * width + x;
|
|
173
|
+
const magnitude = gradientMagnitude[idx];
|
|
174
|
+
const direction = gradientDirection[idx];
|
|
175
|
+
const degrees = (direction * 180 / Math.PI + 180) % 180;
|
|
176
|
+
let neighbor1Idx: number, neighbor2Idx: number;
|
|
177
|
+
|
|
178
|
+
if ((degrees >= 0 && degrees < 22.5) || (degrees >= 157.5 && degrees <= 180)) {
|
|
179
|
+
neighbor1Idx = idx - 1; neighbor2Idx = idx + 1;
|
|
180
|
+
} else if (degrees >= 22.5 && degrees < 67.5) {
|
|
181
|
+
neighbor1Idx = (y - 1) * width + (x + 1); neighbor2Idx = (y + 1) * width + (x - 1);
|
|
182
|
+
} else if (degrees >= 67.5 && degrees < 112.5) {
|
|
183
|
+
neighbor1Idx = (y - 1) * width + x; neighbor2Idx = (y + 1) * width + x;
|
|
184
|
+
} else {
|
|
185
|
+
neighbor1Idx = (y - 1) * width + (x - 1); neighbor2Idx = (y + 1) * width + (x + 1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (magnitude >= gradientMagnitude[neighbor1Idx] && magnitude >= gradientMagnitude[neighbor2Idx]) {
|
|
189
|
+
result[idx] = magnitude;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private static hysteresisThresholding(nonMaxSuppressed: number[], width: number, height: number, lowThreshold: number, highThreshold: number): boolean[] {
|
|
197
|
+
const result = new Array(width * height).fill(false);
|
|
198
|
+
const visited = new Array(width * height).fill(false);
|
|
199
|
+
const stack: number[] = [];
|
|
200
|
+
|
|
201
|
+
for (let i = 0; i < nonMaxSuppressed.length; i++) {
|
|
202
|
+
if (nonMaxSuppressed[i] >= highThreshold) {
|
|
203
|
+
result[i] = true;
|
|
204
|
+
stack.push(i);
|
|
205
|
+
visited[i] = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const dx = [-1, 0, 1, -1, 1, -1, 0, 1];
|
|
210
|
+
const dy = [-1, -1, -1, 0, 0, 1, 1, 1];
|
|
211
|
+
|
|
212
|
+
while (stack.length > 0) {
|
|
213
|
+
const currentIdx = stack.pop()!;
|
|
214
|
+
const currentX = currentIdx % width;
|
|
215
|
+
const currentY = Math.floor(currentIdx / width);
|
|
216
|
+
|
|
217
|
+
for (let i = 0; i < 8; i++) {
|
|
218
|
+
const newX = currentX + dx[i];
|
|
219
|
+
const newY = currentY + dy[i];
|
|
220
|
+
if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
|
|
221
|
+
const newIdx = newY * width + newX;
|
|
222
|
+
if (!visited[newIdx] && nonMaxSuppressed[newIdx] >= lowThreshold) {
|
|
223
|
+
result[newIdx] = true;
|
|
224
|
+
stack.push(newIdx);
|
|
225
|
+
visited[newIdx] = true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
}
|