# 实践-边缘识别
图像梯度用于边缘检测。边缘是像素值发生跃迁的地方,是图像的显著特征之一,在图像特征提取、目标检测等方面都有重要的作用。
图像中有灰度值的变化就会有梯度,从而产生边缘,在边缘处,具有变化的强弱及方向。这时一些常见的图像识别算法的基础,比如 hog,sift,都是基于梯度的。
我们利用卷积和特定的算子来计算相邻像素的变化率。 prewitt
算子和 sobel
算子可以计算相邻三个点之间的变化率。它们用于一阶算子的边缘检测,利用像素点上下、左右相邻点的灰度差求取边缘。
# 卷积核
求梯度有三种卷积核( robert
,prewitt
,sobel
算子),每种卷积核有两个,对图像分别做两次卷积,一个代表水平梯度,一个代表垂直梯度。
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);
}