Android下图像空间滤波
今天要完成的项目是关于图像的滤波,也就是均值滤波、中值滤波和锐化滤波,先上图看效果:
均值滤波采用了盒滤波器,也是最简单的均值滤波器;锐化滤波做的略粗糙,是因为在做完计算之后直接判断大于255的为255,小于0的为0,颜色的变化太生硬,就会看到很多颗粒状的噪点。后文中会有优化。
再多吐一句槽,Java写出来的东西运行起来就是慢,怪不得对图像处理速度有要求的都要通过JNI调用C++的代码进行编译运行。看来学习Android的同时,C++也是个得力的助手啊。
1.线性空间滤波器
线性空间滤波器的响应是包含在滤波模板邻域内像素的平均值,因此这种滤波器也称为均值滤波器。它用滤波模板确定的邻域内像素的平均灰度值去代替原始图像中每个像素点的值,这样就可以减小图像灰度的“尖锐”变化。掌握了这些,你也应该就知道美图秀秀这一类的自动美图软件的磨皮的基本原理了。比如脸上的痘,灰度级会有尖锐变化,因此,一键磨皮的平滑处理应用就是减噪。至于美图用的算法一定不会只有这么粗暴简单,但是作为基础,我就从最简单的盒滤波器开始。
1 | 1 | 1 |
1 | 1 | 1 |
1 | 1 | 1 |
图 4-1 模板1
在图4-1 模板1中,其模板系数都相等,且全为1。所有系数都相等的空间均值滤波器也被称为盒滤波器。上述模板一虽然考虑了邻域点的作用,但并没有考虑各点位置的影响,对于邻域内的所有点都一视同仁,所以平滑的效果并不理想,有可能出现伪轮廓。一般来说,距离待处理像素点越近的点对该点的影响应该越大,为此,我们引入了加权系数,处于模板中心位置的像索比其他任何像素的权值都要大,如图 4-2 中模板2系数确定了权值,有效的提升了平滑效果。
1 | 2 | 1 |
2 | 4 | 2 |
1 | 2 | 1 |
图 4-2模板2
在均值计算中给定的这一像素显得更为重要,而距离模板中心较远的其他像素就显得不太重要。由于对角项与中心之间的距离比正交方向相邻的像素更远,所以,它的重要性要比与中心直接相邻的四个像素低。把中心点设置为最高,并随着到中心点距离的增加而减小系数值,是为了减小平滑处理中的模糊。这种模板也是一个常用的平滑模板,称为高斯(Gauss)模板。
对于一幅M x N的图像经过一个m x n (令模板的m和n都是奇数)的均值滤波
器,其处理结果为:
公式4-1中,a=(m-1)/2,b=(n-1)/2,分母为模板系数之和,分子为模板系数与邻域内像素值的加权和。
实现盒滤波器做平滑滤波的代码如下:
int[] newpix = new int[width*height];
int[] oldpix = new int[width*height];
bmp.getPixels(oldpix, 0, width, 0, 0, width, height);
bmp.getPixels(newpix, 0, width, 0, 0, width, height);
for(int i=halffilter;i<height-halffilter;i++){
for(int j=halffilter;j<width-halffilter;j++){
int red=0,green=0,blue = 0;
for(int x=i-halffilter; x <=i+halffilter; x ++){
for(int y = j-halffilter;y<=j+halffilter; y++ ){
int index=x*width+y;
red += (oldpix[index] >> 16) & 0xff;
<span style="white-space:pre"> </span>green += (oldpix[index] >> 8) & 0xff;
<span style="white-space:pre"> </span>blue += oldpix[index] & 0xff;
}
}
red /= areafilter;
green /= areafilter;
blue /= areafilter;
int index = i*width+j;
newpix[index]=0xff000000|red<<16|green<<8|blue;
其中width是原图宽,height是原图高。
halffilter是滤波模板的宽或者高的一半,如果模板宽高为5,则halffilter = 2,因为模板元素从0计数,所以保证了图像中的第三行第三列的那个元素是第一个能够被计算的元素。areafilter 为模板的面积。如果采用高斯模板,就需要指定具体的阶数,再加一些判断条件,改变权重。如果阶数定位3,则模板就如图4-2所示,那么代码如下:
for (int i = halffilter; i < height - halffilter; i++) {
for (int j = halffilter; j < width - halffilter; j++) {
int red = 0, green = 0, blue = 0, k = 0;
for (int x = i - halffilter; x <= i + halffilter; x++) {
for (int y = j - halffilter; y <= j + halffilter; y++) {
int index = x * width + y;
if(x == i-1 && y == j-1 ||x == i-1 && y == j+1 ||x == i+1 && y == j-1||x == i+1 && y == j+1 )k = 1;
else if(x == i-1 && y == j||x == i && y == j-1 ||x == i+1 && y == j||x == i && y == j+1)k = 2;
else if(x == i && y == j)k = 4;
// red += (oldpix[index] >> 16) & 0xff;
// green += (oldpix[index] >> 8) & 0xff;
// blue += oldpix[index] & 0xff;
red += k*((oldpix[index] >> 16) & 0xff);
green += k*((oldpix[index] >> 8) & 0xff);
blue += k*(oldpix[index] & 0xff);
}
}
red /= areafilter;
green /= areafilter;
blue /= areafilter;
int index = i * width + j;
newpix[index] = 0xff000000 | red << 16 | green << 8 | blue;
}
}
2.非线性滤波
统计滤波器是一种常用的非线性空间滤波器,它的输出响应是基于滤波模板所包围的像区域中像素值的排序,然后由统计排序的结果替代中心像素点的值。统计滤波器中最常见的例子就是中值滤波器,正如其名,它是将像素邻域内灰度的中值代替该像素的值。中值滤波器的使用非常普遍,这是因为对于一定类型的随机噪声,它提供了一种优秀的去噪能力,且它比小尺寸的线性平滑滤波器的模糊程度明显要低。中值滤波器对处理脉冲噪声(也称为椒盐噪声)非常有效,因为这种噪声是以黑白点叠加在图像上的。 一个数值集合的中值x是这样的数值,即数值集合中,有一半小于或等于x,还有一半大于或等于x。为了对一幅图像上的某个点做中值滤波处理,必须先将滤波模板内的像素值进行排序,确定出中值,并将中值赋予该像素点。由此可以看出,中值滤波器的主要功能是使拥有不同灰度的像素点看起来更接近于它的邻近值。事实上,是用n x n的中值滤波器去除那些相对于其邻域像素更亮或更暗,并且其区域小于n2/2(滤波器区域的一半)的孤立像素集,而对较大的像素集的影响很小。尽管在图像处理领域,中值滤波器是使用最广泛的统计滤彼器,但这并不等于它是唯一的。所谓中值就是一系列像素值的第50%个值,但是排序也适用于其他不同的情况。例如,我们可以取第100%个值,即我们所说的最大值滤波器;或者取第0%个值,此时就是最小值滤波器。
代码如下:
for (int i = halffilter; i < height - halffilter; i++) {
<span style="white-space:pre"> </span>for (int j = halffilter; j < width - halffilter; j++) {
<span style="white-space:pre"> </span>int[] red = new int[areafilter];
<span style="white-space:pre"> </span>int[] green = new int[areafilter];
<span style="white-space:pre"> </span>int[] blue = new int[areafilter];<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>for (int x = -halffilter; x <= +halffilter; x++) {
<span style="white-space:pre"> </span>for (int y = -halffilter; y <= +halffilter; y++) {
<span style="white-space:pre"> </span>int index = (x + i) * width + (y + j);
<span style="white-space:pre"> </span>int num = (x + halffilter)* halffilter + y+ halffilter;
<span style="white-space:pre"> </span>// System.out.println("num----------------->"+num);
<span style="white-space:pre"> </span>red[num] = (oldpix[index] >> 16) & 0xff;
<span style="white-space:pre"> </span>// System.out.println("red num ----->"+red.toString());
<span style="white-space:pre"> </span>green[num] = (oldpix[index] >> 8) & 0xff;
<span style="white-space:pre"> </span>blue[num] = oldpix[index] & 0xff;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>Arrays.sort(red);
<span style="white-space:pre"> </span>int redmedian = red[4];
<span style="white-space:pre"> </span>Arrays.sort(green);
<span style="white-space:pre"> </span>int greenmedian = green[4];
<span style="white-space:pre"> </span>Arrays.sort(blue);
<span style="white-space:pre"> </span>int bluemedian = blue[4];
<span style="white-space:pre"> </span>// System.out.println("red:---"+redmedian+red.toString()+"-------green:------"+greenmedian+green.toString()+"------blue:-------"+bluemedian+blue.toString());
<span style="white-space:pre"> </span>int index = i * width + j;
<span style="white-space:pre"> </span>newpix[index] = 0xff000000
<span style="white-space:pre"> </span>| redmedian << 16
<span style="white-space:pre"> </span>| greenmedian << 8
<span style="white-space:pre"> </span>| bluemedian;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span> bmp1.setPixels(newpix, 0, width, 0, 0, width, height);
<span style="white-space:pre"> </span>newpix = null;
<span style="white-space:pre"> </span>oldpix = null;
<span style="white-space:pre"> </span>secondphoto.setImageBitmap(bmp1);// 显示新位图
用Array自带的sort方法进行排序,拿到中间值,不用再自己写冗长的排序算法,冒泡快排统统不用,感觉世界都变得清爽了。
3.锐化操作:
图像锐化处理的主要目的是突出图像中的细节或者增强被模糊了的细节。图像锐化处理的方法多种多样,其应用范围也十分广泛,例如医学成像、工业检测以及军事系统的制导等等。在前述内容中,我们看到空间均值滤波会使得图像变模糊,这是因为均值处理与积分相类似;而锐化处理则可以用空间微分来完成,微分算子的响应强度与图像在该点的突变程度有关,即图像微分增强了边缘和其他突变(如噪声)并削弱了灰度变化缓慢的区域。
图像锐化滤波器按照微分的阶数,可分为基于一阶微分和基于二阶微分的滤波器。在图像处理领域,我们最感兴趣的微分性质是恒定灰度区域(平坦段)、突变的起始点与结束点(阶梯和斜坡突变)以及沿着灰度级斜坡处的特性,这些类型的突变可以将图像中的噪声点、细线与边界模型化。数学函数的微分可以用不同的术语定义,然而,对于一阶微分的任何定义都必须保证以下几点:(1)在平坦段(灰度不变的区域)微分值为零;(2)在灰度阶梯或斜坡的起始点处微分值非零;(3)沿着斜坡面微分值非零。任何二阶微分的定义也类似:(1)在平坦区微分值必为零;(2)在灰度阶梯或斜坡的起始点处微分值非零;(3)沿着斜坡面微分值非零。对图像增强来说,二阶微分处理比一阶微分好一些,因为其形成增强细节的能力好一些。而一阶微分在图像处理邻域主要用于边缘提取。
本实验主要讨论基于二阶微分的锐化滤波器。对于各种滤波器实现,我们最关注的是一种各向同性滤波器,这种滤波器的响应与滤波器作用的图像的突变方向无关。也就是说,各向同性滤波器是旋转不变的,即将原始图像旋转后进行滤波处理给出的结果与先对图像滤波,然后再旋转的结果相同。最简单的各向同性微分算子是拉普拉斯算子,一个图像函数f (x, y)的拉普拉斯变换可定义为:
在处理数字图像时,需要将公式4-2转变成离散形式。考虑到有两个变量,因此在x方向上对二阶偏微分采用以下定义:
类似的,在y方向上为:
因此,公式4-2的二维拉普拉斯变换也可以写作:
这个公式可以用图4-3 模板1来实现,它给出了以90o旋转的各向同性的结果。
0 | 1 | 0 |
1 | -4 | 1 |
0 | 1 | 0 |
图 4-3 模板1
其实,滤波模板的对角线方向也可以加人到离散拉普拉斯变换的定义中,只需在公式4-3中添加两项,即两个对角线方向各加一个。每一个新添加项的形式与式4-3或式4-4类似,只是其坐标轴的方向沿着对角线方向。由于每个对角线方向上的项还包含一个-2f (x, y),所以需要从不同方向的项中总共应该减去8f (x, y),其滤波模板如图4-4所示,此模板对45o旋转的结果是各向同性的。
1 | 1 | 1 |
1 | -8 | 1 |
1 | 1 | 1 |
图 4-4 模板2
锐化算法具体如下:
<pre name="code" class="java">
for (int i = halffilter; i < height
- halffilter; i++) {
for (int j = halffilter; j < width
- halffilter; j++) {
int red = 0, green = 0, blue = 0;
for (int x = i - halffilter; x <= i
+ halffilter; x++) {
for (int y = j - halffilter; y <= j
+ halffilter; y++) {
int index = x * width + y;
int k = -1;
if (x == i && y == j) {
k = 9;
}
int redadd = ((oldpix[index] >> 16) & 0xff)
* k;
int greenadd = ((oldpix[index] >> 8) & 0xff)
* k;
int blueadd = (oldpix[index] & 0xff)
* k;
red += redadd;
green += greenadd;
blue += blueadd;
}
}
if (red > 255) {
red = 255;
}
if (green > 255) {
green = 255;
}
if (blue > 255) {
blue = 255;
}
if (red < 0) {
red = 0;
}
if (green < 0) {
green = 0;
}
if (blue < 0) {
blue = 0;
}
int index = i * width + j;
newpix[index] = 0xff000000
| red << 16 | green << 8
| blue;
}
}
在采用拉普拉斯算子对图像作锐化处理时,其运算结果中或许会出现大于255或者小于0的点,这对于8位BMP图片就会造成溢出。在上面程序中,需要进行优化,一种简单的标度方法是,我们以锐化模板一为例,对每个运算结果再加上4 x 255然后除以8。如果希望更高的精确度,可以采用另一种方法:首先,提取最小运算结果min,并在所有运算结果中减去min(这样就可以使得运算结果的最小值为0),做完减法之后得到所有运算结果的最大值为max。然后,将每一个运算结果乘以255/max,这样就可以使图像中的所有像素值标定到0到255的范围中。
今天的项目就到这里。