# 实践-边缘识别

图像梯度用于边缘检测。边缘是像素值发生跃迁的地方,是图像的显著特征之一,在图像特征提取、目标检测等方面都有重要的作用。

图像中有灰度值的变化就会有梯度,从而产生边缘,在边缘处,具有变化的强弱及方向。这时一些常见的图像识别算法的基础,比如 hog,sift,都是基于梯度的。

我们利用卷积和特定的算子来计算相邻像素的变化率。 prewitt 算子和 sobel 算子可以计算相邻三个点之间的变化率。它们用于一阶算子的边缘检测,利用像素点上下、左右相邻点的灰度差求取边缘。

# 卷积核

求梯度有三种卷积核( robertprewittsobel算子),每种卷积核有两个,对图像分别做两次卷积,一个代表水平梯度,一个代表垂直梯度。

robert 增强正负 45 度的图像边缘

-1  0        0  -1
 0  1        1   0

prewitt

-1  0  1    -1 -1 -1
-1  0  1     0  0  0
-1  0  1     1  1  1

sobel

-1  0  1    -1 -2 -1
-2  0  2     0  0  0
-1  0  1     1  2  1

在图像处理过程中,除了检测线,有时候也需要检测特殊点,这就需要用二阶导数进行检测,著名的就是拉普拉斯(Laplacian)算子

 0 -1  0      0  1  0      1  1  1      -1 -1 -1
-1  4 -1  or  1 -4  1  or  1 -8  1  or  -1  8 -1 
 0 -1  0      0  1  0      1  1  1      -1 -1 -1

# Padding

为了对图像进行卷积操作,我们首先在图像最外层追加一层像素,这层像素通常是rgba(0,0,0,0),这个操作叫做 padding。

# 预览效果

# 实现代码

const ctxSource = document.getElementById("canvas-source").getContext("2d");
const ctxPadding = document.getElementById("canvas-padding").getContext("2d");
const ctxPH = document.getElementById("prewitt-h").getContext("2d");
const ctxPV = document.getElementById("prewitt-v").getContext("2d");
const ctxSH = document.getElementById("sobel-h").getContext("2d");
const ctxSV = document.getElementById("sobel-v").getContext("2d");
const ctxLPLS = document.getElementById("laplacian").getContext("2d");
const img = new Image();
img.src =
    "https://www.gausszhou.top/static/image/learn/webgl/leaves.jpg";
img.crossOrigin = "";
img.onload = () => {
  draw();
};
const prewittH = [
  [-1, -1, -1],
  [0, 0, 0],
  [1, 1, 1]
];
const prewittV = [
  [-1, 0, 1],
  [-1, 0, 1],
  [-1, 0, 1]
];
const sobelH = [
  [-1, -2, -1],
  [0, 0, 0],
  [1, 2, 1]
];
const sobelV = [
  [-1, 0, 1],
  [-2, 0, 2],
  [-1, 0, 1]
];
const LPLS = [
  [-1, -1, -1],
  [-1, 8, -1],
  [-1, -1, -1]
];

function draw() {
  ctxSource.drawImage(img, 0, 0,400, 400);
  ctxPadding.drawImage(img, 1, 1,400, 400);
  const imageDataP = ctxPadding.getImageData(0, 0, 402, 402);
  const grayBuffer = new Uint8Array(402 * 402);
  for (let i = 0; i < 402; i++) {
    for (let j = 0; j < 402; j++) {
      let index = 402 * i + j;
      let PIndex = index * 4;
      let r = imageDataP.data[PIndex];
      let g = imageDataP.data[PIndex + 1];
      let b = imageDataP.data[PIndex + 2];
      let gray = Math.floor((r + g + b) / 3);
      grayBuffer[index] = gray;
    }
  }
  function calc(operator, imageData) {
    for (let i = 0; i < 400; i++) {
      for (let j = 0; j < 400; j++) {
        let grayIndex = 402 * (i + 1) + j + 1;
        let HIndex = (400 * i + j) * 4;
        // 卷积
        let result =
          grayBuffer[grayIndex - 402 - 1] * operator[0][0] +
          grayBuffer[grayIndex - 402] * operator[0][1] +
          grayBuffer[grayIndex - 402 + 1] * operator[0][2] +
          grayBuffer[grayIndex - 1] * operator[1][0] +
          grayBuffer[grayIndex] * operator[1][1] +
          grayBuffer[grayIndex + 1] * operator[1][2] +
          grayBuffer[grayIndex + 402 - 1] * operator[2][0] +
          grayBuffer[grayIndex + 402] * operator[2][1] +
          grayBuffer[grayIndex + 402 + 1] * operator[2][2];

        imageData.data[HIndex] = result;
        imageData.data[HIndex + 1] = result;
        imageData.data[HIndex + 2] = result;
        imageData.data[HIndex + 3] = 255;
      }
    }
  }
  const imageDataPH = ctxPadding.createImageData(400, 400);
  const imageDataPV = ctxPadding.createImageData(400, 400);
  const imageDataSH = ctxPadding.createImageData(400, 400);
  const imageDataSV = ctxPadding.createImageData(400, 400);
  const imageDataLPLS = ctxPadding.createImageData(400, 400);
  calc(prewittH, imageDataPH);
  calc(prewittV, imageDataPV);
  calc(sobelH, imageDataSH);
  calc(sobelV, imageDataSV);
  calc(LPLS, imageDataLPLS);
  ctxPH.putImageData(imageDataPH, 0, 0);
  ctxPV.putImageData(imageDataPV, 0, 0);
  ctxSH.putImageData(imageDataSH, 0, 0);
  ctxSV.putImageData(imageDataSV, 0, 0);
  ctxLPLS.putImageData(imageDataLPLS, 0, 0);
}