id-scanner-lib 1.3.2 → 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.
@@ -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
  /**
@@ -10374,6 +10374,7 @@
10374
10374
  * @file 图像处理工具类
10375
10375
  * @description 提供图像预处理功能,用于提高OCR识别率
10376
10376
  * @module ImageProcessor
10377
+ * @version 1.3.2
10377
10378
  */
10378
10379
  /**
10379
10380
  * 图像处理工具类
@@ -10773,6 +10774,278 @@
10773
10774
  // 获取新的ImageData
10774
10775
  return ctx.getImageData(0, 0, newWidth, newHeight);
10775
10776
  }
10777
+ /**
10778
+ * 边缘检测算法,用于识别图像中的边缘
10779
+ * 基于Sobel算子实现
10780
+ *
10781
+ * @param imageData 原始图像数据,应已转为灰度图
10782
+ * @param threshold 边缘阈值,默认为30
10783
+ * @returns 检测到边缘的图像数据
10784
+ */
10785
+ static detectEdges(imageData, threshold = 30) {
10786
+ // 确保输入图像是灰度图
10787
+ const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
10788
+ const width = grayscaleImage.width;
10789
+ const height = grayscaleImage.height;
10790
+ const inputData = grayscaleImage.data;
10791
+ const outputData = new Uint8ClampedArray(inputData.length);
10792
+ // Sobel算子 - 水平和垂直方向
10793
+ const sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
10794
+ const sobelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
10795
+ // 对每个像素应用Sobel算子
10796
+ for (let y = 1; y < height - 1; y++) {
10797
+ for (let x = 1; x < width - 1; x++) {
10798
+ let gx = 0;
10799
+ let gy = 0;
10800
+ // 应用卷积
10801
+ for (let ky = -1; ky <= 1; ky++) {
10802
+ for (let kx = -1; kx <= 1; kx++) {
10803
+ const pixelPos = ((y + ky) * width + (x + kx)) * 4;
10804
+ const pixelVal = inputData[pixelPos]; // 灰度值
10805
+ const kernelIdx = (ky + 1) * 3 + (kx + 1);
10806
+ gx += pixelVal * sobelX[kernelIdx];
10807
+ gy += pixelVal * sobelY[kernelIdx];
10808
+ }
10809
+ }
10810
+ // 计算梯度强度
10811
+ let magnitude = Math.sqrt(gx * gx + gy * gy);
10812
+ // 应用阈值
10813
+ magnitude = magnitude > threshold ? 255 : 0;
10814
+ // 设置输出像素
10815
+ const pos = (y * width + x) * 4;
10816
+ outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = magnitude;
10817
+ outputData[pos + 3] = 255; // 透明度保持完全不透明
10818
+ }
10819
+ }
10820
+ // 处理边缘像素
10821
+ for (let i = 0; i < width * 4; i++) {
10822
+ // 顶部和底部行
10823
+ outputData[i] = 0;
10824
+ outputData[(height - 1) * width * 4 + i] = 0;
10825
+ }
10826
+ for (let i = 0; i < height; i++) {
10827
+ // 左右两侧列
10828
+ const leftPos = i * width * 4;
10829
+ const rightPos = (i * width + width - 1) * 4;
10830
+ for (let j = 0; j < 4; j++) {
10831
+ outputData[leftPos + j] = 0;
10832
+ outputData[rightPos + j] = 0;
10833
+ }
10834
+ }
10835
+ return new ImageData(outputData, width, height);
10836
+ }
10837
+ /**
10838
+ * 卡尼-德里奇边缘检测
10839
+ * 相比Sobel更精确的边缘检测算法
10840
+ *
10841
+ * @param imageData 灰度图像数据
10842
+ * @param lowThreshold 低阈值
10843
+ * @param highThreshold 高阈值
10844
+ * @returns 边缘检测结果
10845
+ */
10846
+ static cannyEdgeDetection(imageData, lowThreshold = 20, highThreshold = 50) {
10847
+ const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
10848
+ // 1. 高斯模糊
10849
+ const blurredImage = this.gaussianBlur(grayscaleImage, 1.5);
10850
+ // 2. 使用Sobel算子计算梯度
10851
+ const { gradientMagnitude, gradientDirection } = this.computeGradients(blurredImage);
10852
+ // 3. 非极大值抛弃
10853
+ const nonMaxSuppressed = this.nonMaxSuppression(gradientMagnitude, gradientDirection, blurredImage.width, blurredImage.height);
10854
+ // 4. 双阈值处理
10855
+ const thresholdResult = this.hysteresisThresholding(nonMaxSuppressed, blurredImage.width, blurredImage.height, lowThreshold, highThreshold);
10856
+ // 创建输出图像
10857
+ const outputData = new Uint8ClampedArray(imageData.data.length);
10858
+ // 将结果转换为ImageData
10859
+ for (let i = 0; i < thresholdResult.length; i++) {
10860
+ const pos = i * 4;
10861
+ const value = thresholdResult[i] ? 255 : 0;
10862
+ outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = value;
10863
+ outputData[pos + 3] = 255;
10864
+ }
10865
+ return new ImageData(outputData, blurredImage.width, blurredImage.height);
10866
+ }
10867
+ /**
10868
+ * 高斯模糊
10869
+ */
10870
+ static gaussianBlur(imageData, sigma = 1.5) {
10871
+ const width = imageData.width;
10872
+ const height = imageData.height;
10873
+ const inputData = imageData.data;
10874
+ const outputData = new Uint8ClampedArray(inputData.length);
10875
+ // 生成高斯核
10876
+ const kernelSize = Math.max(3, Math.floor(sigma * 3) * 2 + 1);
10877
+ const halfKernel = Math.floor(kernelSize / 2);
10878
+ const kernel = this.generateGaussianKernel(kernelSize, sigma);
10879
+ // 应用高斯核
10880
+ for (let y = 0; y < height; y++) {
10881
+ for (let x = 0; x < width; x++) {
10882
+ let sum = 0;
10883
+ let weightSum = 0;
10884
+ for (let ky = -halfKernel; ky <= halfKernel; ky++) {
10885
+ for (let kx = -halfKernel; kx <= halfKernel; kx++) {
10886
+ const pixelY = Math.min(Math.max(y + ky, 0), height - 1);
10887
+ const pixelX = Math.min(Math.max(x + kx, 0), width - 1);
10888
+ const pixelPos = (pixelY * width + pixelX) * 4;
10889
+ const kernelY = ky + halfKernel;
10890
+ const kernelX = kx + halfKernel;
10891
+ const weight = kernel[kernelY * kernelSize + kernelX];
10892
+ sum += inputData[pixelPos] * weight;
10893
+ weightSum += weight;
10894
+ }
10895
+ }
10896
+ const pos = (y * width + x) * 4;
10897
+ const value = Math.round(sum / weightSum);
10898
+ outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = value;
10899
+ outputData[pos + 3] = 255;
10900
+ }
10901
+ }
10902
+ return new ImageData(outputData, width, height);
10903
+ }
10904
+ /**
10905
+ * 生成高斯核
10906
+ */
10907
+ static generateGaussianKernel(size, sigma) {
10908
+ const kernel = new Array(size * size);
10909
+ const center = Math.floor(size / 2);
10910
+ let sum = 0;
10911
+ for (let y = 0; y < size; y++) {
10912
+ for (let x = 0; x < size; x++) {
10913
+ const distance = Math.sqrt((x - center) ** 2 + (y - center) ** 2);
10914
+ const value = Math.exp(-(distance ** 2) / (2 * sigma ** 2));
10915
+ kernel[y * size + x] = value;
10916
+ sum += value;
10917
+ }
10918
+ }
10919
+ // 归一化
10920
+ for (let i = 0; i < kernel.length; i++) {
10921
+ kernel[i] /= sum;
10922
+ }
10923
+ return kernel;
10924
+ }
10925
+ /**
10926
+ * 计算梯度强度和方向
10927
+ */
10928
+ static computeGradients(imageData) {
10929
+ const width = imageData.width;
10930
+ const height = imageData.height;
10931
+ const inputData = imageData.data;
10932
+ const gradientMagnitude = new Array(width * height);
10933
+ const gradientDirection = new Array(width * height);
10934
+ // Sobel算子
10935
+ const sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
10936
+ const sobelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
10937
+ for (let y = 1; y < height - 1; y++) {
10938
+ for (let x = 1; x < width - 1; x++) {
10939
+ let gx = 0;
10940
+ let gy = 0;
10941
+ for (let ky = -1; ky <= 1; ky++) {
10942
+ for (let kx = -1; kx <= 1; kx++) {
10943
+ const pixelPos = ((y + ky) * width + (x + kx)) * 4;
10944
+ const pixelVal = inputData[pixelPos];
10945
+ const kernelIdx = (ky + 1) * 3 + (kx + 1);
10946
+ gx += pixelVal * sobelX[kernelIdx];
10947
+ gy += pixelVal * sobelY[kernelIdx];
10948
+ }
10949
+ }
10950
+ const idx = y * width + x;
10951
+ gradientMagnitude[idx] = Math.sqrt(gx * gx + gy * gy);
10952
+ gradientDirection[idx] = Math.atan2(gy, gx);
10953
+ }
10954
+ }
10955
+ // 处理边界
10956
+ for (let y = 0; y < height; y++) {
10957
+ for (let x = 0; x < width; x++) {
10958
+ if (y === 0 || y === height - 1 || x === 0 || x === width - 1) {
10959
+ const idx = y * width + x;
10960
+ gradientMagnitude[idx] = 0;
10961
+ gradientDirection[idx] = 0;
10962
+ }
10963
+ }
10964
+ }
10965
+ return { gradientMagnitude, gradientDirection };
10966
+ }
10967
+ /**
10968
+ * 非极大值抛弃
10969
+ */
10970
+ static nonMaxSuppression(gradientMagnitude, gradientDirection, width, height) {
10971
+ const result = new Array(width * height).fill(0);
10972
+ for (let y = 1; y < height - 1; y++) {
10973
+ for (let x = 1; x < width - 1; x++) {
10974
+ const idx = y * width + x;
10975
+ const magnitude = gradientMagnitude[idx];
10976
+ const direction = gradientDirection[idx];
10977
+ // 将方向转化为角度
10978
+ const degrees = (direction * 180 / Math.PI + 180) % 180;
10979
+ // 获取相邻像素索引
10980
+ let neighbor1Idx, neighbor2Idx;
10981
+ // 将方向量化为四个方向: 0°, 45°, 90°, 135°
10982
+ if ((degrees >= 0 && degrees < 22.5) || (degrees >= 157.5 && degrees <= 180)) {
10983
+ // 水平方向
10984
+ neighbor1Idx = idx - 1;
10985
+ neighbor2Idx = idx + 1;
10986
+ }
10987
+ else if (degrees >= 22.5 && degrees < 67.5) {
10988
+ // 45度方向
10989
+ neighbor1Idx = (y - 1) * width + (x + 1);
10990
+ neighbor2Idx = (y + 1) * width + (x - 1);
10991
+ }
10992
+ else if (degrees >= 67.5 && degrees < 112.5) {
10993
+ // 垂直方向
10994
+ neighbor1Idx = (y - 1) * width + x;
10995
+ neighbor2Idx = (y + 1) * width + x;
10996
+ }
10997
+ else {
10998
+ // 135度方向
10999
+ neighbor1Idx = (y - 1) * width + (x - 1);
11000
+ neighbor2Idx = (y + 1) * width + (x + 1);
11001
+ }
11002
+ // 检查当前像素是否是最大值
11003
+ if (magnitude >= gradientMagnitude[neighbor1Idx] &&
11004
+ magnitude >= gradientMagnitude[neighbor2Idx]) {
11005
+ result[idx] = magnitude;
11006
+ }
11007
+ }
11008
+ }
11009
+ return result;
11010
+ }
11011
+ /**
11012
+ * 双阈值处理
11013
+ */
11014
+ static hysteresisThresholding(nonMaxSuppressed, width, height, lowThreshold, highThreshold) {
11015
+ const result = new Array(width * height).fill(false);
11016
+ const visited = new Array(width * height).fill(false);
11017
+ const stack = [];
11018
+ // 标记强边缘点
11019
+ for (let i = 0; i < nonMaxSuppressed.length; i++) {
11020
+ if (nonMaxSuppressed[i] >= highThreshold) {
11021
+ result[i] = true;
11022
+ stack.push(i);
11023
+ visited[i] = true;
11024
+ }
11025
+ }
11026
+ // 使用深度优先搜索连接弱边缘
11027
+ const dx = [-1, 0, 1, -1, 1, -1, 0, 1];
11028
+ const dy = [-1, -1, -1, 0, 0, 1, 1, 1];
11029
+ while (stack.length > 0) {
11030
+ const currentIdx = stack.pop();
11031
+ const currentX = currentIdx % width;
11032
+ const currentY = Math.floor(currentIdx / width);
11033
+ // 检查88个相邻方向
11034
+ for (let i = 0; i < 8; i++) {
11035
+ const newX = currentX + dx[i];
11036
+ const newY = currentY + dy[i];
11037
+ if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
11038
+ const newIdx = newY * width + newX;
11039
+ if (!visited[newIdx] && nonMaxSuppressed[newIdx] >= lowThreshold) {
11040
+ result[newIdx] = true;
11041
+ stack.push(newIdx);
11042
+ visited[newIdx] = true;
11043
+ }
11044
+ }
11045
+ }
11046
+ }
11047
+ return result;
11048
+ }
10776
11049
  }
10777
11050
 
10778
11051
  /**