抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

高斯模糊是被广泛使用的图形算法之一,在实现高斯模糊之前,先要了解正态分布

正态分布

一维的正态分布为
DearXuan
直接让f(x)和f(y)相乘,就得到了二维的正态分布
DearXuan
此处直接令μ=0,将会在下面解释。

权值矩阵

设有一个(2n+1)阶矩阵M,且有
DearXuan
,我们称这个矩阵为权值矩阵,称
DearXuan为(i,j)点处的权。其中n是高斯模糊的半径。

离散卷积

离散卷积是卷积对离散量的特殊形式,假设现有原图矩阵A,权值矩阵B,则点(x,y)处的离散卷积为
DearXuan
在更严格的定义中,A(i,j)应该与B(u-i,v-j)相乘,但是针对本文的高斯模糊而言,其效果是一样的,且上面给出的公式更为简洁。

现在举个例子,有一张尺寸为3*3的图片S,将其颜色转化成矩阵A,为
DearXuan
有权值矩阵B为
DearXuan
将A(i,j)与B(i,j)相乘,将结果相加

(-1) x 2 + (-1) x 4 + (-1) x 6 + (-1) x 8 + 5 x 5 = 5

则以上两个矩阵的离散卷积结果为5,这就是矩阵A经过处理后得到的新矩阵M(2,2)的值。

在高斯模糊中,设模糊半径为n,则定义一个维数为2n+1的权值矩阵G,且G(i,j)=f(i-n-1,j-n-1),类似于将一个直角坐标系放在了G的中点处,这就是μ=0的原因。此处的f是二维正态分布函数。然后求和
DearXuan,将矩阵的每个数除以这个和。求和的步骤是防止图片过亮或过暗。

将得到的矩阵G代替B计算,其结果就是高斯模糊的结果

优化

上述方法的效率较低,在介绍正态分布时,二维的正态分布函数是两个一维函数相乘得到的,这两个一维函数分别是f(x)和f(y),f(x)代表水平方向,f(y)代表垂直方向。对于一个n维权值矩阵,用它来处理a*b尺寸的图片,如果用二维正态分布函数来计算,总共需要计算a*b*n*n=abn²次,及其繁琐。这时我们可以使用一维的正态分布函数,得出一个“权值列向量”,这个向量的作用类似权值矩阵,用这个列向量把图片横向处理,相当于f(x),再用它把图片纵向处理,相当于f(y),此时图片经过两次处理,相当于f(x)*f(y),也可以达到二维正态分布的效果,而计算量仅仅是a*b*n+a*b*n=2abn,下降了一个数量级。该方法不详细介绍,将在代码中展示。

代码实现

GaussianBlur类,算法的核心部分

public final class GaussianBlur {
    private static final int precision = 10000; // 精度,由于返回的是int数组,精度较低,因此需要将所有值同时扩大数倍,可以理解为把小数点向右移动
    private static final double E = 2.718281828459045;//自然常数e
    private static final double PI = 3.141592653589793;//圆周率
 
    /**
     * 快速高斯模糊
     * @param picture 三维数组,picture[a][b][c],a表示颜色,012分别为R,G,B;b和c代表尺寸,宽度为b,高度为c
     * @param radius 半径
     * @return 格式如同picture的数组
     */
    public static int[][][] GaussianBlur(int[][][] picture,int radius){
        int i, j, x, R, G, B, proportion, subscript;
        int[] matrix = LinearNormalDistribution(radius,1.5);
        int width = picture[0].length, height = picture[0][0].length;
        int[][][] color_1 = new int[3][width][height]; // 用来存高斯模糊后的数据
        int[][][] color_2 = new int[3][width][height]; // 临时存储纵向滤波之后的数据
        //纵向滤波
        for (i = 0; i < width; i++) {
            for (j = 0; j < height; j++) {
                R = G = B = 0;
                for (x = j - radius; x <= j + radius; x++) {
                    proportion = matrix[x + radius - j];
                    subscript = (x >= 0 && x < height) ? x : 2 * j - x; // 如果坐标越界了,则计算对称点来代替
                    R += picture[0][i][subscript] * proportion;
                    G += picture[1][i][subscript] * proportion;
                    B += picture[2][i][subscript] * proportion;
                }
                color_2[0][i][j] = R / precision;
                color_2[1][i][j] = G / precision;
                color_2[2][i][j] = B / precision;
            }
        }
        //横向滤波
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                R = G = B = 0;
                for (x = j - radius; x <= j + radius; x++) {
                    proportion = matrix[x + radius - j];
                    subscript = (x >= 0 && x < width) ? x : 2 * j - x;
                    R += color_2[0][subscript][i] * proportion;
                    G += color_2[1][subscript][i] * proportion;
                    B += color_2[2][subscript][i] * proportion;
                }
                //注意for语句中i代表高度,j代表宽度,所以下面三个语句的i和j并没有写错位置
                color_1[0][j][i] = R / precision;
                color_1[1][j][i] = G / precision;
                color_1[2][j][i] = B / precision;
            }
        }
        return color_1;
    }
 
    /**
     * 慢速高斯模糊,采用二维正态分布的方法来处理图像
     * @param picture 三维数组,picture[a][b][c],a表示颜色,012分别为R,G,B;b和c代表尺寸,宽度为b,高度为c
     * @param radius 半径
     * @return 格式如同picture的数组
     */
    public static int[][][] SlowGaussianBlur(int[][][] picture,int radius){
        //flag为真时计算加权,为假时直接代入矩阵
        int[][] matrix = NormalDistribution(radius,1.5);
        int i, j, x, y, R, G, B, proportion, left, right, width = picture[0].length, height = picture[0][0].length;
        int[][][] color = new int[3][width][height];
        //选取每个点
        for (i = 0; i < width; i++) {
            for (j = 0; j < height; j++) {
                //选取半径为radius的矩阵
                R = G = B = 0;
                for (x = i - radius; x <= i + radius; x++) {
                    for (y = j - radius; y <= j + radius; y++) {
                        //求出颜色
                        proportion = matrix[x + radius - i][y + radius - j];
                        left = (x >= 0 && x < width) ? x : 2 * i - x;
                        right = (y >= 0 && y < height) ? y : 2 * j - y;
                        R += picture[0][left][right] * proportion;
                        G += picture[1][left][right] * proportion;
                        B += picture[2][left][right] * proportion;
                    }
                }
                color[0][i][j] = R / precision;
                color[1][i][j] = G / precision;
                color[2][i][j] = B / precision;
            }
        }
        return color;
    }
 
    /**
     * 用一维正态分布函数来计算“权值列向量”,效率较高
     * @param radius 模糊半径
     * @param SIGMA 正态分布参数,如果自己没把握,就填1.5
     * @return “权值列向量”
     */
    private static int[] LinearNormalDistribution(int radius,double SIGMA){
        int[] matrix = new int[2 * radius + 1]; // 定义一个列向量
        int sum, i;
        //计算各个点的正态分布值
        sum = matrix[radius] = (int) (precision / (2 * PI * SIGMA * SIGMA)); // sum的初值为向量中心点的值,例如向量(1,2,3,2,1),则初值为3
        for (i = 1; i <= radius; i++) {
            //根据对称性,可以减少一倍的运算量,i=0的情况已经在sum初值那一步考虑
            matrix[radius-i] = matrix[radius+i] = (int) ((Math.pow(E, -i * i / (2 * SIGMA * SIGMA)) / (2 * PI * SIGMA * SIGMA)) * precision);
            sum += matrix[radius+i] * 2; // 计算向量所有值之和
        }
        for (i = 0; i < 2 * radius + 1; i++) {
            matrix[i] = matrix[i] * precision / sum; // 所有值都除以sum,确保它们的和为“1”,由于扩大了10000倍,所以这个“1”实际上应该是10000
        }
        return matrix;
    }
 
    /**
     * 用二维正态分布函数来计算权值矩阵,效率较低
     * @param radius 模糊半径
     * @param SIGMA 正态分布参数,如果自己没把握,就填1.5
     * @return 权值矩阵
     */
    private static int[][] NormalDistribution(int radius, double SIGMA) {
        int sum = 0, i, j;
        int[][] matrix = new int[2 * radius + 1][2 * radius + 1]; // 定义一个矩阵
        //计算各个点的正态分布值
        for (i = 0; i <= radius; i++) {
            for (j = 0; j <= radius; j++) {
                //写入矩阵并累加,根据矩阵的对称性可以减少3/4的运算量
                matrix[radius-i][radius-j]
                        = matrix[radius-i][radius+j]
                        = matrix[radius+i][radius-j]
                        = matrix[radius+i][radius+j]
                        = (int) (Math.pow(E, -(i * i + j * j) / (2 * SIGMA * SIGMA)) / (2 * PI * SIGMA * SIGMA) * precision);
                sum += 4 * matrix[radius+i][radius+j];
            }
        }
        //计算权值
        for (i = 0; i <= 2 * radius; i++) {
            for (j = 0; j <= 2 * radius; j++) {
                matrix[i][j] = matrix[i][j] * precision / sum; // 所有值都除以sum,确保它们的和为“1”,由于扩大了10000倍,所以这个“1”实际上应该是10000
            }
        }
        return matrix;
    }
}

Filter类,通过调用GaussianBlur类来处理图像

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
 
public final class Filter {
    public static BufferedImage GaussianBlur(String path){
        int pixel;
        try {
            BufferedImage image = ImageIO.read(new File(path));
            int width = image.getWidth(),height = image.getHeight();
            int[][][] picture = new int[3][width][height];
            for(int i=image.getMinX();i<width;i++){
                for(int j=image.getMinY();j<height;j++){
                    pixel = image.getRGB(i,j);
                    //获取每个点的RGB值
                    picture[0][i][j] = (pixel & 0xff0000) >> 16;
                    picture[1][i][j] = (pixel & 0xff00) >> 8;
                    picture[2][i][j] = (pixel & 0xff);
                }
            }
            picture = GaussianBlur.GaussianBlur(picture,100);  // 快速高斯模糊
            for(int i=image.getMinX();i<width;i++){
                for(int j=image.getMinY();j<height;j++){
                    pixel = ((picture[0][i][j] & 0xff) << 16) + ((picture[1][i][j] & 0xff) << 8) + (picture[2][i][j] & 0xff);
                    image.setRGB(i,j,pixel);
                }
            }
            return image;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

Main,打开1.jpg并高斯模糊,保存为2.jpg

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
 
public class Main {
 
    public static void main(String[] args) throws Exception{
        BufferedImage image = Filter.GaussianBlur("D://1.jpg");
        ImageIO.write(image,"jpg",new File("D://2.jpg"));
    }
}

效果

原图

DearXuan

高斯模糊之后的图

DearXuan
(半径20,SIGMA1.5)

评论