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

March 30, 2016
JavaAlgorithm

如果经常使用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)和模糊半径,这个类将会对图片做出模糊处理。

  1. public class BlurEffect {
  2. private int blurRadius = 1;
  3. private BufferedImage image;
  4. private double[][] weightArr;
  5. public BlurEffect(int blurRadius,BufferedImage image){
  6. this.blurRadius = blurRadius;
  7. this.image = image;
  8. weightArr = new double[blurRadius*2+1][blurRadius*2+1];
  9. }

权重矩阵的计算

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

weightMat

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

  1. private double getWeight(int x,int y){
  2. double sigma = (blurRadius*2+1)/2;
  3. double weight = (1/(2*Math.PI*sigma*sigma))*Math.pow(Math.E,((-(x*x+y*y))/((2*sigma)*(2*sigma))));
  4. return weight;
  5. }

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

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

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

weightMat2

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

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

  1. private void getFinalWeightMatrix(){
  2. int length = blurRadius*2+1;
  3. double weightSum = 0;
  4. for (int i = 0;i < length;i++){
  5. for (int j=0; j < length; j++ ){
  6. weightSum+=weightArr[i][j];
  7. }
  8. }
  9. for (int i = 0;i < length;i++){
  10. for (int j=0; j < length; j++ ){
  11. weightArr[i][j] = weightArr[i][j]/weightSum;
  12. }
  13. }
  14. }

weightMat3

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

  1. </pre>
  2. <pre>private double getR(int x,int y){
  3. int rgb = image.getRGB(x, y);
  4. int r = (rgb & 0xff0000) >> 16;
  5. return r;
  6. }
  7. private double getG(int x,int y){
  8. int rgb = image.getRGB(x, y);
  9. int g = (rgb & 0xff00) >> 8;
  10. return g;
  11. }
  12. private double getB(int x,int y){
  13. int rgb = image.getRGB(x, y);
  14. int b = (rgb & 0xff);
  15. return b;
  16. }
  17. private double[][] getColorMatrix(int x, int y, int whichColor){
  18. int startX = x-blurRadius;
  19. int startY = y-blurRadius;
  20. int counter = 0;
  21. int length = blurRadius*2+1;
  22. double[][] arr = new double[length][length];
  23. for (int i=startX ; i<startX+length ;i++){
  24. for (int j = startY; j < startY+length; j++){
  25. if (whichColor == 1){
  26. arr[counter%length][counter/length] = getR(i,j);
  27. }else if (whichColor == 2){
  28. arr[counter%length][counter/length] = getG(i,j);
  29. }else if (whichColor == 3){
  30. arr[counter%length][counter/length] = getB(i,j);
  31. }
  32. counter++;
  33. }
  34. }
  35. return arr;
  36. }

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

getColorMat

  1. double getBlurColor(int x, int y,int whichColor){
  2. double blurGray = 0;
  3. double[][] colorMat = getColorMatrix(x,y,whichColor);
  4. int length = blurRadius*2+1;
  5. for (int i = 0;i <; length;i++){
  6. for (int j=0; j < length; j++ ){
  7. blurGray += weightArr[i][j]*colorMat[i][j];
  8. }
  9. }
  10. return blurGray;
  11. }

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

  1. public BufferedImage getBluredImg(){
  2. BufferedImage bi = new BufferedImage(image.getWidth()-blurRadius*2, image.getHeight()-blurRadius*2, BufferedImage.TYPE_INT_RGB);
  3. for (int x = 0; x < bi.getWidth(); x++) {
  4. for (int y = 0; y < bi.getHeight(); y++) {
  5. int r = (int)getBlurColor(blurRadius+x,blurRadius+y,1);
  6. int g = (int)getBlurColor(blurRadius+x,blurRadius+y,2);
  7. int b = (int)getBlurColor(blurRadius+x,blurRadius+y,3);
  8. Color color = new Color(r,g,b);
  9. bi.setRGB(x, y, color.getRGB());
  10. }
  11. }
  12. File file = new File("C:\\Users\\Mike\\Desktop\\out.jpg");
  13. try {
  14. ImageIO.write(bi,"jpg",file);
  15. } catch (IOException e) {
  16. e.printStackTrace();
  17. }
  18. return null;
  19. }

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

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

Untitled

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

https://github.com/Yigang0622/GaussianBlur

MikeTech现已登陆iPhone和Android

iPhone版下载
Android版下载

Comments

July 21, 2018 at 10:52 am

There are no comments

keyboard_arrow_up