图片滤波
什么是滤波
图片和波如何结合
如何滤波
方法一:使用AudioContext
滤波这种术语,一般会用在音频处理上,通过修改音频的频率从而达到修改声音的表现。例如AudioContext就有提供滤波函数createBiquadFilter
,其内部提供了一系列的滤波算法,例如高通滤波highpass
,低通滤波lowpass
。所以我们可以利用它的api去处理图片。
上图出处:图像与滤波
从上图可以看出曲线波动的地方就是图片边缘处,色差越大,波动越大。
方法二:通过卷积算法
什么是卷积
卷积应用于图片
定义
卷积运算图
此图出处:onvolutional Neural Networks - Basics
此图出处:如何理解卷积
通过上面两张图中,中间层的是卷积核(模版),可以看出其计算规则。而不同的滤波方式有对应的卷积核。
其实不难看出每个像素点的卷积计算其实都是和自身周围的像素进行计算而得到新的像素值。
卷积核
对应算法实现
function convolution (pixels:ImageData, weights:number[]) {
const side = Math.round(Math.sqrt(weights.length))
const halfSide = Math.floor(side / 2)
const src = pixels.data
const canvasWidth = pixels.width
const canvasHeight = pixels.height
const temporaryCanvas = document.createElement('canvas')
const temporaryCtx = temporaryCanvas.getContext('2d')!
const outputData = temporaryCtx.createImageData(canvasWidth, canvasHeight)
for (let y = 0; y < canvasHeight; y++) {
for (let x = 0; x < canvasWidth; x++) {
const dstOff = (y * canvasWidth + x) * 4
let sumReds = 0
let sumGreens = 0
let sumBlues = 0
for (let kernelY = 0; kernelY < side; kernelY++) {
for (let kernelX = 0; kernelX < side; kernelX++) {
const currentKernelY = y + kernelY - halfSide
const currentKernelX = x + kernelX - halfSide
if (currentKernelY >= 0 &&
currentKernelY < canvasHeight &&
currentKernelX >= 0 &&
currentKernelX < canvasWidth) {
const offset = (currentKernelY * canvasWidth + currentKernelX) * 4
const weight = weights[kernelY * side + kernelX]
sumReds += src[offset] * weight
sumGreens += src[offset + 1] * weight
sumBlues += src[offset + 2] * weight
}
}
}
outputData.data[dstOff] = sumReds
outputData.data[dstOff + 1] = sumGreens
outputData.data[dstOff + 2] = sumBlues
outputData.data[dstOff + 3] = 255
}
}
return outputData
}
低通滤波
低通滤波去掉了高频信息,即细节信息,留下的低频信息代表了概貌。常用的例子,比如美图秀秀的磨皮,去掉了脸部细节信息(痘坑,痘印,暗斑等)。
高通滤波
高通滤波会过滤低频信息,保留高频信息。上面我们说过斜率越高说明颜色差异越大这里往往是图片边缘部分,也表示频率越高。所以只保留高频信号的话,说明只保留了图片的边缘部分。所以高通滤波往往用做边缘处理。
不同的滤波对应卷积核
均值滤波(加权平均模糊)
const weights = [
1/9, 1/9, 1/9,
1/9, 1/9, 1/9,
1/9, 1/9, 1/9,
]
从上面的卷积核可以看出该卷积核的值之和为1,所以的目标是将目标像素点的值取周围的平均数,所以用该卷积核处理图片的结果会是模糊该图片,不过因为使用的是均值,所以图片不会很自然。一般使用高斯模糊滤波用作处理图片模糊。
高斯滤波
正态分布的二维函数如下图:
根据上面的公式可推导出的高斯矩阵方法:
function createGaussWeights (radius:number, sigma:number) {
const gaussMatrix = []
let gaussSum = 0
radius = Math.floor(radius) || 3
sigma = sigma || radius / 3
const a = 1 / 2 * Math.PI * (sigma ** 2)
const b = -1 / (2 * (sigma ** 2))
let i = 0
// 生成高斯矩阵
for (let y = radius; y >= -radius; y--) {
for (let x = -radius; x <= radius; x++) {
const g = a * Math.exp(b * (x ** 2 + y ** 2))
gaussMatrix[i++] = g
gaussSum += g
}
}
// 归一化
for (let i = 0, len = gaussMatrix.length; i < len; i++) {
gaussMatrix[i] /= gaussSum
}
return gaussMatrix
}
下图是3*3,模糊半径1.5的高斯模糊矩阵
高斯模糊属于什么滤波?
这里高斯模糊和均值滤波都属于低通滤波,为什么呢?
因为它们的本质就是将目标像素取周围像素的平均值,所以边缘的差异性会降低。我们上面说过频率越高说明颜色差异越大,反过来颜色差异降低的话,频率也就降低了,相当于过滤了高频部分了。让图片更加平滑
highpass高通滤波
const weights = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1,
]
这个是高通滤波的卷积核,可以看出是中间为正数,其余8个为-1,它们之和为0,经过该滤波器,可以看出如果中间的值比四周的大,则会得到放大期和周围的差值,相反比四周的小的话,则得到的是负数,即黑色。这样就会得到图片的边缘图。
laplacian拉普拉斯滤波
const weights = [
0, -1, 0,
-1, 4, -1,
0, -1, 0,
]
拉普拉斯滤波也属于高通滤波,不过它比较的像素点是上下左右四个,不过最终还是能得到图片的边缘效果
应用场景
识别滑块图形验证码
操作步骤如下
- 使用高斯模糊去除图片噪点
- 使用高通滤波获取滑块位置的边缘
- 调节图片亮度,过滤无用边缘,方便后面计算
- 扫描图片像素点,筛选出亮度>10且连续最多的x坐标
制作各式各样滤镜的图片
- 高斯模糊
- 图片锐化(高通滤波)
- 图片浮雕(浮雕滤波器)
- 运动模糊
// 浮雕滤波器
[
-1, -1, 0,
-1, 0, 1,
0, 1, 1
]
// 运动模糊
[
1/3, 0, 0,
0, 1/3, 0,
0, 0, 1/3,
]