Introduction
高斯模糊被广泛运用在各种图形处理软件中,如PhotoShop, GIMP,通常用来减少图像噪声以及降低细节层次。从数学的角度来看,图像的高斯模糊过程就是图像与正态分布做卷积。由于正态分布又叫做高斯分布,所以这项技术就叫做高斯模糊。由于高斯函数的傅立叶变换是另外一个高斯函数,所以高斯模糊对于图像来说就是一个低通滤波器。
Principle
模糊的原理是将每一个像素都重置为周边像素的平均值,即会产生模糊效果,中间点失去细节。显然,在计算平均值时,选择的范围越大,模糊效果越强烈。
但平均并不合理,因为图像是连续的,越靠近的点关系约密切,越远离的点关系越疏远,加权平均更为合理。
正态分布显然是一种可取的权重分配方式。对于2d图像,我们需要使用二维正态分布去计算每个点的权重。假设中心点为,那么可以推算出周围半径为的权重矩阵,其中坐上角为,右下角为。
对于该矩阵的某个位置, 我们有
然后再对进行归一化即可得到高斯模糊矩阵,用该矩阵对某个点,及其周围半径为的相同大小的矩阵做卷积,就是该点的高斯模糊值。
对于图像某一方向应用高斯核,我们可以考虑使用一维高斯核,以减少计算复杂度。一维高斯核的计算公式为
在实际计算中,我们往往可以省略归一化常数 (1d) (2d)
Implementation
Gaussian Kernel 1D
// this one omits the normalization factor
func generateGaussianKernel(radius: Int, sigma: Float) -> [Float] {
let size = 2 * radius + 1
var kernel = [Float](repeating: 0.0, count: size)
var sum: Float = 0.0
let s2 = 2.0 * sigma * sigma
for i in -radius...radius {
let value = exp(-(Float(i * i) / s2))
kernel[i + radius] = value
sum += value
}
for i in 0..<size {
kernel[i] /= sum
}
return kernel
}
func generateGaussianKernel(radius: Int, sigma: Float) -> [Float] {
let size = 2 * radius + 1
var kernel = [Float](repeating: 0.0, count: size)
var sum: Float = 0.0
let s2 = 2.0 * sigma * sigma
let normalizationFactor = 1.0 / sqrt(2.0 * Float.pi * sigma * sigma)
for i in -radius...radius {
let value = exp(-(Float(i * i) / s2)) * normalizationFactor
kernel[i + radius] = value
sum += value
}
for i in 0..<size {
kernel[i] /= sum
}
return kernel
}Gaussian Kernel 2D
func gaussianKernel(radius: Int, sigma: Double) -> [[Double]] {
// Ensure size is odd
let size = radius * 2 + 1
let s = 2.0 * sigma * sigma
var sum: Double = 0.0
var kernel: [[Double]] = Array(repeating: Array(repeating: 0.0, count: size), count: size)
for x in -radius...radius {
for y in -radius...radius {
let r = sqrt(Double(x * x + y * y))
kernel[x + radius][y + radius] = (exp(-(r * r) / s)) / (Double.pi * s)
sum += kernel[x + radius][y + radius]
}
}
// Normalize the kernel
for x in 0..<size {
for y in 0..<size {
kernel[x][y] /= sum
}
}
return kernel
}Gaussian Blur
以下为iOS开发中使用Metal Compute Shader去做一维高斯模糊的shader代码
struct GaussianBlurParams {
float2 direction; // (1, 0) for horizontal, (0, 1) for vertical
int radius;
bool clamp; // true for clamp option
bool blurInOneOverZ; // true for 1/Z space blur
};
kernel void gaussianBlur2D(
texture2d<float, access::read> inputTexture [[texture(0)]],
texture2d<float, access::write> outputTexture [[texture(1)]],
constant GaussianBlurParams &uniforms [[buffer(0)]],
constant float* gaussianKernel [[buffer(1)]],
uint2 gid [[thread_position_in_grid]])
{
uint2 size = uint2(inputTexture.get_width(), inputTexture.get_height());
if (gid.x >= size.x || gid.y >= size.y) { return; }
float4 color = float4(0.0);
float sum = 0.0;
float centerDepth = inputTexture.read(gid).w;
if (uniforms.blurInOneOverZ) {
centerDepth = 1.0 / centerDepth;
}
for (int i = -uniforms.radius; i <= uniforms.radius; ++i) {
int2 offset = int2(uniforms.direction * float2(i));
int2 coord = clamp(int2(gid) + offset, int2(0), int2(size) - int2(1));
float4 sample = inputTexture.read(uint2(coord));
float sampleDepth = sample.w;
if (uniforms.blurInOneOverZ) {
sampleDepth = 1.0 / sampleDepth;
}
float weight = gaussianKernel[i + uniforms.radius];
color += sample * weight;
sum += weight;
}
color /= sum;
if (uniforms.clamp) {
float4 centerSample = inputTexture.read(gid);
color = max(centerSample, color);
}
outputTexture.write(color, gid);
}