图像高斯模糊算法的原理及实现

gussian-blur-feature1

如果经常使用Photoshop等修图软件,那么对高斯模糊滤镜肯定不会陌生,通过调整模糊半径,图像会变得模糊,半径越大越模糊,这篇文章将会讲解高斯模糊算法的原理以及其Java实现。

gaussianblur

上图是Photoshop的高斯模糊滤镜。

什么是高斯模糊

高斯模糊(英语:Gaussian Blur),也叫高斯平滑,通常用及降低图片细节层次。

基本方法

说道图像模糊算法,实现原理也并不是很难懂,要设计一个程序来模糊处理一个图像的话,对于正在处理的每一个像素,取周围若干个像素的RGB值并且平均,然后这个平均值就是模糊处理过的像素,如果对图片中的所有像素都这么处理的话,处理完成的图片就会变得模糊。但是这样做的效果并不是,如果图片颜色变化频繁而且单位面积里面颜色反差很大,并且模糊半径很大的话,那么结果就是:模糊范围的最外层的像素和中心像素周围的像素的权重是一样的,这样处理的图片可能过渡并不是很平滑。

所以,需要有一个算法,来为这些在模糊范围中的像素来分别计算权重,这样的话越在模糊半径外围的像素权重越低,造成的影响就越小,越在内侧的像素得到的权重最高,因为内侧像素更加重要,他们的颜色应该与我们要处理的中心像素更接近,更密切。这个时候就需要用到高斯模糊算法了。

正态分布类型的权重

看到上面一段,思考一下,显然正态分布是一个理想的权重分配方法。

正态分布一种概率分布,也称“常态分布”。正态分布具有两个参数μ和σ^2的连续型随机变量的分布,第一参数μ是服从正态分布的随机变量的均值,第二个参数σ^2是此随机变量的方差,所以正态分布记作N(μ,σ^2)。服从正态分布的随机变量的概率规律为取与μ邻近的值的概率大,而取离μ越远的值的概率越小;σ越小,分布越集中在μ附近,σ越大,分布越分散。

Standard_deviation_diagram

上面就是一个一维上高斯分布的图,μ是中心点,离中心点越远的位置权重越小,在3σ的时候只有0.1%的权重。

可是,我们需要用到的高斯分布应该是二维的,因为我们选择一个中心像素,之后来平均那个像素周边的像素来得到模糊像素的值。而不仅仅是左边的和右边的像素。所以需要用到二维的正态分布。

Gaussian_3D_Circular

正态分布的密度函数叫做”高斯函数”(Gaussian function)他的公式是:

gaussian-function

一般情况下μ就是中心点,所以是0;

通过一维公式,可以推导出二维公式。

gaussian-function-2d

可能看了上面的公式并不是很懂,我来解释一下,在上面的公式中σ就是我们的模糊半径,而x和y就是我们周边像素对于中心像素的相对坐标。

好了有了上面的知识,可以开始编写程序了。

首先我们要设计一个类,这个类叫做BlurEffect,初始化的时候,用户传入一张图片(Java中是BufferredImage)和模糊半径,这个类将会对图片做出模糊处理。

public class BlurEffect {

    private int blurRadius = 1;
    private BufferedImage image;
    private double[][] weightArr;

    public BlurEffect(int blurRadius,BufferedImage image){
        this.blurRadius = blurRadius;
        this.image = image;
        weightArr = new double[blurRadius*2+1][blurRadius*2+1];
    }

权重矩阵的计算

接下来就要构建权重矩阵了,举个例子,如果模糊半径是2的话,就要构建一个 (2*2+1)长宽的矩阵。

weightMat

中心点的坐标为(0,0)其他点依次类推,之后就要写一个函数来根据模糊半径,x坐标,y坐标来计算权重。

private double getWeight(int x,int y){

    double sigma = (blurRadius*2+1)/2;
    double weight = (1/(2*Math.PI*sigma*sigma))*Math.pow(Math.E,((-(x*x+y*y))/((2*sigma)*(2*sigma))));

    return weight;
}

有了这个函数,再写一个函数来计算权重矩阵


private void calculateWeightMatrix(){
    for (int i=0;i < blurRadius*2+1;i++){
        for (int j=0;j < blurRadius*2+1;j++){
            weightArr[i][j] = getWeight(j-blurRadius,blurRadius-i);
        }
    }
}

之后通过调用这个函数就可以得到权重矩阵。

weightMat2

可是如果把这些权重加起来却只有 0.783118,总和不是1,要计算加权平均值的话,必须要让权重之和等于1.因此需要把上面各值在除以0.783118得到一个权重之和为1的矩阵。

通过编写以下函数来得到最终的权重矩阵:之后需要将这两个函数在构造函数中调用,因为只要模糊半径确定,那么权重矩阵将会是确定的。处理所有像素的权重矩阵都是一样的。


private void getFinalWeightMatrix(){

    int length = blurRadius*2+1;
    double weightSum = 0;

    for (int i = 0;i < length;i++){
        for (int j=0; j < length; j++ ){
            weightSum+=weightArr[i][j];
        }
    }


    for (int i = 0;i < length;i++){
        for (int j=0; j < length; j++ ){
            weightArr[i][j] = weightArr[i][j]/weightSum;
        }
    }

}

weightMat3

之后我需要分别编写函数来取得一个像素的R,G,B值,并且还需要一个函数来生成色值矩阵,色值矩阵储存中心像素和周边像素的色值,一共有三个这样的矩阵,分别储存R,G,B值。

</pre>
<pre>private double getR(int x,int y){
    int rgb = image.getRGB(x, y);
    int r = (rgb & 0xff0000) >> 16;
    return r;
}

private double getG(int x,int y){
    int rgb = image.getRGB(x, y);
    int g = (rgb & 0xff00) >> 8;
    return g;
}

private double getB(int x,int y){
    int rgb = image.getRGB(x, y);
    int b = (rgb & 0xff);
    return b;
}

private double[][] getColorMatrix(int x, int y, int whichColor){

    int startX = x-blurRadius;
    int startY = y-blurRadius;
    int counter = 0;

    int length = blurRadius*2+1;
    double[][] arr = new double[length][length];

    for (int i=startX ; i<startX+length ;i++){
        for (int j = startY; j < startY+length; j++){
            if (whichColor == 1){
                arr[counter%length][counter/length] = getR(i,j);
            }else if (whichColor == 2){
                arr[counter%length][counter/length] = getG(i,j);
            }else if (whichColor == 3){
                arr[counter%length][counter/length] = getB(i,j);
            }
            counter++;
        }
    }

    return arr;
}

现在我们可以得到权重矩阵,可以得到RGB色值矩阵,再写一个函数来计算所有矩阵的平均值,把权重矩阵和色值矩阵相乘就可以得到权重处理过后的色值,之后相加,得到最终中心像素的色值。

getColorMat


double getBlurColor(int x, int y,int whichColor){

    double blurGray = 0;
    double[][] colorMat = getColorMatrix(x,y,whichColor);

    int length = blurRadius*2+1;
    for (int i = 0;i <; length;i++){
        for (int j=0; j < length; j++ ){
            blurGray += weightArr[i][j]*colorMat[i][j];
        }
    }

    return blurGray;
}

好了,现在万事具备了,可以调用上面的函数来计算指定像素模糊之后的R,G,B值了,可以生成新的图片文件了。


public BufferedImage getBluredImg(){

    BufferedImage bi = new BufferedImage(image.getWidth()-blurRadius*2, image.getHeight()-blurRadius*2, BufferedImage.TYPE_INT_RGB);
    for (int x = 0; x < bi.getWidth(); x++) {
        for (int y = 0; y < bi.getHeight(); y++) {
            int r = (int)getBlurColor(blurRadius+x,blurRadius+y,1);
            int g = (int)getBlurColor(blurRadius+x,blurRadius+y,2);
            int b = (int)getBlurColor(blurRadius+x,blurRadius+y,3);
            Color color = new Color(r,g,b);
            bi.setRGB(x, y, color.getRGB());
        }
    }

    File file = new File("C:\\Users\\Mike\\Desktop\\out.jpg");
    try {
        ImageIO.write(bi,"jpg",file);
    } catch (IOException e) {
        e.printStackTrace();
    }

    return null;
}

最后经过简单的调用,就可以生成一个经过高斯模糊模糊的图片了。

BufferedImage img = FileUtil.loadImg("1.jpg");
new BlurEffect(7,img).getFinalWeightMatrix();

Untitled

大家可以在我的GitHub上下载项目源码

https://github.com/Yigang0622/GaussianBlur

MikeTech现已登陆iPhone和Android

iPhone版下载
Android版下载

打赏

3 thoughts on “图像高斯模糊算法的原理及实现

  1. 想问下博主,按照博文内描述,最后产生的图片与原图会差blurRadius*blurRadius个个像素啊。
    那么边界应该做什么处理呢?

    1. 边界,可以把差的一般像素从别处挪动填补上,不过那样效果应该不好,可是这个是避免不了的。

Leave a Reply

Your email address will not be published. Required fields are marked *