id-scanner-lib 1.3.0 → 1.3.3

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 CHANGED
@@ -3,6 +3,8 @@
3
3
  纯前端实现的TypeScript身份证与二维码识别库,无需后端支持,所有处理均在浏览器端完成。结合高性能图像处理与OCR技术,提供完整的识别解决方案。
4
4
 
5
5
  [![NPM Version](https://img.shields.io/npm/v/id-scanner-lib.svg)](https://www.npmjs.com/package/id-scanner-lib)
6
+ [![GitHub Stars](https://img.shields.io/github/stars/agions/id-scanner-lib.svg?style=social)](https://github.com/agions/id-scanner-lib)
7
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/id-scanner-lib)](https://bundlephobia.com/package/id-scanner-lib)
6
8
  [![License](https://img.shields.io/npm/l/id-scanner-lib.svg)](https://github.com/agions/id-scanner-lib/blob/master/LICENSE)
7
9
 
8
10
  ## 技术特性
@@ -11,6 +13,7 @@
11
13
  - **二维码扫描**:基于jsQR实现高精度二维码识别与解码
12
14
  - **条形码识别**:支持EAN-13、CODE-128等常见一维码格式
13
15
  - **身份证OCR**:基于Tesseract.js的优化OCR引擎,精确提取身份证信息
16
+ - **身份证防伪检测**:检测多种防伪特征,有效识别伪造证件
14
17
  - **图像处理优化**:内置多种图像预处理算法,提高识别率
15
18
  - **支持多种数据源**:摄像头实时视频流、图片文件、URL、Base64等
16
19
  - **高效缓存机制**:内置LRU缓存,避免重复识别,提升性能
@@ -25,9 +28,10 @@
25
28
  | 二维码识别 | 50-150ms | >98% | 取决于图像质量 |
26
29
  | 条形码识别 | 70-200ms | >95% | 支持多种格式 |
27
30
  | 身份证OCR | 300-800ms | >90% | 优化后的识别速度 |
31
+ | 防伪检测 | 100-200ms | >85% | 多特征综合分析 |
28
32
  | 图像处理 | 20-100ms | - | 视处理操作而定 |
29
33
 
30
- ## 最新版本 (v1.3.0)
34
+ ## 最新版本 (v1.3.2)
31
35
 
32
36
  - **图像处理引擎升级**:
33
37
  - 增强的图像锐化算法,提高低光照环境下的识别率
@@ -41,30 +45,129 @@
41
45
  - 批量图像处理API
42
46
  - 内置图像压缩功能
43
47
  - 一体化演示组件
48
+ - **身份证防伪检测**:识别多种防伪特征,检测伪造证件
49
+ - 支持荧光油墨、微缩文字、光变图案等5种防伪特征检测
50
+ - 基于多特征综合评分,提供置信度评估
51
+ - 缓存机制提高重复检测性能
44
52
  - **架构改进**:
45
53
  - 资源自动释放机制
46
54
  - 更精细的模块划分
47
55
  - 增强的错误处理
48
56
 
57
+ ## 版本路线图
58
+
59
+ ### v1.4.0:人脸比对与活体检测
60
+
61
+ - **人脸比对模块**:
62
+ - 身份证照片与现场采集照片的比对功能
63
+ - 基于深度学习的人脸特征提取与分析
64
+ - 提供相似度评分与置信度
65
+ - **活体检测**:
66
+ - 眨眼、张嘴等动作检测防止照片欺骗
67
+ - 基于光线反射的3D检测技术
68
+ - 多帧分析提高检测准确率
69
+ - **安全增强**:
70
+ - 本地处理所有数据,保护隐私
71
+ - 结果加密存储选项
72
+ - 合规性验证工具
73
+
74
+ ### v1.5.0:多证件类型支持
75
+
76
+ - **护照识别**:
77
+ - MRZ码(机读区)解析
78
+ - 多国护照模板适配
79
+ - 芯片信息读取(ePassport支持)
80
+ - **驾驶证识别**:
81
+ - 驾驶证OCR识别
82
+ - 驾驶资格与限制条件解析
83
+ - 国际驾照支持
84
+ - **营业执照识别**:
85
+ - 企业信息提取
86
+ - 统一社会信用代码验证
87
+ - 经营范围解析
88
+ - **银行卡识别**:
89
+ - 卡号、有效期识别
90
+ - 银行标识解析
91
+ - BIN码验证
92
+
93
+ ### v1.6.0:UI/UX改进与组件库升级
94
+
95
+ - **现代化UI框架**:
96
+ - 基于Web Components的组件系统
97
+ - 自适应扫描界面
98
+ - 多主题支持(含暗色模式)
99
+ - **交互体验优化**:
100
+ - 实时扫描引导框
101
+ - 智能对焦与取景提示
102
+ - 证件边缘自动检测与校正
103
+ - **可访问性支持**:
104
+ - 屏幕阅读器兼容
105
+ - 键盘导航
106
+ - 多语言本地化
107
+ - **动效与反馈**:
108
+ - 平滑过渡动画
109
+ - 触觉反馈(移动设备)
110
+ - 声音反馈与语音提示
111
+
112
+ ### v1.7.0:性能与架构优化
113
+
114
+ - **WebAssembly实现**:
115
+ - 核心图像处理算法WASM化
116
+ - 性能提升3-5倍
117
+ - 更低的CPU占用
118
+ - **离线支持**:
119
+ - 完整离线运行能力
120
+ - 基于IndexedDB的本地缓存
121
+ - Service Worker支持
122
+ - **微前端集成**:
123
+ - React/Vue/Angular专用组件
124
+ - 更简单的集成API
125
+ - TypeScript类型增强
126
+ - **渐进式加载**:
127
+ - 核心功能快速加载
128
+ - 按需延迟加载附加模块
129
+ - 预测性加载提高响应速度
130
+
131
+ ### v2.0.0:企业级解决方案
132
+
133
+ - **云端协同验证**:
134
+ - 可选云端验证API
135
+ - 本地与云端结果比对
136
+ - 多级安全验证
137
+ - **高级分析功能**:
138
+ - 证件使用统计与分析
139
+ - 风险评估模型
140
+ - 异常检测系统
141
+ - **行业解决方案包**:
142
+ - 金融行业KYC流程集成
143
+ - 酒店/零售快速登记系统
144
+ - 政务/安防高安全性验证
145
+ - **企业级管理功能**:
146
+ - 多租户支持
147
+ - 批量处理队列
148
+ - 完整审计日志
149
+
49
150
  ## 系统架构
50
151
 
51
152
  ```
52
- ┌─────────────────────────────────────────────────────────────┐
153
+ ┌────────────────────────────────────────────────────────────┐
53
154
  │ IDScanner 主模块 │
54
155
  ├─────────────┬─────────────────┬────────────────────────────┤
55
156
  │ QRScanner │ BarcodeScanner │ IDCardDetector │
56
157
  ├─────────────┴─────────────────┴────────────────────────────┤
57
- │ Camera (视频流捕获与处理)
58
- └─────────────────────────────────────────────────────────────┘
158
+ │ Camera (视频流捕获与处理)
159
+ └────────────────────────────────────────────────────────────┘
59
160
 
60
161
 
61
162
 
62
- ┌─────────────────────────────────────────────────────────────┐
163
+ ┌────────────────────────────────────────────────────────────┐
63
164
  │ 核心处理引擎 │
64
165
  ├─────────────────┬─────────────────┬────────────────────────┤
65
166
  │ OCRProcessor │ DataExtractor │ ImageProcessor │
66
- │ (文字识别) │ (数据提取验证) (图像预处理)
67
- └─────────────────┴─────────────────┴────────────────────────┘
167
+ │ (文字识别) │ (数据提取验证) (图像预处理)
168
+ ├─────────────────┴─────────────────┴────────────────────────┤
169
+ │ AntiFakeDetector (身份证防伪检测) │
170
+ └────────────────────────────────────────────────────────────┘
68
171
  ```
69
172
 
70
173
  ## 安装与使用
@@ -79,22 +182,22 @@ npm install id-scanner-lib --save
79
182
 
80
183
  ```html
81
184
  <!-- 生产环境 (压缩版) -->
82
- <script src="https://cdn.jsdelivr.net/npm/id-scanner-lib@1.3.0/dist/id-scanner.min.js"></script>
185
+ <script src="https://cdn.jsdelivr.net/npm/id-scanner-lib@1.3.1/dist/id-scanner.min.js"></script>
83
186
 
84
187
  <!-- 开发环境 (未压缩) -->
85
- <script src="https://cdn.jsdelivr.net/npm/id-scanner-lib@1.3.0/dist/id-scanner.js"></script>
188
+ <script src="https://cdn.jsdelivr.net/npm/id-scanner-lib@1.3.1/dist/id-scanner.js"></script>
86
189
  ```
87
190
 
88
191
  ## 包体积优化
89
192
 
90
- v1.3.0版本通过代码分割和Tree-shaking极大地优化了包体积:
193
+ v1.3.1版本通过代码分割和Tree-shaking极大地优化了包体积:
91
194
 
92
195
  | 模块 | 大小 (gzip) | 说明 |
93
196
  | --------------- | ----------- | ---------------- |
94
- | 完整包(min.js) | 25KB | 包含所有功能 |
95
- | 核心包(min.js) | 90KB | 基础功能,无OCR |
96
- | OCR模块(min.js) | 14KB | 仅文字识别 |
97
- | QR模块(min.js) | 6KB | 仅二维码识别 |
197
+ | 完整包(min.js) | 93KB | 包含所有功能 |
198
+ | 核心包(min.js) | 186KB | 基础功能,无OCR |
199
+ | OCR模块(min.js) | 70KB | 仅文字识别 |
200
+ | QR模块(min.js) | 60KB | 仅二维码识别 |
98
201
  | ESM模块 | 各模块更小 | 支持Tree-shaking |
99
202
 
100
203
  ## 最佳实践:按需引入
@@ -107,7 +210,8 @@ import { IDScanner } from 'id-scanner-lib';
107
210
 
108
211
  const scanner = new IDScanner({
109
212
  onQRCodeScanned: (result) => console.log('扫描结果:', result),
110
- onIDCardScanned: (info) => console.log('身份证信息:', info)
213
+ onIDCardScanned: (info) => console.log('身份证信息:', info),
214
+ onAntiFakeDetected: (result) => console.log('防伪检测结果:', result)
111
215
  });
112
216
  ```
113
217
 
@@ -176,12 +280,57 @@ document.getElementById('fileInput').addEventListener('change', async (e) => {
176
280
  // 处理身份证图像
177
281
  const idInfo = await scanner.processIDCardImage(compressed);
178
282
  console.log('身份证信息:', idInfo);
283
+
284
+ // 检查防伪检测结果
285
+ if (idInfo.antiFakeResult) {
286
+ console.log('防伪检测结果:', idInfo.antiFakeResult);
287
+ if (idInfo.antiFakeResult.isAuthentic) {
288
+ console.log('证件验证通过');
289
+ } else {
290
+ console.log('警告:可能为伪造证件');
291
+ }
292
+ }
179
293
  } catch (error) {
180
294
  console.error('处理失败:', error);
181
295
  }
182
296
  });
183
297
  ```
184
298
 
299
+ ### 身份证防伪检测
300
+
301
+ ```javascript
302
+ import { IDScanner } from 'id-scanner-lib';
303
+
304
+ const scanner = new IDScanner({
305
+ // 防伪检测结果回调
306
+ onAntiFakeDetected: (result) => {
307
+ if (result.isAuthentic) {
308
+ console.log('身份证验证通过,检测到的防伪特征:', result.detectedFeatures);
309
+ } else {
310
+ console.log('警告:可能是伪造证件!', result.message);
311
+ // 显示安全提示
312
+ document.getElementById('warning').style.display = 'block';
313
+ }
314
+ }
315
+ });
316
+
317
+ await scanner.initialize();
318
+
319
+ // 方法1:单独进行防伪检测
320
+ const antiFakeResult = await scanner.detectIDCardAntiFake(idCardImage);
321
+ console.log('防伪检测结果:', antiFakeResult);
322
+ console.log('检测置信度:', antiFakeResult.confidence);
323
+
324
+ // 方法2:身份证识别时自动进行防伪检测
325
+ const idInfo = await scanner.processIDCardImage(idCardImage);
326
+ // 防伪检测结果包含在返回的信息中
327
+ if (idInfo.antiFakeResult && idInfo.antiFakeResult.isAuthentic) {
328
+ // 身份证真实,继续处理
329
+ } else {
330
+ // 提示可能为伪造证件
331
+ }
332
+ ```
333
+
185
334
  ### 使用内置演示组件
186
335
 
187
336
  ```javascript
@@ -243,6 +392,23 @@ OCR引擎基于Tesseract.js进行了一系列优化:
243
392
  3. **多线程处理**:使用Web Worker避免主线程阻塞
244
393
  4. **结果缓存**:相同图像指纹不重复计算,提高响应速度
245
394
 
395
+ ### 身份证防伪检测技术
396
+
397
+ 防伪检测模块能识别身份证中的多种防伪特征:
398
+
399
+ 1. **荧光油墨特征**:检测特定区域的荧光反应模式
400
+ 2. **微缩文字**:识别证件上的微小文字,伪造证件难以复制
401
+ 3. **光变图案**:检测特定角度下的光变效果
402
+ 4. **雕刻凹印**:通过纹理检测特定的凹印模式
403
+ 5. **隐形图案**:识别证件上的幽灵图像和隐形水印
404
+
405
+ 算法结合多种图像处理技术:
406
+
407
+ - 特定光谱通道提取与分析
408
+ - 边缘检测与微文字模式识别
409
+ - 对比度与光照调整突出隐形特征
410
+ - 自适应阈值处理增强识别准确度
411
+
246
412
  ### 图像增强算法
247
413
 
248
414
  针对不同场景提供最佳图像处理策略:
@@ -276,24 +442,55 @@ OCR引擎基于Tesseract.js进行了一系列优化:
276
442
 
277
443
  ## 应用场景
278
444
 
279
- - **网上银行身份验证**:快速验证用户身份信息
280
- - **酒店登记系统**:自动录入住客信息
445
+ - **网上银行身份验证**:快速验证用户身份信息,检测伪造证件
446
+ - **酒店登记系统**:自动录入住客信息并验证证件真伪
281
447
  - **自助服务终端**:无需人工,自动处理证件信息
282
- - **企业内部系统**:员工证件信息采集
448
+ - **企业内部系统**:员工证件信息采集与验证
283
449
  - **活动签到系统**:快速扫码签到与证件登记
284
450
 
285
- ## 后续开发计划
451
+ ## 发布指南
452
+
453
+ ### 发布到NPM
454
+
455
+ ```bash
456
+ # 1. 确保版本号正确
457
+ npm version [patch|minor|major]
458
+
459
+ # 2. 构建生产版本
460
+ npm run build:prod
461
+
462
+ # 3. 发布到NPM
463
+ npm publish
286
464
 
287
- - [ ] 身份证防伪识别功能
288
- - [ ] 护照和其他证件支持
289
- - [ ] 离线模型支持
290
- - [ ] 人脸比对功能
291
- - [ ] WebAssembly优化
465
+ # 4. 生成标签
466
+ git push origin --tags
467
+ ```
468
+
469
+ ### 发布到GitHub
470
+
471
+ ```bash
472
+ # 1. 提交代码变更
473
+ git add .
474
+ git commit -m "发布 v1.x.x"
475
+
476
+ # 2. 推送到GitHub
477
+ git push origin main
478
+
479
+ # 3. 创建Release
480
+ # 访问 https://github.com/agions/id-scanner-lib/releases/new
481
+ # 选择对应的标签,填写Release说明
482
+ ```
292
483
 
293
484
  ## 贡献指南
294
485
 
295
486
  欢迎贡献代码、报告问题或提出新功能建议。请通过GitHub Issues或Pull Requests参与项目。
296
487
 
488
+ 1. Fork项目仓库
489
+ 2. 创建你的特性分支 (`git checkout -b feature/amazing-feature`)
490
+ 3. 提交你的更改 (`git commit -m '添加一些很棒的功能'`)
491
+ 4. 推送到分支 (`git push origin feature/amazing-feature`)
492
+ 5. 打开Pull Request
493
+
297
494
  ## 许可证
298
495
 
299
496
  本项目采用MIT许可证。详见[LICENSE](LICENSE)文件。
@@ -303,5 +500,6 @@ OCR引擎基于Tesseract.js进行了一系列优化:
303
500
  <p align="center">
304
501
  <a href="https://github.com/agions/id-scanner-lib">GitHub</a> •
305
502
  <a href="https://www.npmjs.com/package/id-scanner-lib">NPM</a> •
306
- <a href="https://github.com/agions/id-scanner-lib/issues">Issues</a>
503
+ <a href="https://github.com/agions/id-scanner-lib/issues">Issues</a>
504
+ <a href="https://github.com/agions/id-scanner-lib/releases">Releases</a>
307
505
  </p>
@@ -10368,6 +10368,7 @@ function _mergeNamespaces(e,t){return t.forEach((function(t){t&&"string"!=typeof
10368
10368
  * @file 图像处理工具类
10369
10369
  * @description 提供图像预处理功能,用于提高OCR识别率
10370
10370
  * @module ImageProcessor
10371
+ * @version 1.3.2
10371
10372
  */
10372
10373
  /**
10373
10374
  * 图像处理工具类
@@ -10767,6 +10768,278 @@ class ImageProcessor {
10767
10768
  // 获取新的ImageData
10768
10769
  return ctx.getImageData(0, 0, newWidth, newHeight);
10769
10770
  }
10771
+ /**
10772
+ * 边缘检测算法,用于识别图像中的边缘
10773
+ * 基于Sobel算子实现
10774
+ *
10775
+ * @param imageData 原始图像数据,应已转为灰度图
10776
+ * @param threshold 边缘阈值,默认为30
10777
+ * @returns 检测到边缘的图像数据
10778
+ */
10779
+ static detectEdges(imageData, threshold = 30) {
10780
+ // 确保输入图像是灰度图
10781
+ const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
10782
+ const width = grayscaleImage.width;
10783
+ const height = grayscaleImage.height;
10784
+ const inputData = grayscaleImage.data;
10785
+ const outputData = new Uint8ClampedArray(inputData.length);
10786
+ // Sobel算子 - 水平和垂直方向
10787
+ const sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
10788
+ const sobelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
10789
+ // 对每个像素应用Sobel算子
10790
+ for (let y = 1; y < height - 1; y++) {
10791
+ for (let x = 1; x < width - 1; x++) {
10792
+ let gx = 0;
10793
+ let gy = 0;
10794
+ // 应用卷积
10795
+ for (let ky = -1; ky <= 1; ky++) {
10796
+ for (let kx = -1; kx <= 1; kx++) {
10797
+ const pixelPos = ((y + ky) * width + (x + kx)) * 4;
10798
+ const pixelVal = inputData[pixelPos]; // 灰度值
10799
+ const kernelIdx = (ky + 1) * 3 + (kx + 1);
10800
+ gx += pixelVal * sobelX[kernelIdx];
10801
+ gy += pixelVal * sobelY[kernelIdx];
10802
+ }
10803
+ }
10804
+ // 计算梯度强度
10805
+ let magnitude = Math.sqrt(gx * gx + gy * gy);
10806
+ // 应用阈值
10807
+ magnitude = magnitude > threshold ? 255 : 0;
10808
+ // 设置输出像素
10809
+ const pos = (y * width + x) * 4;
10810
+ outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = magnitude;
10811
+ outputData[pos + 3] = 255; // 透明度保持完全不透明
10812
+ }
10813
+ }
10814
+ // 处理边缘像素
10815
+ for (let i = 0; i < width * 4; i++) {
10816
+ // 顶部和底部行
10817
+ outputData[i] = 0;
10818
+ outputData[(height - 1) * width * 4 + i] = 0;
10819
+ }
10820
+ for (let i = 0; i < height; i++) {
10821
+ // 左右两侧列
10822
+ const leftPos = i * width * 4;
10823
+ const rightPos = (i * width + width - 1) * 4;
10824
+ for (let j = 0; j < 4; j++) {
10825
+ outputData[leftPos + j] = 0;
10826
+ outputData[rightPos + j] = 0;
10827
+ }
10828
+ }
10829
+ return new ImageData(outputData, width, height);
10830
+ }
10831
+ /**
10832
+ * 卡尼-德里奇边缘检测
10833
+ * 相比Sobel更精确的边缘检测算法
10834
+ *
10835
+ * @param imageData 灰度图像数据
10836
+ * @param lowThreshold 低阈值
10837
+ * @param highThreshold 高阈值
10838
+ * @returns 边缘检测结果
10839
+ */
10840
+ static cannyEdgeDetection(imageData, lowThreshold = 20, highThreshold = 50) {
10841
+ const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
10842
+ // 1. 高斯模糊
10843
+ const blurredImage = this.gaussianBlur(grayscaleImage, 1.5);
10844
+ // 2. 使用Sobel算子计算梯度
10845
+ const { gradientMagnitude, gradientDirection } = this.computeGradients(blurredImage);
10846
+ // 3. 非极大值抛弃
10847
+ const nonMaxSuppressed = this.nonMaxSuppression(gradientMagnitude, gradientDirection, blurredImage.width, blurredImage.height);
10848
+ // 4. 双阈值处理
10849
+ const thresholdResult = this.hysteresisThresholding(nonMaxSuppressed, blurredImage.width, blurredImage.height, lowThreshold, highThreshold);
10850
+ // 创建输出图像
10851
+ const outputData = new Uint8ClampedArray(imageData.data.length);
10852
+ // 将结果转换为ImageData
10853
+ for (let i = 0; i < thresholdResult.length; i++) {
10854
+ const pos = i * 4;
10855
+ const value = thresholdResult[i] ? 255 : 0;
10856
+ outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = value;
10857
+ outputData[pos + 3] = 255;
10858
+ }
10859
+ return new ImageData(outputData, blurredImage.width, blurredImage.height);
10860
+ }
10861
+ /**
10862
+ * 高斯模糊
10863
+ */
10864
+ static gaussianBlur(imageData, sigma = 1.5) {
10865
+ const width = imageData.width;
10866
+ const height = imageData.height;
10867
+ const inputData = imageData.data;
10868
+ const outputData = new Uint8ClampedArray(inputData.length);
10869
+ // 生成高斯核
10870
+ const kernelSize = Math.max(3, Math.floor(sigma * 3) * 2 + 1);
10871
+ const halfKernel = Math.floor(kernelSize / 2);
10872
+ const kernel = this.generateGaussianKernel(kernelSize, sigma);
10873
+ // 应用高斯核
10874
+ for (let y = 0; y < height; y++) {
10875
+ for (let x = 0; x < width; x++) {
10876
+ let sum = 0;
10877
+ let weightSum = 0;
10878
+ for (let ky = -halfKernel; ky <= halfKernel; ky++) {
10879
+ for (let kx = -halfKernel; kx <= halfKernel; kx++) {
10880
+ const pixelY = Math.min(Math.max(y + ky, 0), height - 1);
10881
+ const pixelX = Math.min(Math.max(x + kx, 0), width - 1);
10882
+ const pixelPos = (pixelY * width + pixelX) * 4;
10883
+ const kernelY = ky + halfKernel;
10884
+ const kernelX = kx + halfKernel;
10885
+ const weight = kernel[kernelY * kernelSize + kernelX];
10886
+ sum += inputData[pixelPos] * weight;
10887
+ weightSum += weight;
10888
+ }
10889
+ }
10890
+ const pos = (y * width + x) * 4;
10891
+ const value = Math.round(sum / weightSum);
10892
+ outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = value;
10893
+ outputData[pos + 3] = 255;
10894
+ }
10895
+ }
10896
+ return new ImageData(outputData, width, height);
10897
+ }
10898
+ /**
10899
+ * 生成高斯核
10900
+ */
10901
+ static generateGaussianKernel(size, sigma) {
10902
+ const kernel = new Array(size * size);
10903
+ const center = Math.floor(size / 2);
10904
+ let sum = 0;
10905
+ for (let y = 0; y < size; y++) {
10906
+ for (let x = 0; x < size; x++) {
10907
+ const distance = Math.sqrt((x - center) ** 2 + (y - center) ** 2);
10908
+ const value = Math.exp(-(distance ** 2) / (2 * sigma ** 2));
10909
+ kernel[y * size + x] = value;
10910
+ sum += value;
10911
+ }
10912
+ }
10913
+ // 归一化
10914
+ for (let i = 0; i < kernel.length; i++) {
10915
+ kernel[i] /= sum;
10916
+ }
10917
+ return kernel;
10918
+ }
10919
+ /**
10920
+ * 计算梯度强度和方向
10921
+ */
10922
+ static computeGradients(imageData) {
10923
+ const width = imageData.width;
10924
+ const height = imageData.height;
10925
+ const inputData = imageData.data;
10926
+ const gradientMagnitude = new Array(width * height);
10927
+ const gradientDirection = new Array(width * height);
10928
+ // Sobel算子
10929
+ const sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
10930
+ const sobelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
10931
+ for (let y = 1; y < height - 1; y++) {
10932
+ for (let x = 1; x < width - 1; x++) {
10933
+ let gx = 0;
10934
+ let gy = 0;
10935
+ for (let ky = -1; ky <= 1; ky++) {
10936
+ for (let kx = -1; kx <= 1; kx++) {
10937
+ const pixelPos = ((y + ky) * width + (x + kx)) * 4;
10938
+ const pixelVal = inputData[pixelPos];
10939
+ const kernelIdx = (ky + 1) * 3 + (kx + 1);
10940
+ gx += pixelVal * sobelX[kernelIdx];
10941
+ gy += pixelVal * sobelY[kernelIdx];
10942
+ }
10943
+ }
10944
+ const idx = y * width + x;
10945
+ gradientMagnitude[idx] = Math.sqrt(gx * gx + gy * gy);
10946
+ gradientDirection[idx] = Math.atan2(gy, gx);
10947
+ }
10948
+ }
10949
+ // 处理边界
10950
+ for (let y = 0; y < height; y++) {
10951
+ for (let x = 0; x < width; x++) {
10952
+ if (y === 0 || y === height - 1 || x === 0 || x === width - 1) {
10953
+ const idx = y * width + x;
10954
+ gradientMagnitude[idx] = 0;
10955
+ gradientDirection[idx] = 0;
10956
+ }
10957
+ }
10958
+ }
10959
+ return { gradientMagnitude, gradientDirection };
10960
+ }
10961
+ /**
10962
+ * 非极大值抛弃
10963
+ */
10964
+ static nonMaxSuppression(gradientMagnitude, gradientDirection, width, height) {
10965
+ const result = new Array(width * height).fill(0);
10966
+ for (let y = 1; y < height - 1; y++) {
10967
+ for (let x = 1; x < width - 1; x++) {
10968
+ const idx = y * width + x;
10969
+ const magnitude = gradientMagnitude[idx];
10970
+ const direction = gradientDirection[idx];
10971
+ // 将方向转化为角度
10972
+ const degrees = (direction * 180 / Math.PI + 180) % 180;
10973
+ // 获取相邻像素索引
10974
+ let neighbor1Idx, neighbor2Idx;
10975
+ // 将方向量化为四个方向: 0°, 45°, 90°, 135°
10976
+ if ((degrees >= 0 && degrees < 22.5) || (degrees >= 157.5 && degrees <= 180)) {
10977
+ // 水平方向
10978
+ neighbor1Idx = idx - 1;
10979
+ neighbor2Idx = idx + 1;
10980
+ }
10981
+ else if (degrees >= 22.5 && degrees < 67.5) {
10982
+ // 45度方向
10983
+ neighbor1Idx = (y - 1) * width + (x + 1);
10984
+ neighbor2Idx = (y + 1) * width + (x - 1);
10985
+ }
10986
+ else if (degrees >= 67.5 && degrees < 112.5) {
10987
+ // 垂直方向
10988
+ neighbor1Idx = (y - 1) * width + x;
10989
+ neighbor2Idx = (y + 1) * width + x;
10990
+ }
10991
+ else {
10992
+ // 135度方向
10993
+ neighbor1Idx = (y - 1) * width + (x - 1);
10994
+ neighbor2Idx = (y + 1) * width + (x + 1);
10995
+ }
10996
+ // 检查当前像素是否是最大值
10997
+ if (magnitude >= gradientMagnitude[neighbor1Idx] &&
10998
+ magnitude >= gradientMagnitude[neighbor2Idx]) {
10999
+ result[idx] = magnitude;
11000
+ }
11001
+ }
11002
+ }
11003
+ return result;
11004
+ }
11005
+ /**
11006
+ * 双阈值处理
11007
+ */
11008
+ static hysteresisThresholding(nonMaxSuppressed, width, height, lowThreshold, highThreshold) {
11009
+ const result = new Array(width * height).fill(false);
11010
+ const visited = new Array(width * height).fill(false);
11011
+ const stack = [];
11012
+ // 标记强边缘点
11013
+ for (let i = 0; i < nonMaxSuppressed.length; i++) {
11014
+ if (nonMaxSuppressed[i] >= highThreshold) {
11015
+ result[i] = true;
11016
+ stack.push(i);
11017
+ visited[i] = true;
11018
+ }
11019
+ }
11020
+ // 使用深度优先搜索连接弱边缘
11021
+ const dx = [-1, 0, 1, -1, 1, -1, 0, 1];
11022
+ const dy = [-1, -1, -1, 0, 0, 1, 1, 1];
11023
+ while (stack.length > 0) {
11024
+ const currentIdx = stack.pop();
11025
+ const currentX = currentIdx % width;
11026
+ const currentY = Math.floor(currentIdx / width);
11027
+ // 检查88个相邻方向
11028
+ for (let i = 0; i < 8; i++) {
11029
+ const newX = currentX + dx[i];
11030
+ const newY = currentY + dy[i];
11031
+ if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
11032
+ const newIdx = newY * width + newX;
11033
+ if (!visited[newIdx] && nonMaxSuppressed[newIdx] >= lowThreshold) {
11034
+ result[newIdx] = true;
11035
+ stack.push(newIdx);
11036
+ visited[newIdx] = true;
11037
+ }
11038
+ }
11039
+ }
11040
+ }
11041
+ return result;
11042
+ }
10770
11043
  }
10771
11044
 
10772
11045
  /**