本文用到了卷积的内容,如有了解较少的同学建议转到java图像处理(卷积,强调边缘,平滑与高斯模糊)先了解一下
预备知识
高斯模糊的概念
高斯模糊(Gaussian Blur),是常用的用来减少噪声的算法。其实质上是图像与正态分布的卷积,而正态分布又叫做高斯分布,故而名为高斯模糊
高斯模糊的原理
像素重置
模糊的过程即将每个像素值进行重置的过程,重置的一般过程即为将每一个像素设置成周围像素的平均值(算数平均或加权平均),如以下像素组
这个像素组的中间为8,其余周围为1
假定中心点的元素对周围元素无影响,那么我们需要用来求平均值的卷积核为
卷积一下
这样,中间的值就从较大的8变成了较小的16/9,相当于是平滑了中间值,因此使用上面卷积核来进行模糊的算法也叫做平滑模糊
当然我们也可以以卷积核作为权来进行加权平均,如下面我们使用的高斯模糊的卷积核就是加权平均的一种
使用范围为3,5,7,9的平均值卷积核来进行平滑模糊的效果如图所示
很明显,随着范围的增大,模糊的效果越来越明显,但是平滑模糊由于权重一致,虽然有一定的模糊效果,但是对噪声的处理不是很好,如果我们找到了一种权重的定义的话,可能会对我们的模糊处理有着更有益的效果。
很容易想到的是将权重与距离联系起来,距离中心元越近权重越高,距离中心元越远权重越低。
如何分辨中心元素和周围元素的距离关系?
一种常见的衡量方法为
正态分布(也叫高斯分布)
一维的正态分布我们已经很常见了
这种中心高,两侧低的算法正好可以用于我们对距离的判定
但是,由于图像是二维的,因此一维的正态分布在这里不适用
我们将x换成(x^2 + y^2)的开方,即可将一维的正态曲线旋转画到二维中
由于我们将(0,0)点定义成中心点,这样公式中的μ值取0,公式即为
有了这个公式,我们就可以根据公式生成一个卷积核,然后去和像素区域进行卷积,这样得到的就是加权平均过后的中心值了
根据正态分布产生的卷积核我们叫做高斯模板
高斯模板代码(java)
//设置一个数组存储高斯模板,其中size为模板的大小(也叫做卷积核范围)
double[][] gaussModel = new double[size][size];
//设置图片中心参数
int CenterX = (size-1)/2;
int CenterY = CenterX;
//临时中间量
double part;
//高斯模板的总和
double sum = 0;
//定义一个合适的sigma值
double sigma = size / 3.0f;
for(int i = 0; i< size; ++i){
//根据高斯公式计算高斯模板
for(int j = 0; j < size; ++j){
//首先产生指数值中有关x,y的内容
part = (i - CenterX) * (i - CenterX) + (j - CenterY) * (j - CenterY);
//然后进行高斯值的计算,并赋到高斯模板的数组中
gaussModel[i][j] = (double)(Math.exp( - part / (2 * sigma * sigma) )
/ Math.sqrt(2 * Math.PI * sigma * sigma));
//将高斯值加到总和中
sum += gaussModel[i][j];
}
}
// 归一化,为了使处理后的图像与原图亮度相同
for(int i = 0; i< size; ++i){
for(int j = 0; j < size; ++j){
//将所得高斯值除以sum值,使其缩到1内,即为归一化
gaussModel[i][j] = (double) (gaussModel[i][j] / sum);
}
}
附:生成的3 * 3与5 * 5的高斯模板
边缘处理
暂时想到的是可以将原图先四周围临时补上一圈无意义的像素点,但是补什么样的点暂时我还没有想出来,在这里先补上了黑色点,像素点宽度为size/2(例如3 * 3的卷积核补充一圈宽度为1的像素点),这样在处理图像时就可以将整个图像进行处理,最后再去掉补上的点即可。
代码请见下面高斯模板内的代码
下面是效果
处理之前的边缘,是有很明显的没有处理的边界的
处理之后的边缘,很明显是被处理过的,补的是黑色边框(根据周围颜色来补边框的算法暂时还没研究出来,如果有好的解决方法欢迎各位支援!)
高斯模糊的代码实现
这里实现的是彩色图像的高斯模糊,因此我们需要对原图的rgb值进行提取,然后分别对r,g,b进行卷积处理后绘出(也可以理解成使用了3 * 3 * 3的一个三维卷积核)
请仔细阅读注释内容,注释中有详细的代码解释
//设置临时边缘的宽度
int tempWidth = size/2;
//设置一个数组存储含边缘的图片数据,其中边缘因为是上下左右均要补边,因此是2*tempWidth
int[][] tempImg = new int[width+2*tempWidth][height+2*tempWidth];
//将横着的边缘补上
for(int x = 0;x < width+2*tempWidth;x++){
for(int y = 0;y < tempWidth;y++){
//将上下的边缘补上
tempImg[x][y] =0;
//height+2*tempWidth-y-1计算下面需要补充的边缘的y值位置,下同,不再赘述
tempImg[x][height+2*tempWidth-y-1] =0;
}
}
//将竖着的边缘补上
for(int y = 0;y < height+2*tempWidth;y++){
for(int x = 0;x < tempWidth;x++){
tempImg[x][y] = 0;
tempImg[width+2*tempWidth-x-1][y] =0;
}
}
//将原有图像传到新数组中等待处理,其中将原有图像向右下角移动tempWidth个单位即可将原图赋到新图中
for(int x = 0;x < width;x++){
for(int y = 0;y < height;y++){
tempImg[x+tempWidth][y+tempWidth] = rgbOfImg[x][y];
}
}
//下面开始搞卷积算法
//初始化rgb数组
R = new int[size][size];
G = new int[size][size];
B = new int[size][size];
//遍历图像的二维数组进行处理
for(int x = 0;x < width+2*tempWidth-size+1;x++){
for(int y = 0;y < height+2*tempWidth-size+1;y++){
//一定要在循环内部进行初始化0,这样才能每次有不同的值
int resultOfR = 0;
int resultOfG = 0;
int resultOfB = 0;
//将格子的rgb值都取出,便于之后的卷积操作
for(int i = 0;i < size;i++){
for(int j = 0;j <size;j++){
//将该点的ARGB信息取出,放到变量中待操作
int argb = tempImg[x+i][y+j];
//分段获取其R,G,B信息
//int变量共4位32字节,0位对应透明度(A),1位对应R值,2位对应G值,3位对应B值
//>>操作:将二进制代码向右移动,左边空位根据符号补充,正号为0,负号为1,右边超过范围的全部舍弃
//&:二进制位与运算符,只有两个变量对应值均为1时该位才返回1,0xff表示全为1的十六进制数(11111111),因此任何与0xff进行位与的结果均为其本身
//先移位后取位与可以将不同值对应的位信息取出,位与的意义是只取32字节的后8字节
R[i][j] = argb>>16 & 0xff;
G[i][j] = argb>>8 & 0xff;
B[i][j] = argb & 0xff;
}
}
//分别对R,G,B进行卷积操作,对应相乘后加起来
for(int i = 0;i < size;i++){
for(int j = 0;j < size;j++){
resultOfR += (int)(gaussModel[i][j]*R[i][j]);
}
}
for(int i = 0;i < size;i++){
for(int j = 0;j < size;j++){
resultOfG += (int)(gaussModel[i][j]*G[i][j]);
}
}
for(int i = 0;i < size;i++){
for(int j = 0;j < size;j++){
resultOfB += (int)(gaussModel[i][j]*B[i][j]);
}
}
//如果超过了界限,将其按照最大值或最小值处理
if(resultOfR > 255)resultOfR = 255;
if(resultOfR < 0)resultOfR = 0;
if(resultOfG > 255)resultOfG = 255;
if(resultOfG < 0)resultOfG = 0;
if(resultOfB > 255)resultOfB = 255;
if(resultOfB < 0)resultOfB = 0;
//将卷积得出的高斯模糊的灰度值传到中间元中
//根据该argb值创建颜色对象
Color color = new Color(resultOfR, resultOfG, resultOfB);
//设置颜色
graphics2.setColor(color);
//画像素点,将点向左上平移以实现边框的去除
graphics2.drawLine(x+size/2-tempWidth, y+size/2-tempWidth, x+size/2-tempWidth, y+size/2-tempWidth);
}
}
效果(左为3 * 3,右为5 * 5)
附原图