图像处理均值滤波c++

2017-06-12 14:53:30 u011776903 阅读数 5524

1.均值滤波

        含义:把每个像素都用周围的8个像素来做均值操作 。

均值滤波器有什么用处呢?

      主要还是平滑图像的用处, 有的图像的锐度很高,用这样的均值算法,可以把锐度降低。使得图像看上去更加自然。(只能微弱的降低噪声,不能剔除噪声,对降噪几乎没用)

源码:

#include "highgui.h"    
#include "cv.h"    

void MeanFilter(uchar* smooth, int width, int height)
{
		    
	int sum = height * width * sizeof(uchar);//图像所占内存容量     
	uchar *corrupted = (uchar*)malloc(sum);
	memcpy((char*)corrupted, (char*)smooth, sum);
	
	for (int j = 1; j<height - 1; j++)
	{
		for (int i = 1; i<width - 1; i++)
		{
			smooth[j*width + i] = (corrupted[(j - 1)*width + (i - 1)] + corrupted[(j - 1)*width + i] + corrupted[(j - 1)*width + (i + 1)] +
				corrupted[j*width + (i - 1)] + corrupted[j*width + i] + corrupted[j*width + (i + 1)] +
				corrupted[(j + 1)*width + (i - 1)] + corrupted[(j + 1)*width + i] + corrupted[(j + 1)*width + (i + 1)]) / 9;
		}
	}
}

void imgOperate(IplImage* image)
{
	cvNamedWindow("image-in", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("image-access", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("image-out", CV_WINDOW_AUTOSIZE);
	cvShowImage("image-in", image);
	//将色彩图像强制转化为灰度图像    
	IplImage* pGrayImg = NULL;
	IplImage* pImg = NULL;
	pGrayImg = cvCreateImage(cvGetSize(image), 8, 1);
	pImg = cvCreateImage(cvGetSize(image), 8, 1);
	cvCvtColor(image, pGrayImg, CV_RGB2GRAY);
	//添加高斯噪声  
	cvZero(pImg);
	CvRNG rng = cvRNG(-1);  //初始化随机数发生器  
	cvRandArr(&rng, pImg, CV_RAND_NORMAL, cvScalarAll(0), cvScalarAll(15));
	cvAdd(pGrayImg, pImg, pImg);
	cvShowImage("image-access", pImg);
	MeanFilter((unsigned char*)pImg->imageData, pImg->width, pImg->height);
	cvShowImage("image-out", pImg);
	cvReleaseImage(&pGrayImg);
	cvWaitKey(0);
	cvDestroyWindow("image-in");
	cvDestroyWindow("image-out");
}

int main()
{
	IplImage* img = cvLoadImage("E:\\11.jpg");
	imgOperate(img);
	cvReleaseImage(&img);
	return 0;
}




2.中值滤波

        均值滤波是像素周围的3*3的像素做平均值操作, 那么中值就是在3*3中的像素中寻找中值。


        可以清晰地看到, 这里的6,2,0,3,97,4,19,3,10这些像素最终都被中值4取代。

源码:

#include "highgui.h"    
#include "cv.h"    

void MedianFilter(uchar* smooth, int width, int height)
{
		    
	int sum = height * width * sizeof(uchar);//图像所占内存容量     
	uchar *corrupted = (uchar*)malloc(sum);
	memcpy((char*)corrupted, (char*)smooth, sum);
	
	for (int j=1;j<height-1;j++)  
    {  
        for (int i=1;i<width-1;i++)  
        {  
            int k = 0;  
            unsigned char window[9];  
            for (int jj = j - 1; jj < j + 2; ++jj)  
                for (int ii = i - 1; ii < i + 2; ++ii)  
                    window[k++] = corrupted[jj * width + ii];  
            //   Order elements (only half of them)  
            for (int m = 0; m < 5; ++m)  
            {  
                int min = m;  
                for (int n = m + 1; n < 9; ++n)  
                    if (window[n] < window[min])  
                        min = n;  
                //   Put found minimum element in its place  
                unsigned char temp = window[m];  
                window[m] = window[min];  
                window[min] = temp;				
            }  
            smooth[ j*width+i ] = window[4];
			//printf("%d\n", window[4]);
        }  
    }  
}

void imgOperate(IplImage* image)
{
	cvNamedWindow("image-in", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("image-access", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("image-out", CV_WINDOW_AUTOSIZE);
	cvShowImage("image-in", image);
	//将色彩图像强制转化为灰度图像    
	IplImage* pGrayImg = NULL;
	IplImage* pImg = NULL;
	pGrayImg = cvCreateImage(cvGetSize(image), 8, 1);
	pImg = cvCreateImage(cvGetSize(image), 8, 1);
	cvCvtColor(image, pGrayImg, CV_RGB2GRAY);
	//添加高斯噪声  
	cvZero(pImg);
	CvRNG rng = cvRNG(-1);  //初始化随机数发生器  
	cvRandArr(&rng, pImg, CV_RAND_NORMAL, cvScalarAll(0), cvScalarAll(15));
	cvAdd(pGrayImg, pImg, pImg);
	cvShowImage("image-access", pImg);
	MedianFilter((unsigned char*)pImg->imageData, pImg->width, pImg->height);
	cvShowImage("image-out", pImg);
	cvReleaseImage(&pGrayImg);
	cvWaitKey(0);
	cvDestroyWindow("image-in");
	cvDestroyWindow("image-out");
}

int main()
{
	IplImage* img = cvLoadImage("E:\\11.jpg");
	imgOperate(img);
	cvReleaseImage(&img);
	return 0;
}


3.混合中值滤波器(Hybrid Median Filter


        首先当前像素的上下左右和自身取中值 , 然后左上右上左下右下和自身取中值 , 完了前面的两个值和当前像素值再取一次中值 , 得到的值就是最后的终极像素值了。

#include "highgui.h"    
#include "cv.h"    

unsigned char median(unsigned char* elements, int width)
{
	//   Order elements (only half of them)  
	for (int i = 0; i < (width >> 1) + 1; ++i)
	{
		//   Find position of minimum element  
		int min = i;
		for (int j = i + 1; j < width; ++j)
		if (elements[j] < elements[min])
			min = j;
		//   Put found minimum element in its place  
		unsigned char temp = elements[i];
		elements[i] = elements[min];
		elements[min] = temp;
	}
	//   Get result - the middle element  
	return elements[width >> 1];
}


void hybridMedianFilter(uchar* smooth, int width, int height)
{

	int sum = height * width * sizeof(uchar);//图像所占内存容量     
	uchar *corrupted = (uchar*)malloc(sum);
	memcpy((char*)corrupted, (char*)smooth, sum);

	for (int j = 1; j<height - 1; j++)
	{
		for (int i = 1; i<width - 1; i++)
		{
			unsigned char window[5];
			unsigned char results[3];
			//   Pick up cross-window elements  
			window[0] = corrupted[(j - 1) * width + i];
			window[1] = corrupted[j * width + i - 1];
			window[2] = corrupted[j * width + i];
			window[3] = corrupted[j * width + i + 1];
			window[4] = corrupted[(j + 1) * width + i];
			//   Get median  
			results[0] = median(window, 5);
			//   Pick up x-window elements  
			window[0] = corrupted[(j - 1) * width + i - 1];
			window[1] = corrupted[(j - 1) * width + i + 1];
			window[2] = corrupted[j * width + i];
			window[3] = corrupted[(j + 1) * width + i - 1];
			window[4] = corrupted[(j + 1) * width + i + 1];
			//   Get median  
			results[1] = median(window, 5);
			//   Pick up leading element  
			results[2] = corrupted[j * width + i];
			//   Get result  
			smooth[j*width + i] = median(results, 3);
		}
	}
}

void imgOperate(IplImage* image)
{
	cvNamedWindow("image-in", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("image-access", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("image-out", CV_WINDOW_AUTOSIZE);
	cvShowImage("image-in", image);
	//将色彩图像强制转化为灰度图像    
	IplImage* pGrayImg = NULL;
	IplImage* pImg = NULL;
	pGrayImg = cvCreateImage(cvGetSize(image), 8, 1);
	pImg = cvCreateImage(cvGetSize(image), 8, 1);
	cvCvtColor(image, pGrayImg, CV_RGB2GRAY);
	//添加高斯噪声  
	cvZero(pImg);
	CvRNG rng = cvRNG(-1);  //初始化随机数发生器  
	cvRandArr(&rng, pImg, CV_RAND_NORMAL, cvScalarAll(0), cvScalarAll(15));
	cvAdd(pGrayImg, pImg, pImg);
	cvShowImage("image-access", pImg);
	hybridMedianFilter((unsigned char*)pImg->imageData, pImg->width, pImg->height);
	cvShowImage("image-out", pImg);
	cvReleaseImage(&pGrayImg);
	cvWaitKey(0);
	cvDestroyWindow("image-in");
	cvDestroyWindow("image-out");
}

int main()
{
	IplImage* img = cvLoadImage("E:\\11.jpg");
	imgOperate(img);
	cvReleaseImage(&img);
	return 0;
}


2014-08-31 15:02:47 EbowTang 阅读数 34240

中值滤波

中值滤波法是一种非线性平滑技术,它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值.中值滤波是基于排序统计理论的一种能有效抑制噪声的非线性信号处理技术,中值滤波的基本原理是把数字图像或数字序列中一点的值用该点的一个邻域中各点值的中值代替,让周围的像素值接近的真实值,从而消除孤立的噪声点。方法是用某种结构的二维滑动模板,将板内像素按照像素值的大小进行排序,生成单调上升(或下降)的为二维数据序列。二维中值滤波输出为g(x,y)=med{f(x-k,y-l),(k,l∈W)} ,其中,f(x,y),g(x,y)分别为原始图像和处理后图像。W为二维模板,通常为3*3,5*5区域,也可以是不同的的形状,如线状,圆形,十字形,圆环形等。

以3*3的滤波窗口为例:

中值滤波就是在3*3中的像素中寻找中值。 来看这样一个描述图(不然无图无真相)

这把可以清晰地看到, 这里有6,2,0,3,97,4,19,3,10这些像素, 然后中间的这些像素值就被这些像素的中位数也就是中值取代了。为了满足和前面一篇文章的格式相对应, 我们马上进入下一个单元, 来看看在平滑和降噪方面的功效!

原图1                                                                           中值滤波之后

 

噪声图(5%)                                                                中值滤波后:

 

很明显中值滤波不仅使得图像变得平滑(也可以理解为图像变得模糊了),但是其对去除了椒盐噪声特别有效,因为中值滤波就是专门针对去除孤立点噪声孕育为生的。从这里中值的逻辑来看, 我们做中值操作的时候, 那么白色(接近255的像素点)和黑色(接近0的像素)因为在窗口排序时是最大最小值, 除非周围的颜色都是黑色或者白色,不然一般都会被剔除掉,所以,一般来说这个中值滤波是去除椒盐噪声的非常理想的选择。

最后还是贴一段我运行的代码:

////////////////中值滤波////////////////////////////////
int media_filter(unsigned char* inbuffer, int width, int height, unsigned char* outbuffer)
{
	int mid=0;
	int winsize = 3;//滤波窗口大小
	int* windows = new int[winsize*winsize];
	int pos = (winsize - 1) / 2;
	memcpy(outbuffer, inbuffer, width*height);
	for (int m = pos; m < height - pos; m++)
	{
		for (int n = pos; n < width - pos; n++)//(m,n)是窗口中心位置
		{
			//提取winsize*winsize的数据进入滤波窗
			int winpos = 0;
			for (int i = -pos; i < (winsize - pos); i++)
				for (int j = -pos; j < (winsize - pos); j++)
					windows[winpos++] = inbuffer[(m + i)*width + n + j];
			//对滤波窗中的数据排序取中值
			sort(windows, winsize*winsize);
			mid = windows[(winsize*winsize)/2];
			outbuffer[m*width + n] = mid;
		}
	}
	//对边界进行处理
	//未被处理的地方均取被处理的最边界
	for (int k = 0; k < pos; k++)
	for (int l = pos; l < width - pos; l++)
		outbuffer[k*width + l] = outbuffer[pos * width + l];//1到pos这几行未被处理的边界数据,始终取被处理的最上边界一层像素

	for (int a = height - pos; a < height; a++)
	for (int b = pos; b < width - pos; b++)
		outbuffer[a*width + b] = outbuffer[(height - pos - 1)*width + b];

	for (int c = 0; c < pos; c++)
	for (int d = 0; d < height; d++)
		outbuffer[d*width + c] = outbuffer[d*width + pos];

	for (int e = width - pos; e < width; e++)
	for (int f = 0; f < height; f++)
		outbuffer[f*width + e] = outbuffer[f*width + width - pos - 1];
	
	delete[] windows;
	windows = NULL;
	return 0;
}



均值滤波

把每个像素都用周围的9个像素来做均值操作 ”, 比如说这里有一个例子:

 

sample

非常明显的, 这个3*3区域像素的颜色值分别是5,3,6,2,1,9,8,4,7那么中间的1这个像素的过滤后的值就是这些值的平均值, 也就是前面的计算方法: (5+3+6+2+1+9+8+4+7)/9=5

一目了然。那么这个均值滤波器有什么用处呢?

主要还是平滑图像的用处, 有的图像的锐度很高,用这样的均值算法,可以把锐度降低。使得图像看上去更加自然,下面就有几幅图我们可以看出一些端倪:

原图:                                                                          原图均值滤波处理之后:

 

这里还是可以明显的感觉到不同的, 没有好坏之分,就是第二幅图片看上去更为平滑。 继续我们的问题, 那这里均值平滑是否具有去除噪声的功能呢? 我们搞来了椒盐噪声(就是随机的白点,黑点)来试试手:

噪声图(5%椒盐噪声):                         均值滤波平滑处理之后:

 

首先这里的噪声还是比较小的, 只有5%,从均值的效果来看的话, 我可以说几乎没有用,其实直观的想也可以判断, 因为这里的处理并没有剔除这些噪声点, 而只是微弱地降低了噪声,所以效果可以想见的。。

好吧, 最后的时候还是贴上一段处理的C++均值滤波算法代码:


////////////////均值滤波////////////////////////////////
int mean_filter(unsigned char* inbuffer, int width, int height, unsigned char* outbuffer)
{
	int mid=0;
	int winsize = 3;//滤波窗口大小
	int* windows = new int[winsize*winsize];
	int pos = (winsize - 1) / 2;
	memcpy(outbuffer, inbuffer, width*height);
	for (int m = pos; m < height - pos; m++)
	{
		for (int n = pos; n < width - pos; n++)//(m,n)是窗口中心位置
		{
			//提取winsize*winsize的数据进入滤波窗
			int winpos = 0;
			for (int i = -pos; i < (winsize - pos); i++)
				for (int j = -pos; j < (winsize - pos); j++)
					windows[winpos++] = inbuffer[(m + i)*width + n + j];
			mid=getaverage(windows, winsize*winsize);//对滤波窗中的数据取平均值
			outbuffer[m*width + n] = mid;
		}
	}
	//对边界进行处理
	//未被处理的地方均取被处理的最边界
	for (int k = 0; k < pos; k++)
	for (int l = pos; l < width - pos; l++)
		outbuffer[k*width + l] = outbuffer[pos * width + l];//1到pos这几行未被处理的边界数据,始终取被处理的最上边界一层像素

	for (int a = height - pos; a < height; a++)
	for (int b = pos; b < width - pos; b++)
		outbuffer[a*width + b] = outbuffer[(height - pos - 1)*width + b];

	for (int c = 0; c < pos; c++)
	for (int d = 0; d < height; d++)
		outbuffer[d*width + c] = outbuffer[d*width + pos];

	for (int e = width - pos; e < width; e++)
	for (int f = 0; f < height; f++)
		outbuffer[f*width + e] = outbuffer[f*width + width - pos - 1];
	
	delete[] windows;
	windows = NULL;
	return 0;
}


一种改进的中值滤波(HMF)

在中值滤波器和均值滤波器之后, 这次看到是中值滤波器的改进, Hybrid Median Filter ,这个名字我实在不好把握实在不知道翻译成什么比较妥当.只好沿用了混合动力车里面的混合二字, 姑且把这种滤波器命名为混合中值滤波器. 这个滤波器的工作流程较之原先有些繁琐, 来看看这张图

flow

图截的有些模糊, 但是还是看出了大致的流程, 首先当前像素的上下左右和自身取中值1 然后左上右上左下右下和自身取中值2 完了前面的两个中值和当前像素值再取一次中值 , 得到的值就是最后的终极像素值了. 可以看到这个流程还是有些复杂的.. 那他的效果如何捏? 我们还是和前几次一样来观察一把:

原图:                                                      HMF之后:

 

噪声污染:                                                 HMF之后:

 

从前面的图片可以看到, 图片从高锐度经由混合中值滤波之后, 锐度下降了. 但是在去噪声的环节上, 这个所谓的混合中值滤波器表现的甚至不如中值滤波来的效果好, 当然因为这里的噪声是随机的, 可能有些因素在其中. 但从原理上分析.这种滤波器确实存在不能过滤掉的可能, 原因在什么地方呢? 且看我们这个大图中的一个小局部:

把图放大之后看的很清楚了, 在红色的圆圈圈出来的地方, 不出意外的发现了水平连续的3个白色像素. 再联想到我们之前经过HMF之后的效果图就不难得出结论, 就是者连续的3个点造成了最后的这个白色像素. 怎么说呢? 很简单, 在HMF的第一步过程中, 因为我们选择的点是5各点, 上下左右中, 那么经过计算, 就是白色无疑了, 不管第二步得到什么像素, 在第三步的时候, 让2个白色取中值, 肯定得到的白色像素了! 仔细想来确实是这个么道理.  但是median filter的时候因为要整体考虑9个像素的中值, 这里很有可能就不是白色了! 所以这个混合中值滤波器会在保持线条的边缘上有独到之处(因为比较容易保持联系像素的颜色不受伤害), 而消除噪声还是median filter表现更胜一筹.老规矩, 最后再贴一下处理的函数代码:

unsigned char median(unsigned char* elements, int width)  
{  
    //   Order elements (only half of them)  
    for (int i = 0; i < (width >> 1) + 1; ++i)  
    {  
        //   Find position of minimum element  
        int min = i;  
        for (int j = i + 1; j < width; ++j)  
            if (elements[j] < elements[min])  
                min = j;  
        //   Put found minimum element in its place  
        unsigned char temp = elements[i];  
        elements[i] = elements[min];  
        elements[min] = temp;  
    }  
    //   Get result - the middle element  
    return elements[width >> 1];  
}  
/** 
** method to remove noise from the corrupted image by hybrid median value 
* @param corrupted input grayscale binary array with corrupted info 
* @param smooth output data for smooth result, the memory need to be allocated outside of the function 
* @param width width of the input grayscale image 
* @param height height of the input grayscale image 
*/  
void hybridMedianFilter (unsigned char* corrupted, unsigned char* smooth, int width, int height)  
{  
    memcpy ( smooth, corrupted, width*height*sizeof(unsigned char) );  
    for (int j=1;j<height-1;j++)  
    {  
        for (int i=1;i<width-1;i++)  
        {  
            unsigned char window[5];  
            unsigned char results[3];  
            //   Pick up cross-window elements  
            window[0] = corrupted[(j - 1) * width + i];  
            window[1] = corrupted[j * width + i - 1];  
            window[2] = corrupted[j * width + i];  
            window[3] = corrupted[j * width + i + 1];  
            window[4] = corrupted[(j + 1) * width + i];  
            //   Get median  
            results[0] = median(window, 5);  
            //   Pick up x-window elements  
            window[0] = corrupted[(j - 1) * width + i - 1];  
            window[1] = corrupted[(j - 1) * width + i + 1];  
            window[2] = corrupted[j * width + i];  
            window[3] = corrupted[(j + 1) * width + i - 1];  
            window[4] = corrupted[(j + 1) * width + i + 1];  
            //   Get median  
            results[1] = median(window, 5);  
            //   Pick up leading element  
            results[2] = corrupted[j * width + i];  
            //   Get result  
            smooth[j*width+i] = median(results, 3);  
        }  
    }  
}

因为要取中值, 还附加了一个取中值的函数median



实验结果:

以下所有结果均为C++处理。
1,对原图像进行处理的结果
可以发现对边缘有就很好的保护能力:(左为原图)


2,对噪声图像进行处理的结果
注意这是matlab里面的椒盐噪声,最左边是噪声图像,中间是本文改进中值滤波(HMF)算法,最右边是自适应中值滤波。
去噪效果一般。椒盐是中值滤波系列最容易处理的噪声,可以见得该改进算法有它的不足之处,在椒盐噪声的处理效果上明显不如经典的自适应中值滤波!



中值滤波对噪声图像的效果,存在明显的不足(模糊效果):





参考资源:

【1】百度百科,http://baike.baidu.com/link?url=mQ37rozxl6jHqzdVY56zOO_tpSvInR0EA8AGK5W242ljIXOxPz_xtTT8Bgi8fOVocmp6XpiI1FK6byALfUir1a

【2】hhygcy,http://blog.csdn.net/hhygcy/article/details/4325462


注:本文是在原作者hhygcy基础上二次加工重写,代码有较大改动

2019-03-24 11:55:46 weixin_40647819 阅读数 2406

写在前面

从均值滤波开始,着手实现各种常用的滤波算法。均值滤波是一种线性滤波。图像的空域滤波无非分为两种,线性滤波和非线性滤波。由于我之前对线性、非线性理解不够清晰,这次就好好总结一下吧。

线性滤波:对邻域中的像素的计算为线性运算时,如利用窗口函数进行平滑加权求和的运算,或者某种卷积运算,都可以称为线性滤波。常见的线性滤波有:均值滤波、高斯滤波、盒子滤波、拉普拉斯滤波等等,通常线性滤波器之间只是模版系数不同。

非线性滤波:非线性滤波利用原始图像跟模版之间的一种逻辑关系得到结果,如最值滤波器,中值滤波器。比较常用的有中值滤波器和双边滤波器。

均值滤波的应用场合:

根据冈萨雷斯书中的描述,均值模糊可以模糊图像以便得到感兴趣物体的粗略描述,也就是说,去除图像中的不相关细节,其中“不相关”是指与滤波器模板尺寸相比较小的像素区域,从而对图像有一个整体的认知。即为了对感兴趣的物体得到一个大致的整体的描述而模糊一幅图像,忽略细小的细节。

均值滤波的缺陷:

均值滤波本身存在着固有的缺陷,即它不能很好地保护图像细节,在图像去噪的同时也破坏了图像的细节部分,从而使图像变得模糊,不能很好地去除噪声点。特别是椒盐噪声。

 

原理

均值滤波的原理很简单,一般是用下面的系数模版与图像做卷积运算,公式表示为:

                                                         

g(x,y)为该邻域的中心像素,n跟系数模版大小有关,一般3*3邻域的模板,n取为9,如:

 

当然,模板是可变的,一般取奇数,如5*5,7*7等等。

注:在实际处理过程中可对图像边界进行扩充,扩充为0或扩充为邻近的像素值。

       

结果

                           

                                              原图                                                                                7*7模板

分析:如果采用常规方法,随着模板尺寸的增大,求和计算量也急剧增大,而且模板是逐像素移动的,可见这种效率上的代价是巨大的,下一篇将介绍一种采用积分图处理的快速均值滤波方法,会大大提高执行效率,可以说是工程应用的首选!

下图是常规均值滤波处理一张分辨率为485*528图像的时间(模板15*15):

下图是积分图快速均值滤波处理的时间(模板15*15):

速度提升10倍,爽到爆有木有!

代码(常规方法)

#include <opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

void MeanFilater(cv::Mat& src,cv::Mat& dst,cv::Size wsize){
	//图像边界扩充:窗口的半径
	if (wsize.height % 2 == 0 || wsize.width % 2 == 0){
		fprintf(stderr,"Please enter odd size!" );
		exit(-1);
	}
	int hh = (wsize.height - 1) / 2;
	int hw = (wsize.width - 1) / 2;
	cv::Mat Newsrc;
	cv::copyMakeBorder(src, Newsrc, hh, hh, hw, hw, cv::BORDER_REFLECT_101);//以边缘为轴,对称
	dst=cv::Mat::zeros(src.size(),src.type());

    //均值滤波
	int sum = 0;
	int mean = 0;
	for (int i = hh; i < src.rows + hh; ++i){
		for (int j = hw; j < src.cols + hw;++j){

			for (int r = i - hh; r <= i + hh; ++r){
				for (int c = j - hw; c <= j + hw;++c){
					sum = Newsrc.at<uchar>(r, c) + sum;
				}
			}
			mean = sum / (wsize.area());
			dst.at<uchar>(i-hh,j-hw)=mean;
			sum = 0;
			mean = 0;
		}
	}

}

int main(){
	cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\Fig0334(a)(hubble-original).tif");
	if (src.empty()){
		return -1;
	}
	if (src.channels() > 1)
		cv::cvtColor(src,src,CV_RGB2GRAY);

	cv::Mat dst;
	cv::Mat dst1;
	cv::Size wsize(7,7);

	double t2 = (double)cv::getTickCount();
	MeanFilater(src, dst, wsize); //均值滤波
	t2 = (double)cv::getTickCount() - t2;
	double time2 = (t2 *1000.) / ((double)cv::getTickFrequency());
	std::cout << "FASTmy_process=" << time2 << " ms. " << std::endl << std::endl;

	cv::namedWindow("src");
	cv::imshow("src", src);
	cv::namedWindow("dst");
	cv::imshow("dst", dst);
	cv::imwrite("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Image Filtering\\MeanFilter\\Mean_hubble.jpg",dst);
	cv::waitKey(0);
}

 

 

2017-09-23 10:15:05 a8039974 阅读数 12372

均值滤波介绍

      滤波是滤波是将信号中特定波段频率滤除的操作,是从含有干扰的接收信号中提取有用信号的一种技术。

     均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素(如3×3模板:以目标象素为中心的周围8个象素,构成一个滤波模板,即去掉目标象素本身),再用模板中的全体像素的平均值来代替原来像素值。

      均值滤波效果:平滑线性滤波处理降低了图像的“尖锐”变化。由于典型的随机噪声由灰度级的急剧变化组成,因此常见的平滑处理的应用就是降低噪声。均值滤波器的主要应用是去除图像中的不相关细节,其中“不相关”是指与滤波器模板尺寸相比较小的像素区域。然而,由于图像的边缘也是由图像灰度的尖锐变化带来的特性,所以均值滤波处理还是存在着边缘模糊的负面效应。

均值滤波算法实现(C语言) 

复制代码
  1 // junzhilvbo.cpp : 定义控制台应用程序的入口点。
  2 //
  3 
  4 #include "stdafx.h"
  5 #include "stdlib.h"
  6 #include "string.h"
  7 
  8 #define DATA_X 256      //数字图像水平像素个数
  9 #define DATA_Y 256      //数字图像竖直像素个数
 10 
 11 void OpenFile(const char *cFilePath , int nOriginalData[DATA_Y][DATA_X])
 12 {
 13     printf("正在获取数据......\n");
 14     FILE *fp ;
 15     fp = fopen(cFilePath , "r");
 16     if(NULL == fp)
 17     {
 18         printf("open file failed! \n");
 19         return ;
 20     }
 21 
 22     unsigned char *pData = (unsigned char *)malloc(sizeof(unsigned char)*DATA_X*DATA_Y);
 23     if(NULL == pData)
 24     {
 25         printf("memory malloc failed!\n");
 26         return ;
 27     }
 28 
 29     fread(pData , sizeof(unsigned char)*DATA_X*DATA_Y , 1 , fp);
 30 
 31     int count_x = 0 ;
 32     int count_y = 0 ;
 33 
 34     for(;count_y < DATA_Y ; count_y++)
 35     {
 36         for(; count_x < DATA_X ;count_x++)
 37         {
 38             nOriginalData[count_y][count_x] = pData[count_y*DATA_Y+count_x];
 39         }
 40     }
 41 
 42     free(pData);
 43     fclose(fp);    
 44 
 45     return ;
 46 }
 47 
 48 void SaveFile(const char *cFilePath , int nResultData[DATA_Y][DATA_X])
 49 {
 50     printf("正在保存数据......\n");
 51     int count_x,count_y;
 52 
 53     FILE *fp ;
 54     fp = fopen(cFilePath , "w");
 55     if(NULL == fp)
 56     {
 57         printf("open file failed! \n");
 58         return ;
 59     }
 60 
 61     for(count_y=0;count_y<DATA_Y;count_y++) 
 62     {
 63         for(count_x=0;count_x<DATA_X;count_x++)          
 64         {
 65             fwrite(&nResultData[count_y][count_x],1,1,fp);
 66         }
 67     }
 68         
 69     fclose(fp);    
 70     printf("文件保存成功! \n");
 71 
 72     return ;
 73 }
 74 
 75 bool JunZhiLvBo(const int nOriginalData[DATA_Y][DATA_X], int nResultData[DATA_Y][DATA_X])
 76 {
 77     printf("正在进行均值滤波......\n");
 78     int count_x ,count_y ;
 79     
 80     /*3*3模版滤波计算,不计算边缘像素*/
 81     for(count_y = 1 ; count_y < DATA_Y ; count_y++)
 82     {
 83         for(count_x = 1 ; count_x < DATA_X ;count_x++)
 84         {
 85             nResultData[count_y][count_x] = (int)((nOriginalData[count_y-1][count_x-1]+
 86                                                    nOriginalData[count_y-1][count_x]  +
 87                                                    nOriginalData[count_y-1][count_x+1]+
 88                                                    nOriginalData[count_y][count_x-1]  +
 89                                                    nOriginalData[count_y][count_x]    +
 90                                                    nOriginalData[count_y][count_x+1]  +
 91                                                    nOriginalData[count_y+1][count_x-1]+
 92                                                    nOriginalData[count_y+1][count_x]  +
 93                                                    nOriginalData[count_y+1][count_x+1])/9); 
 94         }
 95     }
 96 
 97     /*对四个边缘直接进行赋值处理*/
 98     for(count_x=0;count_x<DATA_X;count_x++)                                        //水平边缘像素等于原来像素灰度值
 99     {
100         nResultData[0][count_x]=nOriginalData[0][count_x];
101         nResultData[DATA_Y-1][count_x]=nOriginalData[DATA_Y-1][count_x];
102     }
103     for(count_y=1;count_y<DATA_Y-1;count_y++)                                     //竖直边缘像素等于原来像素灰度值
104     {
105         nResultData[count_y][0]=nOriginalData[count_y][0];
106         nResultData[count_y][DATA_X-1]=nOriginalData[count_y][DATA_X-1];
107     }
108 
109     return true ;
110 }
111 
112 int _tmain(int argc, _TCHAR* argv[])
113 {
114     int nOriginalData[DATA_Y][DATA_X]; //保存原始图像灰度值
115     int nResultData[DATA_Y][DATA_X];   //保存滤波后的灰度值
116 
117     memset(nOriginalData,0,sizeof(nOriginalData));  //初始化数组
118     memset(nResultData,0,sizeof(nResultData));                                     
119 
120     char cOpenFilePath[] = "Lena.raw";                                           //图像文件路径
121 
122     OpenFile(cOpenFilePath,nOriginalData);                          
123     
124     if(!JunZhiLvBo(nOriginalData,nResultData))                                   //滤波计算
125     {
126         printf("操作失败!\n");
127         return 0;
128     }
129 
130     char cSaveFilePath[] = "Result.raw";                                        //文件保存路径
131 
132     SaveFile(cSaveFilePath,nResultData); 
133 
134     return 0;
135 }
复制代码

均值滤波算法效果对比

均值滤波之前:                                均值滤波之后:

 

2019-07-17 00:37:56 huqiang_823 阅读数 1545

1、算法原理

在数字图像处理中,滤波是一个很重要的操作,许多算法其本质都是滤波操作。使用白话的形式对滤波定义:对于一个像素点,使用其领域像素(可以包含自身,也可不包含)的相关特性,计算出一个值,代替当前像素值。举个例子,3X3均值滤波,就是计算每个像素对应的3X3领域所有像素值的平均值,代替当前像素。滤波操作可分为线性滤波和非线性滤波两大种类,其中,常见的线性滤波操作有:均值滤波、高斯滤波、方差滤波(局部方差)等等,常见的非线性滤波操作有:中值滤波、最大值滤波、最小值滤波等等。

2、算法实现

(1)、边界处理

一个完善的滤波算法,边界处理是必要的。对于图像的边界像素,其领域是不存在的,比如坐标为(0,0)(y,x形式)的像素点,其3X3的领域坐标集合为

其中,所有坐标包含-1的像素点都是不存在的,这个时候滤波操作就需要对这些不存在的点进行处理。有几种边界处理方式:固定值填充法、镜像法、重叠法。所谓的固定值填充法即为:所有不存在的像素点的像素值认为是某一个固定的值(一般由算法调用者去设定,或者默认为0);镜像法:以边界像素为对称轴,使用对称像素点的像素值代替;重叠法:使用边界像素值代替。示意图如下:

固定值填充法(0)
边界重叠法
镜像法

一般来说,如无特殊的应用场景,推荐大家使用边界重叠法进行边界处理。C++代码实现边界重叠法代码如下:

//函数名:makeRepeatBorder
//作用:边界填充(边界像素重叠法)
//参数:
//matInput:输入图像
//matOutput : 输出图像
//cnBorder : 边界尺寸
//返回值:无
//注:支持单通道8位灰度图像
void makeRepeatBorder(cv::Mat& matInput, cv::Mat& matOutput, const int& cnBorder)
{
	//构造输出图像
	matOutput = cv::Mat::zeros(matInput.rows + cnBorder * 2, matInput.cols + cnBorder * 2, matInput.type());

	unsigned char *pSrc = NULL, *pDst = NULL;

	//拷贝原始图像数据
	for (int m = 0; m < matInput.rows; m++)
	{
		//源地址
		pSrc = matInput.data + m * matInput.step[0];
		//目的地址
		pDst = matOutput.data + (m + cnBorder) * matOutput.step[0] + cnBorder;
		memcpy(pDst, pSrc, matInput.step[0]);
	}
	//边界处理
	for (int m = 0; m < cnBorder; m++)
	{
		//顶部
		pSrc = matOutput.data + cnBorder * matOutput.step[0];
		pDst = matOutput.data + m * matOutput.step[0];
		memcpy(pDst, pSrc, matOutput.step[0]);
		//底部
		pSrc = matOutput.data + (cnBorder + matInput.rows - 1) * matOutput.step[0];
		pDst = matOutput.data + (cnBorder + matInput.rows + m) * matOutput.step[0];
		memcpy(pDst, pSrc, matOutput.step[0]);
	}

	for (int m = 0; m < matOutput.rows; m++)
		for (int n = 0; n < cnBorder; n++)
		{
			//左
			matOutput.at<unsigned char>(m, n) = matOutput.at<unsigned char>(m, cnBorder);
			//右
			matOutput.at<unsigned char>(m, n + matInput.cols + cnBorder) = matOutput.at<unsigned char>(m, matOutput.cols - cnBorder - 1);
		}
}

效果如下:

原始图像
边界填充20像素(边界像素重叠法)

其余两种方法,大家可以自行编码实现。

(2)、均值滤波

经过边界处理后,图像尺寸增大了。假如进行5*5的均值滤波,图像的尺寸增大了4,那么,在边界处理后的图像进行滤波操作的时候,滤波的起始、停止下标同样需要进行改变。一般我们滤波的窗口尺寸设为奇数,主要是考虑到窗口存在滤波中心(锚点),便于计算(个人理解,未经证实)。C++实现代码如下:

//函数名:meanFilter
//作用:均值滤波实现
//参数:
//matInput:输入图像
//matOutput : 输出图像
//cnWinSize : 滤波窗口尺寸
//返回值:无
//注:支持单通道8位灰度图像
void meanFilter(cv::Mat& matInput, cv::Mat& matOutput, const int& cnWinSize)
{
	//滤波窗口为奇数
	assert(cnWinSize % 2 == 1);

	//构造输出图像
	matOutput = cv::Mat::zeros(matInput.rows, matInput.cols, matInput.type());

	int nAnchor = cnWinSize / 2;
	//边界处理
	cv::Mat matMakeBorder;
	makeRepeatBorder(matInput, matMakeBorder, nAnchor);

	//使用行首地址存储法遍历滤波
	unsigned char ** pRow = new unsigned char*[matMakeBorder.rows];
	for (int r = 0; r < matMakeBorder.rows; r++)
		pRow[r] = matMakeBorder.data + r * matMakeBorder.step[0];
	
	//乘法代替除法
	float fNormal = 1.0f / (cnWinSize * cnWinSize);

	//滤波操作
	for (int r = 0; r < matMakeBorder.rows - cnWinSize + 1; r++)
	{
		unsigned char* pOut = matOutput.data + r * matOutput.step[0];
		for (int c = 0; c < matMakeBorder.cols - cnWinSize + 1; c++)
		{
			//求和
			float fSum = 0;
			for (int m = 0; m < cnWinSize; m++)
			for (int n = 0; n < cnWinSize; n++)
			{
				fSum += pRow[m + r][n + c];
			}

			//求均值输出
			pOut[c] = (unsigned char)(fSum * fNormal);
		}
	}
	delete[] pRow;
}

滤波结果下:

滤波尺寸3*3
滤波尺寸7*7

3、均值滤波的加速

经过上面的编码实现,我们可以粗略的计算下,该种方式实现均值滤波的算法复杂度。假设滤波图像尺寸为W*H,滤波窗口为M*N,不考虑边界处理的情况下,内存访问的次数为W*H*M*N。也就是说,算法的耗时与滤波窗口的尺寸相关。算法耗时情况如下表:

无加速均值滤波耗时(640*480灰度图像)
滤波尺寸 3 5 7 9 11
耗时(ms) 2.431 5.296 9.949 17.06 27.37

那么,是否有更加优雅的方式实现呢?我这里提供一种基于积分图加速办法(查看OpenCV源码可知,cpu处理下,还存在行列滤波加速、频域加速等的方法):

(1)、积分图原理

积分图的首次出现在基于HAAR特征的人脸检测的框架中,原始论文为《Rapid object detection using a boosted cascade of simple features》。Viola 提出了一种利用积分图(integral image)快速计算 Haar 特征的方法, 这个方法使得图像的局部矩形求和运算的复杂度从 O(mn) 下降到了 O(4) 。图像是由一系列的离散像素点组成,因此图像的积分其实就是求和。 图像积分图中每个点的值是原图像中该点左上角的所有像素值之和。示意图如下:

积分图示意

 

在积分图像中,I(x,y)存储的是SAT(x,y)矩形区域对应原始图像的像素和值。那么,在给定积分图后,计算任意矩形区域(x,y,w,h)的像素的和值的公式为:

也就是说,得到积分图后,仅仅需要四个点就可以计算任意矩形区域的像素值之和。

(2)、积分图快速实现

积分图的C++实现有许多种方法,网络上也有很多博主写出了。我查看不少的博文,我向大家推荐这位博主的原理讲解https://blog.csdn.net/xiaowei_cqu/article/details/17928733。其实,大家去查看OpenCV的积分图实现源码就可以发现,积分图的尺寸是比原始图像的尺寸大1的。

这是为什么呢?某个点的积分图反应的是原图中此位置左上角所有像素之和,这里是的累加和是不包括这个点像素本身的,那么这样,对于原图的第一行和第一列的所有像素,其对应位置的积分图就应该是0,这样考虑到所有的像素,为了能容纳最后一列和最后一行的情况,最终的积分图就应该是 (W + 1) X (H + 1)大小。C++实现代码如下:

//函数名:integral
//作用:快速积分图计算实现
//参数:
//matInput:输入图像
//matOutput : 输出积分图(32位整型数据)
//返回值:无
//注:支持单通道8位灰度图像
void integral(cv::Mat& matInput, cv::Mat& matOutput)
{
	//构造积分图
	matOutput = cv::Mat::zeros(matInput.rows + 1, matInput.cols + 1, CV_32SC1);
	for (int r = 0; r < matInput.rows; r++)
	{
		int nLineACC = 0;
		for (int c = 0; c < matInput.cols; c++)
		{
			nLineACC += matInput.at<unsigned char>(r, c);
			matOutput.at<int>(r + 1, c + 1) = matOutput.at<int>(r, c + 1) + nLineACC;
		}
	}
}

(3)、基于积分图的快速均值滤波

//函数名:fastMeanFilter
//作用:基于积分图的快速均值滤波实现
//参数:
//matInput:输入图像
//matOutput : 输出图像
//cnWinSize : 滤波窗口尺寸
//返回值:无
//注:支持单通道8位灰度图像
void fastMeanFilter(cv::Mat& matInput, cv::Mat& matOutput, const int& cnWinSize)
{
	//滤波窗口为奇数
	assert(cnWinSize % 2 == 1);

	//构造输出图像
	matOutput = cv::Mat::zeros(matInput.rows, matInput.cols, matInput.type());

	int nAnchor = cnWinSize / 2;
	//边界处理
	cv::Mat matMakeBorder;
	makeRepeatBorder(matInput, matMakeBorder, nAnchor);

	//计算积分图
	cv::Mat matIntegral;
	integral(matMakeBorder, matIntegral);

	//使用行首地址存储法遍历滤波
	int ** pRow = new int*[matIntegral.rows];
	for (int r = 0; r < matIntegral.rows; r++)
		pRow[r] = (int*)(matIntegral.data + r * matIntegral.step[0]);


	float fNormal = 1.0f / (cnWinSize * cnWinSize);
	//滤波操作
	for (int r = 0; r < matMakeBorder.rows - cnWinSize + 1; r++)
	{
		unsigned char* pOut = matOutput.data + r * matOutput.step[0];
		for (int c = 0; c < matMakeBorder.cols - cnWinSize + 1; c++)
		{
			//求和
			float fSum = pRow[r][c] + pRow[r + cnWinSize][c + cnWinSize] - \
				pRow[r + cnWinSize][c] - pRow[r][c + cnWinSize];
			//求均值输出
			pOut[c] = (unsigned char)(fSum * fNormal);
		}
	}

	delete[] pRow;
}

经过积分图加速后,均值滤波算法耗时如下:

基于积分图加速的均值滤波耗时
滤波尺寸 3 5 7 9 11
耗时(ms) 2.246 2.456 2.658 2.954 3.155

4、总结

(1)、均值滤波优势在于计算量小、有多种加速的方法,对于随机噪声、点噪声有一定的抑制作用;缺陷在于,算法没有保护图像细节,滤波的同时也把图像边缘细节进行了平滑。

(2)、使用积分图进行均值滤波加速,能够使算法耗时基本与滤波窗口尺寸无关。

(3)、博文中的算法绝对耗时的值是与计算机配置、编译器等相关的,但是,耗时的趋势基本是可信的。

技术交流合作QQ:3355138068