如果经常使用Photoshop等修图软件,那么对高斯模糊滤镜肯定不会陌生,通过调整模糊半径,图像会变得模糊,半径越大越模糊,这篇文章将会讲解高斯模糊算法的原理以及其Java实现。
上图是Photoshop的高斯模糊滤镜。
什么是高斯模糊
高斯模糊(英语:Gaussian Blur),也叫高斯平滑,通常用及降低图片细节层次。
基本方法
说道图像模糊算法,实现原理也并不是很难懂,要设计一个程序来模糊处理一个图像的话,对于正在处理的每一个像素,取周围若干个像素的RGB值并且平均,然后这个平均值就是模糊处理过的像素,如果对图片中的所有像素都这么处理的话,处理完成的图片就会变得模糊。但是这样做的效果并不是,如果图片颜色变化频繁而且单位面积里面颜色反差很大,并且模糊半径很大的话,那么结果就是:模糊范围的最外层的像素和中心像素周围的像素的权重是一样的,这样处理的图片可能过渡并不是很平滑。
所以,需要有一个算法,来为这些在模糊范围中的像素来分别计算权重,这样的话越在模糊半径外围的像素权重越低,造成的影响就越小,越在内侧的像素得到的权重最高,因为内侧像素更加重要,他们的颜色应该与我们要处理的中心像素更接近,更密切。这个时候就需要用到高斯模糊算法了。
正态分布类型的权重
看到上面一段,思考一下,显然正态分布是一个理想的权重分配方法。
正态分布一种概率分布,也称“常态分布”。正态分布具有两个参数μ和σ^2的连续型随机变量的分布,第一参数μ是服从正态分布的随机变量的均值,第二个参数σ^2是此随机变量的方差,所以正态分布记作N(μ,σ^2)。服从正态分布的随机变量的概率规律为取与μ邻近的值的概率大,而取离μ越远的值的概率越小;σ越小,分布越集中在μ附近,σ越大,分布越分散。
上面就是一个一维上高斯分布的图,μ是中心点,离中心点越远的位置权重越小,在3σ的时候只有0.1%的权重。
可是,我们需要用到的高斯分布应该是二维的,因为我们选择一个中心像素,之后来平均那个像素周边的像素来得到模糊像素的值。而不仅仅是左边的和右边的像素。所以需要用到二维的正态分布。
正态分布的密度函数叫做”高斯函数”(Gaussian function)他的公式是:
一般情况下μ就是中心点,所以是0;
通过一维公式,可以推导出二维公式。
可能看了上面的公式并不是很懂,我来解释一下,在上面的公式中σ就是我们的模糊半径,而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)长宽的矩阵。
中心点的坐标为(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);
}
}
}
|
之后通过调用这个函数就可以得到权重矩阵。
可是如果把这些权重加起来却只有 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;
}
}
}
|
之后我需要分别编写函数来取得一个像素的R,G,B值,并且还需要一个函数来生成色值矩阵,色值矩阵储存中心像素和周边像素的色值,一共有三个这样的矩阵,分别储存R,G,B值。
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
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色值矩阵,再写一个函数来计算所有矩阵的平均值,把权重矩阵和色值矩阵相乘就可以得到权重处理过后的色值,之后相加,得到最终中心像素的色值。
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();
大家可以在我的GitHub上下载项目源码
https://github.com/Yigang0622/GaussianBlur
MikeTech现已登陆iPhone和Android
iPhone版下载
Android版下载