了解高斯滤波之前,我们首先熟悉一下高斯噪声。高斯噪声是指它的概率密度函数服从高斯分布(即正态分布)的一类噪声。如果一个噪声,它的幅度分布服从高斯分布,而它的功率谱密度又是均匀分布的,则称它为高斯白噪声。高斯白噪声的二阶矩不相关,一阶矩为常数,是指先后信号在时间上的相关性,高斯白噪声包括热噪声和散粒噪声。
高斯滤波器是一类根据高斯函数的形状来选择权值的线性平滑滤波器。高斯平滑滤波器对于抑制服从正态分布的噪声非常有效。一维零均值高斯函数为:
g(x)=exp( -x^2/(2 sigma^2)
其中,高斯分布参数Sigma决定了高斯函数的宽度。对于图像处理来说,常用二维零均值离散高斯函数作平滑滤波器,高斯函数的图形:
对于图像来说,高斯滤波器是利用高斯核的一个2维的卷积算子,用于图像模糊化(去除细节和噪声)。
1) 高斯分布
一维高斯分布:
二维高斯分布:
2) 高斯核
理论上,高斯分布在所有定义域上都有非负值,这就需要一个无限大的卷积核。实际上,仅需要取均值周围3倍标准差内的值,以外部份直接去掉即可。 如下图为一个标准差为1.0的整数值高斯核。
高斯函数具有五个重要的性质,这些性质使得它在早期图像处理中特别有用.这些性质表明,高斯平滑滤波器无论在空间域还是在频率域都是十分有效的低通滤波器,且在实际图像处理中得到了工程人员的有效使用.高斯函数具有五个十分重要的性质,它们是:
(1)二维高斯函数具有旋转对称性,即滤波器在各个方向上的平滑程度是相同的.一般来说,一幅图像的边缘方向是事先不知道的,因此,在滤波前是无法确定一个方向上比另一方向上需要更多的平滑.旋转对称性意味着高斯平滑滤波器在后续边缘检测中不会偏向任一方向.
(2)高斯函数是单值函数.这表明,高斯滤波器用像素邻域的加权均值来代替该点的像素值,而每一邻域像素点权值是随该点与中心点的距离单调增减的.这一性质是很重要的,因为边缘是一种图像局部特征,如果平滑运算对离算子中心很远的像素点仍然有很大作用,则平滑运算会使图像失真.
(3)高斯函数的傅立叶变换频谱是单瓣的.正如下面所示,这一性质是高斯函数付立叶变换等于高斯函数本身这一事实的直接推论.图像常被不希望的高频信号所污染(噪声和细纹理).而所希望的图像特征(如边缘),既含有低频分量,又含有高频分量.高斯函数付立叶变换的单瓣意味着平滑图像不会被不需要的高频信号所污染,同时保留了大部分所需信号.
(4)高斯滤波器宽度(决定着平滑程度)是由参数σ表征的,而且σ和平滑程度的关系是非常简单的.σ越大,高斯滤波器的频带就越宽,平滑程度就越好.通过调节平滑程度参数σ,可在图像特征过分模糊(过平滑)与平滑图像中由于噪声和细纹理所引起的过多的不希望突变量(欠平滑)之间取得折衷.
(5)由于高斯函数的可分离性,较大尺寸的高斯滤波器可以得以有效地实现.二维高斯函数卷积可以分两步来进行,首先将图像与一维高斯函数进行卷积,然后将卷积结果与方向垂直的相同一维高斯函数卷积.因此,二维高斯滤波的计算量随滤波模板宽度成线性增长而不是成平方增长.
高斯滤波后图像被平滑的程度取决于标准差。它的输出是领域像素的加权平均,同时离中心越近的像素权重越高。因此,相对于均值滤波(mean filter)它的平滑效果更柔和,而且边缘保留的也更好。
高斯滤波被用作为平滑滤波器的本质原因是因为它是一个低通滤波器,见下图。而且,大部份基于卷积平滑滤波器都是低通滤波器。
图.高斯滤波器(标准差=3像素)的频率响应。The spatial frequency axis is marked
in cycles per pixel, and hence no value above 0.5 has a real meaning。
(1)移动相关核的中心元素,使它位于输入图像待处理像素的正上方
(2)将输入图像的像素值作为权重,乘以相关核
(3)将上面各步得到的结果相加做为输出
1 // gaosilvbo.cpp : 定义控制台应用程序的入口点。 2 // 3 4 #include "stdafx.h" 5 #include6 #include 7 8 typedef unsigned long DWORD; 9 typedef long LONG; 10 typedef unsigned short WORD; 11 typedef unsigned char BYTE; 12 13 typedef struct tagRGBQUAD { 14 BYTE rgbBlue; 15 BYTE rgbGreen; 16 BYTE rgbRed; 17 BYTE rgbReserved; 18 } RGBQUAD; 19 20 #pragma pack (2) /*指定按字节对齐*/ 21 typedef struct tagBITMAPFILEHEADER { 22 WORD bfType; 23 DWORD bfSize; 24 WORD bfReserved1; 25 WORD bfReserved2; 26 DWORD bfOffBits; 27 } BITMAPFILEHEADER; 28 29 //恢复对齐状态 30 typedef struct tagBITMAPINFOHEADER{ 31 DWORD biSize; 32 LONG biWidth; 33 LONG biHeight; 34 WORD biPlanes; 35 WORD biBitCount; 36 DWORD biCompression; 37 DWORD biSizeImage; 38 LONG biXPelsPerMeter; 39 LONG biYPelsPerMeter; 40 DWORD biClrUsed; 41 DWORD biClrImportant; 42 } BITMAPINFOHEADER; 43 44 unsigned char *pTempBmpBuf; //读入图像数据的指针 45 46 unsigned char *pBmpBuf; //读入图像数据的指针 47 48 49 int bmpWidth; //图像的宽 50 int bmpHeight; //图像的高 51 RGBQUAD *pColorTable; //颜色表指针 52 int biBitCount; //图像类型,每像素位数 53 54 bool readBmp(char *bmpName) 55 { 56 //二进制读方式打开指定的图像文件 57 58 FILE *fp=fopen(bmpName,"rb"); 59 if(fp==0) return 0; 60 61 62 //跳过位图文件头结构BITMAPFILEHEADER 63 fseek(fp, sizeof(BITMAPFILEHEADER),0); 64 65 //定义位图信息头结构变量,读取位图信息头进内存,存放在变量head中 66 BITMAPINFOHEADER head; 67 fread(&head, sizeof(BITMAPINFOHEADER), 1,fp); 68 69 //获取图像宽、高、每像素所占位数等信息 70 bmpWidth = head.biWidth; 71 bmpHeight = head.biHeight; 72 biBitCount = head.biBitCount; 73 74 75 //定义变量,计算图像每行像素所占的字节数(必须是的倍数) 76 int lineByte=(bmpWidth * biBitCount/8+3)/4*4; 77 78 //灰度图像有颜色表,且颜色表表项为 79 if(biBitCount==8){ 80 //申请颜色表所需要的空间,读颜色表进内存 81 pColorTable=new RGBQUAD[256]; 82 fread(pColorTable,sizeof(RGBQUAD),256,fp); 83 } 84 85 86 //申请位图数据所需要的空间,读位图数据进内存 87 pTempBmpBuf=new unsigned char[lineByte * bmpHeight]; 88 89 pBmpBuf=new unsigned char[lineByte * bmpHeight]; 90 fread(pTempBmpBuf,1,lineByte * bmpHeight,fp); 91 fseek(fp, 1078,0); 92 fread(pBmpBuf,1,lineByte * bmpHeight,fp); 93 94 //关闭文件 95 fclose(fp); 96 return 1; 97 } 98 99 bool saveBmp(char *bmpName, unsigned char *imgBuf, int width, int height,int biBitCount, RGBQUAD *pColorTable) 100 { 101 102 if(!imgBuf) 103 return 0; 104 105 //颜色表大小,以字节为单位,灰度图像颜色表为字节,彩色图像颜色表大小为 106 int colorTablesize=0; 107 if(biBitCount==8) 108 colorTablesize=1024; 109 110 //待存储图像数据每行字节数为的倍数 111 int lineByte=(width * biBitCount/8+3)/4*4; 112 113 //以二进制写的方式打开文件 114 FILE *fp=fopen(bmpName,"wb"); 115 if(fp==0) return 0; 116 117 //申请位图文件头结构变量,填写文件头信息 118 BITMAPFILEHEADER fileHead; 119 fileHead.bfType = 0x4D42;//bmp类型 120 121 //bfSize是图像文件个组成部分之和 122 fileHead.bfSize= sizeof(BITMAPFILEHEADER) 123 + sizeof(BITMAPINFOHEADER) 124 + colorTablesize + lineByte*height; 125 126 fileHead.bfReserved1 = 0; 127 fileHead.bfReserved2 = 0; 128 129 //bfOffBits是图像文件前个部分所需空间之和 130 fileHead.bfOffBits=54+colorTablesize; 131 132 //写文件头进文件 133 fwrite(&fileHead, sizeof(BITMAPFILEHEADER),1, fp); 134 135 //申请位图信息头结构变量,填写信息头信息 136 BITMAPINFOHEADER head; 137 head.biBitCount=biBitCount; 138 head.biClrImportant=0; 139 head.biClrUsed=0; 140 head.biCompression=0; 141 head.biHeight=height; 142 head.biPlanes=1; 143 head.biSize=40; 144 head.biSizeImage=lineByte*height; 145 head.biWidth=width; 146 head.biXPelsPerMeter=0; 147 head.biYPelsPerMeter=0; 148 149 //写位图信息头进内存 150 fwrite(&head, sizeof(BITMAPINFOHEADER),1, fp); 151 152 //如果灰度图像,有颜色表,写入文件 153 if(biBitCount==8) 154 fwrite(pColorTable, sizeof(RGBQUAD),256, fp); 155 156 //写位图数据进文件 157 fwrite(imgBuf, height*lineByte, 1, fp); 158 159 //关闭文件 160 fclose(fp); 161 return 1; 162 } 163 164 int _tmain(int argc, _TCHAR* argv[]) 165 { 166 //读入指定BMP文件进内存 167 char readPath[]="guass_test.bmp"; 168 readBmp(readPath); 169 //输出图像的信息 170 printf("width=%d,height=%d,biBitCount=%d\n", 171 bmpWidth,bmpHeight,biBitCount); 172 173 //每行字节数 174 int lineByte=(bmpWidth*biBitCount/8+3)/4*4; 175 //定义最终写入的数据体 176 //pBmpBuf=new unsigned char[lineByte * bmpHeight]; 177 //循环变量,图像的坐标 178 int y,x; 179 180 //循环变量,针对彩色图像,遍历每像素的三个分量 181 int k; 182 //单精度变量暂存计算后的灰度值(针对灰度图像) 183 float TempNum; 184 //指向TempBmpbuf的指针 185 unsigned char *TemPtr; 186 //指向BmpBuf的指针 187 unsigned char *Ptr; 188 //定义*3的模板(拉普拉斯) 189 float CoefArray[9]={1.0f,2.0f,1.0f,2.0f,4.0f,2.0f,1.0f,2.0f,1.0f}; 190 //定义模板前乘的系数(拉普拉斯) 191 float coef=(float)(1.0/16.0);; 192 193 //lapulas滤波 194 if(biBitCount==8){//对于灰度图像 195 for(y=1;y 1;y++){ 196 for(x=0;x 1;x++){ 197 198 TemPtr=pTempBmpBuf+y*lineByte+x; 199 Ptr=pBmpBuf+y*lineByte+x; 200 201 TempNum=(float)((unsigned char)*(TemPtr+lineByte-1))*CoefArray[0]; 202 TempNum+=(float)((unsigned char)*(TemPtr+lineByte))*CoefArray[1]; 203 TempNum+=(float)((unsigned char)*(TemPtr+lineByte+1))*CoefArray[2]; 204 TempNum+=(float)((unsigned char)*(TemPtr-1))*CoefArray[3]; 205 TempNum+=(float)((unsigned char)*TemPtr)*CoefArray[4]; 206 TempNum+=(float)((unsigned char)*(TemPtr+1))*CoefArray[5]; 207 TempNum+=(float)((unsigned char)*(TemPtr-lineByte-1))*CoefArray[6]; 208 TempNum+=(float)((unsigned char)*(TemPtr-lineByte))*CoefArray[7]; 209 TempNum+=(float)((unsigned char)*(TemPtr-lineByte+1))*CoefArray[8]; 210 211 TempNum*=coef; 212 213 if(TempNum>255.0) *Ptr =(BYTE)255; 214 else if(TempNum<0.0) 215 *Ptr =(unsigned char)fabs(TempNum); 216 //用到了fabs函数,需要添加math.h头文件 217 else *Ptr=(char)TempNum; 218 } 219 } 220 } 221 222 else if(biBitCount==24){//彩色图像 223 for(y=0;y 2;y++){ 224 for(x=0;x 2;x++){ 225 for(k=0;k<3;k++)//每像素RGB三个分量分别置才变成黑色 226 *(pBmpBuf+y*lineByte+x*3+k)=0; 227 } 228 } 229 } 230 //将图像数据存盘 231 char writePath[]="gauss_result.BMP"; 232 saveBmp(writePath, pBmpBuf, bmpWidth, 233 bmpHeight, biBitCount, pColorTable); 234 //清除缓冲区,pBmpBuf和pColorTable是全局变量,在文件读入时申请的空间 235 delete []pBmpBuf; 236 delete []pTempBmpBuf; 237 if(biBitCount==8) 238 delete []pColorTable; 239 return 0; 240 }