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);
 
}

References

图像处理(7)—高斯模糊原理-CSDN博客