本文首先介绍了高斯模糊算法然后叙述了线性渐变的高斯模糊算法的原理,最后通过一个小demo展示了如何实现y方向上线性渐变的高斯模糊效果。
1. 高斯模糊算法
高斯模糊是图像处理中一种常用的模糊技术,它通过数学上的高斯函数来实现对图像的平滑处理。这种模糊效果看起来像是图像被轻微地"散开",能够有效地减少图像噪声和细节,从而产生一种柔和的视觉效果。高斯模糊原理详细说明如下:
高斯函数
高斯函数(或高斯分布)是一种钟形的、平滑的曲线,其数学表达式为:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> G ( x ) = 1 2 π σ 2 e − x 2 2 σ 2 G(x) = \frac{1}{\sqrt{2\pi\sigma^2}} e^{-\frac{x^2}{2\sigma^2}} </math>G(x)=2πσ2 1e−2σ2x2
其中, <math xmlns="http://www.w3.org/1998/Math/MathML"> σ \sigma </math>σ 是标准差,决定了曲线的宽度。在图像处理中,高斯函数用于创建一个称为"高斯核"的权重矩阵。
高斯核
高斯核是一个二维数组,用于根据高斯函数的值来给图像中的每个像素及其邻域像素分配权重。核的中心值最大,表示对当前像素点的权重最高,而远离中心的像素权重逐渐减小。
模糊过程
在进行高斯模糊时,算法会遍历图像的每个像素点,并对每个像素及其周围的像素应用高斯核。具体操作如下:
- 遍历像素点: 对图像中的每个像素点进行遍历。
- 应用高斯核: 对于每个像素点,将其邻域内的像素值与高斯核中对应位置的权重相乘,并对结果求和。
- 更新像素值: 将上述步骤得到的加权和作为新的像素值。这样,每个像素点的新值是其本身及邻域像素值的加权平均。
模糊效果
应用高斯模糊后,由于邻域像素的加权平均作用,图像中的每个像素都会与周围像素融合,从而产生平滑和模糊的效果。这种模糊处理可以减少图像的细节和噪声,使图像看起来更加柔和。
应用场景
高斯模糊在许多领域都有应用,包括但不限于:
- 图像美化: 使图像看起来更柔和、自然。
- 去除噪声: 减少图像的随机噪声。
- 图像分割: 在图像处理的预处理阶段,为了更好地进行边缘检测或者特征提取。
高斯模糊的关键在于合理选择高斯核的大小( <math xmlns="http://www.w3.org/1998/Math/MathML"> σ \sigma </math>σ值),这直接影响到模糊的程度和效果。
2. 有梯度的高斯模糊效果的实现
要在y方向上实现有梯度的高斯模糊,可以通过动态调整高斯核的标准差(σ值)来实现。具体来说,随着y坐标的变化,逐渐增加或减少σ值,从而在垂直方向上产生渐变的模糊效果。
1. 动态调整σ值
对于图像中的每一行,根据其y坐标动态计算σ值。可以设计一个函数,使σ值从图像顶部到底部线性增加或减少。
2. 生成高斯核
针对每一行的σ值,生成对应的高斯核。由于每行的σ值不同,所以每行的高斯核也将不同。
3. 应用高斯模糊
使用生成的高斯核对每一行进行模糊处理。具体来说,对于图像中的每个像素,使用其所在行的高斯核进行加权平均计算,得到新的像素值。
4. 实现梯度效果
由于每行的模糊程度随σ值变化,因此整个图像在垂直方向上将呈现出渐变的高斯模糊效果。
5. 伪代码
javascript
function applyGradientGaussianBlur(ctx, width, height) {
const copy = ctx.getImageData(0, 0, width, height);
const copyData = copy.data;
for (let y = 0; y < height; y++) {
const sigma = calculateSigmaForRow(y, height);
const kernel = getGaussianKernel(sigma);
// 应用高斯模糊到这一行...
}
ctx.putImageData(copy, 0, 0);
}
function calculateSigmaForRow(row, height) {
const maxSigma = 5; // 假设最大sigma值为5
return maxSigma * (row / height); // 使sigma值随y坐标线性增加
}
function getGaussianKernel(sigma) {
// 根据sigma值生成高斯核...
}
这个函数会根据y坐标的变化来调整σ值,并为每一行生成相应的高斯核,从而实现在y方向上的渐变高斯模糊效果。
3. 构建测试框架
为了对比采用线性渐变的高斯模糊效果,需要搭建一个简单的html文件用来对比应用前后的效果:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>高斯模糊示例</title>
<style>
body {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<div style="margin-right: 50px;">
<h1>模糊之后</h1>
<canvas id="canvas"></canvas>
</div>
<div>
<h1>模糊之前</h1>
<canvas id="canvas2"></canvas>
</div>
<script>
window.onload = function () {
const canvas = document.getElementById('canvas');
const canvas2 = document.getElementById('canvas2');
const ctx = canvas.getContext('2d');
const ctx2 = canvas2.getContext('2d');
const img = new Image();
img.src = 'demo.jpg'; // 替换为你的图片路径
img.onload = function () {
...
};
};
function applyGaussianBlur(ctx, width, height, pos) {
...
}
function calculateSigmaForRow(row, height) {
...
}
function getGaussianKernel(size, sigma) {
...
}
</script>
</body>
</html>
4. 详细解释
这段代码实现了一个带有渐变高斯模糊效果的图像处理示例。下面分别对代码中的关键部分进行解释:
img.onload
回调函数
当图像加载完成后,这个回调函数被触发。它首先设置画布的尺寸以匹配图像的尺寸。接着,它通过一个循环,分块地将图像绘制到画布上,同时应用不同程度的模糊效果。这种逐渐增加模糊度的效果通过调整 ctx.filter
属性来实现。
js
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
canvas2.width = img.width;
canvas2.height = img.height;
// ctx.drawImage(img, 0, 0);
// 分10份逐份绘制
const numOfParts = 200;
const pos = 3 / 2;
const parts = 20;
const partHeight = img.height / numOfParts;
for (let i = 0; i < numOfParts; i++) {
if(i>numOfParts/pos){
ctx.filter = `blur(${1.5*(i/parts - numOfParts/pos/parts)}px)`;
console.log('i/parts: ', i/parts - numOfParts/pos/parts)
} else {
ctx.filter = `blur(${0}px)`;
}
ctx.drawImage(img, 0, i * partHeight, img.width, partHeight, 0, i * partHeight, img.width, partHeight);
}
ctx2.drawImage(img, 0, 0);
applyGaussianBlur(ctx, img.width, img.height, pos);
};
applyGaussianBlur
函数
这个函数实现了高斯模糊算法。它接收一个绘图上下文(ctx
),图像的宽度和高度,以及一个控制模糊范围的位置参数(pos
)。函数首先获取画布上当前的图像数据。然后,它对每个像素应用高斯模糊,基于高斯核和每个像素周围的像素值来计算新的像素值。
js
function applyGaussianBlur(ctx, width, height, pos) {
const copy = ctx.getImageData(0, 0, width, height);
const copyData = copy.data;
const kernelSize = 16; // 高斯核的固定大小
for (let y = 0; y > height/pos; y++) {
// 对于每一行,根据行号计算sigma值
const sigma = 5 * calculateSigmaForRow(y, height);
const kernel = getGaussianKernel(kernelSize, sigma);
const half = Math.floor(kernelSize / 2);
for (let x = 0; x < width; x++) {
let r = 0, g = 0, b = 0, total = 0;
for (let ky = -half; ky <= half; ky++) {
for (let kx = -half; kx <= half; kx++) {
const px = x + kx;
const py = y + ky;
if (px >= 0 && px < width && py >= 0 && py < height) {
const p = (py * width + px) * 4;
const weight = kernel[ky + half][kx + half];
r += copyData[p] * weight;
g += copyData[p + 1] * weight;
b += copyData[p + 2] * weight;
total += weight;
}
}
}
const pos = (y * width + x) * 4;
copyData[pos] = r / total;
copyData[pos + 1] = g / total;
copyData[pos + 2] = b / total;
}
}
ctx.putImageData(copy, 0, 0);
}
calculateSigmaForRow
函数
这个函数根据行号和图像高度计算高斯模糊的 sigma
值。这里使用了一个简单的线性关系来确定 sigma
的大小,从而创建一种模糊程度随位置变化的效果。
js
function calculateSigmaForRow(row, height) {
// 示例:线性增长的sigma
// 你可以根据需要调整这个函数来改变sigma的计算方式
const maxSigma = 15; // 最大sigma值
return maxSigma * (row / height);
}
getGaussianKernel
函数
此函数生成一个高斯核,这是实现高斯模糊的关键。高斯核是一个二维数组,表示每个像素与其周围像素的权重。这个核在 applyGaussianBlur
函数中用于计算每个像素的新值。
js
function getGaussianKernel(size, sigma) {
const kernel = [];
let sum = 0;
const half = Math.floor(size / 2);
for (let y = -half; y <= half; y++) {
kernel[y + half] = [];
for (let x = -half; x <= half; x++) {
const value = Math.exp(-(x * x + y * y) / (2 * sigma * sigma)) / (2 * Math.PI * sigma * sigma);
kernel[y + half][x + half] = value;
sum += value;
}
}
// 归一化核
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
kernel[y][x] /= sum;
}
}
return kernel;
}
整体思路
代码的主要思想是在图像的一部分区域实现渐变的高斯模糊效果。这通过分段绘制图像并逐步增加模糊程度来实现。img.onload
回调函数处理图像的加载和初步处理,而 applyGaussianBlur
函数则负责应用高斯模糊算法。calculateSigmaForRow
和 getGaussianKernel
函数提供了实现高斯模糊所需的数学计算。
5. 完整代码及效果展示
效果展示

完整代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>高斯模糊示例</title>
<style>
body {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<div style="margin-right: 50px;">
<h1>模糊之后</h1>
<canvas id="canvas"></canvas>
</div>
<div>
<h1>模糊之前</h1>
<canvas id="canvas2"></canvas>
</div>
<script>
window.onload = function () {
const canvas = document.getElementById('canvas');
const canvas2 = document.getElementById('canvas2');
const ctx = canvas.getContext('2d');
const ctx2 = canvas2.getContext('2d');
const img = new Image();
img.src = 'demo.jpg'; // 替换为你的图片路径
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
canvas2.width = img.width;
canvas2.height = img.height;
// ctx.drawImage(img, 0, 0);
// 分10份逐份绘制
const numOfParts = 200;
const pos = 3 / 2;
const parts = 20;
const partHeight = img.height / numOfParts;
for (let i = 0; i < numOfParts; i++) {
if(i>numOfParts/pos){
ctx.filter = `blur(${1.5*(i/parts - numOfParts/pos/parts)}px)`;
console.log('i/parts: ', i/parts - numOfParts/pos/parts)
} else {
ctx.filter = `blur(${0}px)`;
}
ctx.drawImage(img, 0, i * partHeight, img.width, partHeight, 0, i * partHeight, img.width, partHeight);
}
ctx2.drawImage(img, 0, 0);
applyGaussianBlur(ctx, img.width, img.height, pos);
};
};
function applyGaussianBlur(ctx, width, height, pos) {
const copy = ctx.getImageData(0, 0, width, height);
const copyData = copy.data;
const kernelSize = 16; // 高斯核的固定大小
for (let y = 0; y > height/pos; y++) {
// 对于每一行,根据行号计算sigma值
const sigma = 5 * calculateSigmaForRow(y, height);
const kernel = getGaussianKernel(kernelSize, sigma);
const half = Math.floor(kernelSize / 2);
for (let x = 0; x < width; x++) {
let r = 0, g = 0, b = 0, total = 0;
for (let ky = -half; ky <= half; ky++) {
for (let kx = -half; kx <= half; kx++) {
const px = x + kx;
const py = y + ky;
if (px >= 0 && px < width && py >= 0 && py < height) {
const p = (py * width + px) * 4;
const weight = kernel[ky + half][kx + half];
r += copyData[p] * weight;
g += copyData[p + 1] * weight;
b += copyData[p + 2] * weight;
total += weight;
}
}
}
const pos = (y * width + x) * 4;
copyData[pos] = r / total;
copyData[pos + 1] = g / total;
copyData[pos + 2] = b / total;
}
}
ctx.putImageData(copy, 0, 0);
}
function calculateSigmaForRow(row, height) {
// 示例:线性增长的sigma
// 你可以根据需要调整这个函数来改变sigma的计算方式
const maxSigma = 15; // 最大sigma值
return maxSigma * (row / height);
}
function getGaussianKernel(size, sigma) {
const kernel = [];
let sum = 0;
const half = Math.floor(size / 2);
for (let y = -half; y <= half; y++) {
kernel[y + half] = [];
for (let x = -half; x <= half; x++) {
const value = Math.exp(-(x * x + y * y) / (2 * sigma * sigma)) / (2 * Math.PI * sigma * sigma);
kernel[y + half][x + half] = value;
sum += value;
}
}
// 归一化核
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
kernel[y][x] /= sum;
}
}
return kernel;
}
</script>
</body>
</html>