第 3 章 图 像 平 滑 本章主要讲述均值滤波、中值滤波、最小值滤波、最大值滤波、高斯滤波、二值图像滤波、数学形态学滤波、条件滤波等常用滤波方法,并讲述它们的思想和特点。 本章讲述滤波器的C++语言编程及其优化,包括“整数除法或者浮点数乘法和除法变为整数乘法和移位”“列积分”“积分图”“MMX及SSE”“直方图求中值”“直方图递推”“基于均值滤波的二值图像滤波”等程序优化技巧。 3.1噪声与图像平滑 3.1.1噪声 噪声(noise)这个词的原意是正常声音信号受到的干扰。凡是干扰人们休息、学习和工作以及对正常的声音产生干扰的声音,即不需要的声音,统称为噪声。后来,人们把这个概念推广到了更广泛的领域。 在图像处理领域,图像噪声是指存在于图像数据中不必要的干扰信息,表现为图像中引起较强视觉效果的孤立像素点或像素块。图像噪声的产生原因是多方面的: 一是图像采集设备产生的噪声,比如图像传感器CCD和CMOS在采集图像过程中,由于受传感器材料属性、工作环境、电子元器件和电路结构等影响,会引入各种噪声,如电阻引起的热噪声、场效应管的沟道热噪声、暗电流噪声、像素的非均匀性噪声; 二是在图像处理过程中,由于处理方法的缺陷,比如计算精度不够、模型考虑不全面、参数自适应能力不够、近似计算等产生的错误信息; 三是图像数据在传输过程中受到外界的电磁波干扰,或者在用胶片存储图像时的胶片老化等。 对一个图像处理系统而言,噪声的来源分为外部噪声和内部噪声。外部噪声是指由于该系统外的因素引起的噪声,比如以电磁波辐射或经电源引入系统而引起的噪声,如电器设备、天体放电现象等引起的噪声,电源纹波引起的噪声,图31所示就是电源纹波引起的噪声。内部噪声是指来自系统内的因素引起的噪声,比如图像传感器材质特性本身产生的噪声、各种接头的抖动引起电流变化等产生的噪声、系统内部的电源芯片或者晶振温度漂移等产生的噪声,如图32所示。 图31电源纹波对某红外热像仪的条纹干扰 图32夜晚光照不足时某图像传感器的噪声 在对噪声的处理和分析上,常进行如下分类。 (1) 按统计特性划分: 统计特性不随时间变化的噪声称为平稳噪声,反之称为非平稳噪声。 (2) 按噪声幅度分布形状划分: 呈高斯分布的噪声称为高斯噪声,呈雷利分布的噪声称为雷利噪声。 (3) 按噪声频谱的形状划分: 频谱均匀分布的噪声称为白噪声,频谱与频率成反比的噪声称为1/f噪声,频谱与频率平方成正比的噪声称为三角噪声。 (4) 按噪声n(t)和信号S(t)之间的关系划分: 输出信号为S(t)+n(t)形式的噪声称为加性噪声,输出信号为S(t)(1+n(t))形式的噪声称为乘性噪声。 为了便于分析和处理,往往将乘性噪声近似地认为是加性噪声,并总是假设信号和噪声互相统计独立。去除噪声的过程称作滤波,不同的滤波方法称作滤波器(filter)。 在图像处理和图像分析的算法中,为了保持一定的稳健性,一般需要先对原始图像做一定的滤波处理(称为图像预处理)。 3.1.2图像平滑概述 在图像处理中,用作去掉图像噪声的各种滤波方法统称为图像平滑(image smoothing)。图像平滑的字面意思是使一个像素到其相邻像素的灰度变化是平滑的。 在图像处理中,从空间域的观点看,滤波就是去掉突然变大或变小的灰度值,用一个合适的灰度值替代该值。很显然,一个像素的灰度值是不是噪声,是由该像素在图像中的位置决定的,通过它与相邻像素的比较才能判定它的灰度值是异常(突然变大还是变小)还是正常。比如,一个人的收入为1000美元/月,这个收入在有些国家可能是属于贫困的,但在另一些国家可能是属于富裕的。那么,既然一个像素是否是噪声是由其邻域决定的,显然去掉噪声也得通过邻域计算,因此空间域的图像滤波属于邻域运算。 看下面一组测量数据D1: 3339339993999,其数据曲线如图33所示。 图33原始数据曲线 该曲线上有2个明显的毛刺(噪声),如画圈处所示。在波形的前半截,数值从“3”突然变成“9”,在波形的后半截,数值从“9”突然变成“3”。 如前所言,空间域的图像滤波是邻域运算,而邻域有大小和形状之说,运算有何种运算之说,因此本章的主要内容就是围绕运算和邻域两个方面展开的。 3.2均值滤波 3.2.1均值滤波的定义 一个朴素的想法是,既然噪声表现为它的灰度值与周围相比有异常变化,比如突然变大或者突然变小,那么把它与周围的像素做灰度平均即可。比如,一个宿舍有5个同学,其中1个同学特别富有,那么把财富平均即可。也就是说,这种滤波算法的运算采用的是求均值,因此就称为均值滤波。 【定义31】均值滤波(average filtering)就是以当前像素为中心取一个邻域,用该区域的所有像素灰度值的均值作为该像素滤波后的灰度值。 那么如何设定邻域的大小和形状呢?在一维数据上,可以取相邻3像素,也可以取相邻5像素,还可以取相邻7像素,总之邻域的大小是奇数(要以当前像素为中心左右对称)。 对前面的数据D1,假定取相邻3像素作为邻域,其均值滤波的计算过程和结果如下: 原值求均值均值 3(邻域不完整,保持原值)3 3(3+3+3)/33 3(3+3+9)/35 9(3+9+3)/35 3(9+3+3)/35 3(3+3+9)/35 9(3+9+9)/37 9(9+9+9)/39 9(9+9+3)/37 3(9+3+9)/37 9(3+9+9)/37 9(9+9+9)/39 9(邻域不完整,保持原值)9 经过以上计算,数据变得平滑了,其结果是3355557977799,如图34所示。 图34均值滤波后的结果 上述均值滤波过程可以描述为: G(x)=g(x-1)+g(x)+g(x+1)3(31) 进一步形式化,一维均值滤波的形式化写法为: G(x)=∑mj=-mg(x+j)2m+1(32) 对于常规的二维图像而言,邻域常取矩形(习惯上称为滤波窗口,一般是正方形)。设此矩形的大小为n行m列(m,n均为奇数),则二维均值滤波的形式化写法为: G(x,y)=∑n2i=-n2∑m2j=-m2g(x+j,y+i)m×n(33) 其含义就是以当前像素为中心取出矩形块内所有像素的灰度值累加,再除以矩形块内像素的个数,即均值。 3.2.2邻域与卷积运算 式(33)可以描述矩形的、正方形的邻域,但除此之外,邻域还可以是其他任意形状,此时它就不能很好地表述了,该怎么处理呢?比如,采用当前像素周围5像素的均值时,如图35所示。 于是想到用一个小的图形来表示邻域,该图形称作模板(template)。该图形中参与运算的像素对应位置设为“1”,不参与运算的像素对应位置设为“0”,有时也用不填值代表0,即图36。可以看出图36是一个3×3的矩阵,记为T。则均值滤波的公式(33)用模板表示为: G(x,y)=∑n2i=-n2∑m2j=-m2g(x+j,y+i)Tm2+j,n2+i∑n2i=-n2∑m2j=-m2Tm2+j,n2+i(34) 图35十字形邻域 图36模板表示 式(34)的含义是像素邻域的灰度值与模板的值做对应位置相乘,得到其总和,再用总和除以模板中不为0的元素的个数。显然,模板中取0的元素与对应像素的灰度值相乘后为0,相当于对应像素的灰度值没有累加到总和中,即该像素没有起作用。 模板就是一个小的矩阵,像素邻域中的每个像素分别与该矩阵的每个元素对应相乘,这是一种卷积运算(convolution),所以模板又常称为卷积核,均值滤波又被看成是一种卷积运算。 因此,采用模板的形式可以表示任意的邻域。在图像处理中,常用的均值滤波模板的形状有方形和圆形两种,大小有3×3和5×5两种,如图37~图310所示,式(34)中的分母值就是模板中数值不为0的像素个数。 T3=19111 111 111 图373×3的方形滤波器 T3=15010 111 010 图383×3的十字形滤波器 T5=12511111 11111 11111 11111 11111 图395×5的方形滤波器 T5=12101110 11111 11111 11111 01110 图3105×5的圆形滤波器 3.2.3均值滤波的特点 均值滤波的特点是: (1) 滤波结果是灰度值大的像素在滤波后灰度值变小,灰度值小的像素在滤波后灰度值变大,图像总灰度值之和不变,即图像的均值(亮度)不变; (2) 因为灰度值大的像素在滤波后灰度值变小、灰度值小的像素在滤波后灰度值变大,所以图像的标准差(对比度)变小,图像变模糊; (3) 在目标和背景的边界上,滤波前后灰度值的变化尤其大,所以边界模糊得尤其突出; (4) 均值滤波采用的邻域越大则模糊越突出。 图311是原始的灰度图像,其均值是108,标准差是42; 图312是5×5的均值滤波的图像,其均值是108,标准差是39。图313是原始的灰度图像,其均值是144,标准差是101; 图314是5×5的均值滤波的图像,其均值是144,标准差是101。 可以思考一下,图312和图314都是5×5的均值滤波图像,分别和原始图像相比,为什么图312标准差的变化较大而图314的标准差几乎没有变化。 图311原始图像(均值=108, 标准差=42) 图3125×5的均值滤波图像 (均值=108,标准差=39) 图313原始图像(均值=144, 标准差=101) 图3145×5的均值滤波图像 (均值=144,标准差=101) 3.2.4基于列积分的快速均值滤波 求均值的过程就是一个多次累加和一次除法的过程,如果邻域大小为101×101,难道均值滤波需要对每个像素求邻域均值且都进行1万多(10201)次加法吗?那么一幅1080p(1920×1080像素)灰度图像的均值滤波岂不是要进行200多亿(21152793600)次加法?显然均值滤波肯定不是这么计算的。本书作者在多年前提出了一种基于列积分的快速均值滤波方法。 在图315和图316中,两个框分表代表两个相邻像素的邻域。可以发现,相邻像素的邻域是重叠的,且重叠的部分相当大,这样就意味着重叠部分的灰度累加不用重复进行。对于n行×m列的邻域,在某行中从一个像素处理到下一个像素时,邻域仅仅是去掉最左列积分并增加右列积分,如图315所示,仅访问2n个数据; 在某列中,从一个像素移动到下一个像素时,邻域仅仅是去掉最上行和增加下行,仅访问2m个数据,如图316所示。显然,访问的数据量无论是2n还是2m都远远小于整个邻域的大小n×m。 实际远不止如此,计算还可以更加快速。在同一行上,如果先把n行数据进行按列求和(列积分),并将列积分放到一个数组sumCol[x]中,x=0…width-1,则邻域从当前像素向右移动到其下一个像素时,只需要减去该邻域最左边的列积分,再加上其右边的列 图315左右相邻的两个像素的邻域 图316上下相邻的两个像素的邻域 积分,这样就只需要1次减法运算和1次加法运算。当从当前行移动到下一行时,则只需要把sumCol[x]更新一下即可,这个更新也非常简单,只需要将sumCol[x]减去该邻域的最上一行像素的灰度值,并加上其下一行像素的灰度值即可,也只需要1次减法运算和1次加法运算。算法描述如下。 【算法31】基于列积分的快速均值滤波 void RmwAvrFilterBySumCol( BYTE *pGryImg,//原始灰度图像 int width, int height,//图像的宽度和高度 int M, int N, //滤波邻域: M列N行 BYTE *pResImg //结果图像 ) { //没有对边界上邻域不完整的像素进行处理,可以采用变窗口的策略 BYTE *pAdd, *pDel, *pRes; int halfx, halfy; int x, y; int sum,c; int sumCol[4096]; //约定图像宽度不大于4096 // step.1------------初始化--------------------------// M = M/2*2+1; //奇数化 N= N/2*2+1; //奇数化 halfx = M/2; //滤波器的x半径 halfy = N/2; //滤波器的y半径 c = (1<<23)/(M*N); //乘法因子 memset(sumCol, 0, sizeof(int)*width); for (y = 0, pAdd = pGryImg; y>23; //用整数乘法和移位代替除法 //换列,更新灰度和 sum -= sumCol[x-halfx]; //减去左边的列积分 sum += sumCol[x+halfx+1]; //加上右边的列积分 } pRes += halfx; //跳过右侧邻域不完整的像素 //换行,更新sumCol for (x = 0; x>23; //用整数乘法和移位代替除法 } pRes += halfx; //跳过右侧 } // step.3------------返回----------------------------// return; } 3.2.6基于列积分的积分图实现 积分图的计算公式(38)非常简单,很容易实现。根据处理器架构的不同,比如CPU(比如X86的SIMD指令集)或者GPU(比如NV的SIMT架构),积分图应有多种不同的快速实现形式。根据3.2.4节中给出的 图319使用列积分的积分图计算 “列积分”概念,下面给出一个基于列积分的积分图计算方法。 假设在y0行上,已经知道了每列之和sumCol(x,y0),则积分图s(x,y0)的值为: s(x,y0)=∑xj=0sumCol(x,y0)(310) 如图319中,已经知道了y=3时的x=0,x=1,x=2时列积分如下,分别如图319中不同底色所示。 sumCol(0,3)=1+9+17+25 sumCol(1,3)=2+10+18+26 sumCol(2,3)=3+11+19+27 显然有: s(0,3)=sumCol0,3 s(1,3)=sumCol(0,3)+sumCol(1,3) s(2,3)=sumCol(0,3)+sumCol(1,3)+sumCol(2,3) 写成递推形式就是: s(0,3)=sumCol(0,3) s(1,3)=s(0,3)+sumCol(1,3) s(2,3)=s(1,3)+sumCol(2,3) 【算法33】基于列积分的积分图计算 void RmwDoSumGryImg( BYTE *pGryImg, //原始灰度图像 int width, //图像的宽度 int height,//图像的高度 int *pSumImg //计算得到的积分图 ) { BYTE *pGry; int *pRes; int x, y; int sumCol[4096]; //约定图像宽度不大于4096 memset(sumCol, 0, sizeof(int)*width); for (y = 0, pGry = pGryImg, pRes=pSumImg; y//SIMD指令集内建函数的头文件 void RmwDoSumGryImg_SSE( BYTE *pGryImg, //原始灰度图像 int width, int height,//图像的宽度和高度 int *pSumImg //计算得到的积分图 ) {//width必须为4的倍数 _declspec(align(16)) int sumCol[4096]; //约定图像宽度不大于4096,16字节对齐 __m128i *pSumSSE,A; BYTE *pGry; int *pRes; int x, y; memset(sumCol, 0, sizeof(int)*width); for (y = 0, pGry = pGryImg, pRes=pSumImg; y即可。 3.3中值滤波 3.3.1中值滤波的由来 仔细观察图33和图34就会发现,一个带有噪声的方波,经过均值滤波后却变成了一个斜波,滤波结果模糊了两类数值“3”和“9”之间的边界。从图311和图312、图313和图314也看到均值滤波后的图像变模糊了。也就是说,均值滤波在去除噪声的同时,带来了很大的负面作用: 它使得图像变模糊了。 如何解决这个问题呢?首先来分析一下变模糊的原因是什么。模糊的原因就是没有区分像素的类别,而将不同类别像素的灰度值进行了平均,比如没有区分是牛还是老鼠,而将牛的体重和老鼠的体重进行了累加,将其均值作为牛(老鼠)的均值滤波后的体重。那么,怎么区分是牛还是老鼠呢?难道在均值滤波以前还要先做分类? 体重在不同类别之间求平均是不对的(会生成不存在的物种),因此可以先在邻域内进行判断,如果牛的数量多就取牛的体重,如果老鼠的数量多就取老鼠的体重,是从里面挑出一个体重,而不是求均值去生成一个新的体重,这样就把分类的问题转换为谁多的问题。那么在邻域内是牛多还是老鼠多呢?显然,将它们的体重进行从小到大排序,谁多谁就出现在排序的最中间位置。比如,4只老鼠5头牛,则排序后的第5号位置肯定是牛的体重; 若5只老鼠4头牛,则排序后的第5号位置肯定是老鼠的体重。因此,取排序后位于最中间位置的体重作为滤波后的体重即可,这可能就是中值滤波的由来。 3.3.2中值滤波的定义 【定义33】将n(n为奇数)个数据按其值di进行从大到小或者从小到大排列后得到一个有序序列d0d1…dn-1,则dn2称为中值。例如,有序序列10,11,12,13,14,15,16,17,18的n=9,有92=4,则中值为d4,即14。 【定义34】中值滤波(median filtering)就是以当前像素为中心取一个邻域,用该区域的所有像素灰度值的中值作为该像素滤波后的灰度值。 在前面的例子数据D1中,取相邻3个像素作为邻域,其中值滤波的计算过程和结果如下: 原值邻域值中值 3(邻域不完整,保持原值)3 3(3,3,3)3 3(3,3,9)3 9(3,9,3)3 3(9,3,3)3 3(3,3,9)3 9(3,9,9)9 9(9,9,9)9 9(9,9,3)9 3(9,3,9)9 9(3,9,9)9 9(9,9,9)9 9(邻域不完整,保持原值)9 经过中值滤波以后,可以看出滤波后的结果完全去掉了噪声,这是因为像素受到噪声污染后的灰度值在邻域内要么是最大值,要么是最小值,而不是中值。数据D1的中值滤波结果如图320所示。 图320中值滤波后的结果 对图313取与图314相同大小的邻域进行中值滤波,得到的结果如图321所示。进一步增大滤波器的尺寸,得到图322和图323。可以看出,随着滤波尺寸的变大,中值滤波得到的结果图像没有变得越来越模糊,黑色圆形区域的边界仍然清晰,且更加干净,说明噪声得到了滤除。这体现了中值滤波不会使图像变模糊,具有良好的边界清晰度保持能力。 图3215×5的中值滤波 图3229×9的中值滤波 但是当滤波器的大小增加到21×21时,黑色圆形区域却发生了严重的变形,如图324所示。这是因为随着邻域的增大,不能保证邻域内最多有2类目标,此时有了白色区域、黑色的圈、灰色的外环和黑色的背景,多达4类目标,不满足中值滤波的成立条件。 中值滤波在实际应用时,其邻域大小的选择要保证该邻域内最多有2类目标。至于受到噪声干扰的像素,其灰度值在邻域内要么最大,要么最小,一般不会是中值,所以会被滤除。当邻域不大时,可以认为邻域内最多有两类目标。 图32311×11的中值滤波 图32421×21的中值滤波 3.3.3中值滤波的特点 中值一定是邻域内某个像素的灰度值而不是某几个像素灰度的生成值(相比于它,均值就像是一个伪造的值),所以中值滤波不会使图像变模糊,这是它的优点; 但是,中值滤波需要排序,而排序的复杂度远比相加求和大得多,所以中值滤波的速度要比均值滤波慢很多,这是它的缺点。其实,在图像处理中,排除编程技巧等因素,几乎可以说算法越复杂,则执行速度越慢,处理效果越好。 3.3.4中值滤波的快速实现 如上所述,中值滤波需要排序,学过“数据结构”的人都知道在排序操作上花费时间非常多。假设邻域大小为101×101,即10201个像素,即使采用快速排序算法对它们的灰度值进行排序,按照快速排序时间复杂度O(nlbn)计算,也至少需要135840次比较。求一个像素的邻域中值都需要十多万次的比较,这对于一幅动辄有几百万像素的图像而言,其速度之慢是无法忍受的。 回顾一下第2章的直方图,通过直方图很容易求出一个邻域的最大灰度值、最小灰度值和均值。如果给邻域建立一个灰度直方图,那么是否可以通过直方图求出这个邻域的灰度中值呢?比如一个班有55个同学考高等数学,有55个成绩,哪个成绩是中值呢?显然是在这个成绩之前有27个人。因此可以用一个计数器,从0分开始计数,若计数到某个成绩时计数值大于27,则这个成绩就是中值。 【算法35】使用直方图求灰度中值 void GetMedianGry(int *histogram, int N, int *medGry)//N是像素总个数 { int g; int num; // step.1-------------求灰度中值------------------------// num = 0; for (g = 0; g<256; g++) { num += histogram[g]; if (2*num>N) break; //num>N/2 } *medGry = g; // step.2-------------结束------------------------------// return; } 利用直方图中数据本身的有序特性,使得求图像的中值非常简单,大大减少了比较次数。从算法35可以看出,利用直方图求中值最多进行256次比较,因此可以利用直方图来快速得到中值。 此外,通过图315和图316可以发现,相邻像素的邻域有很大的交集区域,因此在求一个像素的邻域直方图时,若能利用其前一个像素的邻域直方图,就可以大大减少建立直方图的时间。比如,中值滤波在同一行中是从左向右进行的,当从像素A向右移动一个像素到达像素B时,B的邻域直方图就是A的邻域直方图丢掉其最左边列的像素灰度值,再增加一个新右边列的像素灰度值。 使用直方图进行中值滤波的算法如下。 【算法36】使用直方图的快速中值滤波 double RmwMedianFilter( BYTE *pGryImg, int width, int height, int M, int N,//滤波邻域: M列N行 BYTE *pResImg ) {//每行建一个直方图,没有进行相邻行直方图递推和图像边界上像素变窗口等处理 BYTE *pCur, *pRes; int halfx,halfy,x, y, i, j, y1, y2; int histogram[256]; int wSize, j1, j2; int num, med, v; int dbgCmpTimes = 0; //搜索中值所需比较次数的调试 M = M/2*2+1; //奇数化 N = N/2*2+1; //奇数化 halfx = M/2; //x半径 halfy = N/2; //y半径 wSize = (halfx*2+1)*(halfy*2+1); //邻域内像素总个数 for (y = halfy, pRes = pResImg+y*width; ywSize) { med = i; break; } } //滤波 pRes += halfx; //没有处理图像左边界侧的像素 for (x = halfx; xwSize) break; } dbgCmpTimes += 1; //总的比较次数,调试用 } else //到上次中值med的个数多了,则med要变小 { while ((num-histogram[med])*2>wSize)//若减去后,仍变小 { dbgCmpTimes++; //总的比较次数,调试用 num -= histogram[med]; med--; } dbgCmpTimes += 2; //总的比较次数,调试用 } //end of x } pRes += halfx; //没有处理图像右边界侧的像素 } //end of y //返回搜索中值需要的平均比较次数 return dbgCmpTimes*1.0/((width-halfx*2)*(height-halfy*2)); } step.4中有一个用来搜索中值的循环,其比较次数不固定。但是,从中值滤波的实践来看,平均搜索次数非常有限(一般就在2~3,极个别图像会达到9左右),跟邻域大小没有太直接的关系。比如邻域大小为101×101,即有10201个像素时,相邻两个像素的邻域有10201-101=10100个共同的像素,不会因为101个像素的变化就引起中值的剧烈变化,所以step.4中的循环执行次数是很少的(分析一下dbgCmpTimes的值就能知道),相比而言,step.3的直方图递推浪费的时间更多。 3.4极值滤波 分析均值滤波和中值滤波,可以知道它们分别是取一个数据集合(邻域内灰度值的集合)的均值和中值。对于一个数据集合而言,该数据集合除了有均值和中值外,还有2个最直观的特性,分别是最小值和最大值,那么最大值和最小值是否可以用于滤波呢? 如果事先能够知道噪声像素的灰度值是比周围像素的灰度值大,那么就可以使用邻域内的最小值来替代当前像素的灰度值,称为最小值滤波(minimum filtering)。反之,称为最大值滤波(maximum filtering)。 图325是带有反光的水面图像,若把反光当作噪声,那么采用最小值滤波应该可以去掉反光。图326是对图325使用5×5最小值滤波的结果,可以看到反光像素基本被去掉了。 图325原始图像 图326最小值滤波结果 3.5高斯滤波 式(31)是对3个像素的灰度值求平均值,将其按照式(34)的模板形式进行表示,得到式(311),对应的模板如图327所示。图328虽然是5邻域均值滤波的模板,但是该模板中最左侧和最右侧的值为“0”,相当于没有起作用,它实际上就是3邻域的均值滤波。 G(x)=∑1j=-1g(x+j)×T(j)∑1j=-1T(j)(311) 图3273邻域均值滤波 图3285邻域均值滤波 对于图328表示的模板,可以这样认为: 因为该邻域内的最左侧像素、最右侧像素到中心像素的距离远了,所以把它们对G(x)的贡献度设为0,令它们对均值没有贡献。因此,模板中的“1”和“0”可以看成是权重,将图327转换为权重函数的形式即得到图329。 简单地将权重设置成要么“1”要么“0”,没有柔和地体现“近处贡献度大、远处贡献度小”的特性。那么,用什么函数能够更好地体现贡献度的变换呢?均值为0的高斯函数是一个常用的权重函数,如图330所示。其表达式为: T(j)=12πδ2e-j22δ2(312) 图3293邻域均值滤波的权重函数 图330高斯函数 式(312)中,σ为高斯函数的标准差。当j距离原点越远时,T(j)值越小。由于该函数有点复杂,不容易直观地看出它的取值,因此表31给出了j在不同取值下的T(j)值。 表31高斯函数数值表 j e-j22δ2的值 σ=1时 T(j) 的值 σ=2时 T(j) 的值 0 1.000000 0.282095 0.141047 0.5σ 0.882497 0.248948 0.124474 1.0σ 0.606531 0.171099 0.085550 1.5σ 0.324652 0.091583 0.045791 2.0σ 0.135335 0.038177 0.019089 2.5σ 0.043937 0.012394 0.006197 3.0σ 0.011109 0.003134 0.001567 从表31可以看出,就e-j22δ2的值而言,距离中心像素3倍σ距离的像素的权重0.011109比中心像素的权重1.0降低了约1/100,已经降到了可以忽略的程度。因此,使用高斯函数加权均值滤波时,邻域的半径一般定为3σ,半径超过3σ时,对滤波结果的影响不大。比如,当σ=1,j=3时,T(j)的值为0.003134,即使像素g(x+j)的值为最大值255,g(x+j)×T(j)的值也小于1.0,可以忽略不计了。滤波邻域的半径和标准差σ具有一一对应的关系。 习惯上把使用高斯函数作为权重函数的均值滤波,该滤波称为高斯滤波(Gaussian average filtering)或者高斯平滑。同其他均值滤波一样,高斯滤波也会导致结果图像变模糊,所以有时也称为高斯模糊(Gaussian blur)。 图330和式(312)是一维的高斯函数,图像滤波的邻域一般是二维的,此时应采用二维高斯函数,如式(313)所示。 T(i,j)=12πσ2e-(j2+i2)2σ2(313) 高斯滤波的特点是: 当σ越大时,高斯函数也越平缓,如图330所示; 当σ越大时,邻域的大小也越大; 邻域大小越大,就有更多的像素参加滤波,图像模糊得也就越突出,如图331和图332所示。 图331高斯滤波(半径为4) 图332高斯滤波(半径为5) 在实际应用中,为了快速计算,二维的高斯滤波可以被分解为两个一维的高斯滤波,比如先对图像按行进行一维的高斯滤波,再对得到的结果图像按列进行一维的高斯滤波。另外,为了提高高速缓存的命中率,从而提高速度,还可以把行滤波得到的结果图像进行90°转置,把按列进行的一维高斯滤波变为按行的一维高斯滤波,最后再把得到的最终结果图像转置回来。 3.6二值图像滤波与数学形态学滤波 3.6.1基于均值滤波的二值图像滤波 前面以灰度图像为例,讲述了均值滤波、中值滤波、极值滤波以及高斯滤波,对于彩色图像的滤波完全可以按照灰度图像滤波的方法进行,只不过彩色图像中每个像素有3个分量而已。 那么对于二值图像有没有特别的滤波方法?首先来分析一下二值图像的特点。 (1) 二值图像只有两种灰度值,比如分别是0或者255。 (2) 二值图像的灰度最小值和灰度最大值不用求取,最小值为0,最大值为255。 (3) 二值图像可以进行均值滤波,但是不能在结果图像中给像素赋值为0和255以外的其他灰度值。 (4) 二值图像可以进行中值滤波,但是肯定不用进行排序。在一个邻域内,若0的个数多,中值就是0,若将其当作灰度图像,此时邻域内灰度均值小于128; 反之,中值就是255,若将其当作灰度图像,此时邻域内灰度均值大于或等于128。 基于以上特点,本书作者给出了一个基于均值滤波思想的二值图像最小值、最大值和中值滤波的方法(见算法37),其基本思想是二值图像当作灰度图像处理,具体如下。 (1) 当邻域内灰度均值大于或等于255时,赋值255; 否则,赋值0。这就是最小值滤波。 (2) 当邻域内灰度均值大于0时,赋值255; 否则,赋值0。这就是最大值滤波。 (3) 当邻域内灰度均值大于或等于128时,赋值255; 否则,赋值0。这就是中值滤波。 【算法37】基于均值滤波的二值图像滤波 void RmwBinImgFilter( BYTE *pBinImg,//原始二值图像 int width, int height,//图像的宽度和高度 int M, int N, //滤波邻域: M列N行 double threshold, //灰度阈值,大于或等于该值时结果赋255 BYTE *pResImg //结果图像 ) {//没有对边界上邻域不完整的像素进行处理,可以采用变窗口的策略 BYTE *pAdd, *pDel, *pRes; int halfx, halfy; int x, y, sum, sumThreshold; int sumCol[4096]; //约定图像宽度不大于4096 //step.1------------初始化--------------------------// M = M/2*2+1; //奇数化 N= N/2*2+1; //奇数化 halfx = M/2; //滤波器的x半径 halfy = N/2; //滤波器的y半径 sumThreshold = max(1,(int)(threshold*M*N)); //转换为邻域内灰度值之和的阈值 memset(sumCol, 0, sizeof(int)*width); for (y = 0, pAdd = pBinImg; y=sumThreshold)*255; //理解这个表达式的含义 //换列,更新灰度值的和 sum -= sumCol[x-halfx]; //减去左边列的灰度值 sum += sumCol[x+halfx+1]; //加上右边列的灰度值 } pRes += halfx; //跳过右侧 //换行,更新sumCol for (x = 0; x