高效中值濾波的方法及實現

中值濾波的原理很簡單:就是用滑動窗口中灰度中值代替窗口中心像素的灰度值


高效中值濾波:

wKioL1SO02-TdjaBAAJjgZ-7R9s798.jpg

wKiom1SO0tGz5nYnAADPax42eHc203.jpg

wKioL1SO02_CapQNAAI5AChdMq8725.jpg

代碼實現:
//中值濾波
//窗口大小爲width_Aperture*width_Aperture的正方形
void MedianBlur(const Mat &image_Src, Mat &image_Dst, int width_Aperture)
{
	/////////////重新分配圖像(如果需要)/////////////////////
	int width_Dst=image_Src.cols;
	int height_Dst=image_Src.rows;
	image_Dst.create(Size(width_Dst,height_Dst),CV_8UC1);//如果重新分配,之前的空間會扔掉
	image_Dst.setTo(Scalar(0));//置爲0

	//滑動窗口
	int pixelCount=width_Aperture*width_Aperture;//窗口內像素總個數
	Mat image_Aperture(width_Aperture,width_Aperture,CV_8UC1);//滑動窗口圖像

	//直方圖
	Mat histogram;
	int histogramSize=256;//灰度等級
	int thresholdValue=pixelCount/2+1;//step1.設置閾值(步驟參考:圖像的高效編程要點之四)

	//計算起點座標
	int startX=width_Aperture/2;
	int startY=width_Aperture/2;

	//第一行
	//這裏需要設置3個指針:這三個指針綁定在一起,一起滑動
	//1.源圖像中被處理的像素 
	//2.目標圖像被處理的像素 
	//3.源圖像滑動窗口
	uchar *row_Src=image_Src.data+startY*width_Dst+startX;//源圖像
	uchar *row_Dst=image_Dst.data+startY*width_Dst+startX;//目標圖像
	uchar *row_Aperture_Src=image_Src.data;//源圖像中的滑動窗口
	
	for (int y=startY;y<=height_Dst-startY-1;++y)
	{
		//列
		uchar *col_Src=row_Src;
		uchar *col_Dst=row_Dst;
		uchar *col_Aperture_Src=row_Aperture_Src;//操作整個滑動窗口
		
		///////////////對滑動窗口操作//////////////////
		//計算每行第一個滑動窗口直方圖
		//提取滑動窗口圖像
		uchar *row_Aperture=image_Aperture.data;
		uchar *row_Aperture_Src_2=col_Aperture_Src;
		for (int k=0;k<=width_Aperture-1;++k)
		{
			//列
			uchar *col_ApertureImage=row_Aperture;
			uchar *col_Aperture_Src_2=row_Aperture_Src_2;
			
			for (int w=0;w<=width_Aperture-1;++w)
			{
				//處理每個像素
				col_ApertureImage[0]=col_Aperture_Src_2[0];
				
				//下一個像素
				col_ApertureImage++;
				col_Aperture_Src_2++;
			}

			//下一行
			row_Aperture+=width_Aperture;
			row_Aperture_Src_2+=width_Dst;
		}

		//step 2.確定中值,並記錄亮度<=中值的像素點個數
		//求直方圖
		calcHist(&image_Aperture, 
			1,//Mat的個數
			0,//用來計算直方圖的通道索引,通道索引依次排開
			Mat(),//Mat()返回一個空值,表示不用mask,
			histogram, //直方圖
			1, //直方圖的維數,如果計算2個直方圖,就爲2
			&histogramSize, //直方圖的等級數(如灰度等級),也就是每列的行數
			0//分量的變化範圍
			);
		
		//求亮度中值和<=中值的像素點個數
		int medianValue,pixleCountLowerMedian;
		CalculateImage_MedianGray_PixelCount(histogram,pixelCount,medianValue,pixleCountLowerMedian);
		////////////滑動窗口操作結束///////////////////////
		
		//濾波
		col_Dst[0]=(uchar)medianValue;
		
		//滑動一個像素(三個指針在一起移動)
		col_Dst++;
		col_Src++;
		col_Aperture_Src++;
		for (int x=startX+1;x<=width_Dst-startX-1;++x)//從每行第二個濾波像素開始
		{
			////////////維持滑動窗口直方圖//////////////
			//step 3.去掉左側
			uchar *col_Left=col_Aperture_Src-1;
			float *data=(float*)histogram.data;
			for (int k=0;k<=width_Aperture-1;++k)
			{
				int gray=col_Left[0];
				data[gray]-=1.0;
				if (gray<=medianValue)
				{
					pixleCountLowerMedian--;
				}
				col_Left+=width_Dst;
			}
			
			//step 4.增加右側
			uchar *col_Right=col_Aperture_Src+width_Aperture-1;
			for (int k=0;k<=width_Aperture-1;++k)
			{
				int gray=col_Right[0];
				data[gray]+=1.0;
				if (gray<=medianValue)
				{
					pixleCountLowerMedian++;
				}
				col_Right+=width_Dst;
			}

			//搜索新的中值
			if (pixleCountLowerMedian>thresholdValue)//step 6.
			{
				while(1)
				{
					pixleCountLowerMedian-=data[medianValue];
					medianValue--;
					if (pixleCountLowerMedian<=thresholdValue)
					{
						break;
					}
				}
				
			}
			else
			{
				while(pixleCountLowerMedian<thresholdValue)//step 5
				{
					medianValue++;
					pixleCountLowerMedian+=data[medianValue];

				}

			}

			//濾波
			col_Dst[0]=(uchar)medianValue;

			//滑動一個像素
			col_Src++;
			col_Dst++;
			col_Aperture_Src++;
		}//end of x

		//下一行
		row_Src+=width_Dst;
		row_Dst+=width_Dst;
		row_Aperture_Src+=width_Dst;
	}//end of y

}
//計算亮度中值和灰度<=中值的像素點個數
void CalculateImage_MedianGray_PixelCount(const Mat &histogram,int pixelCount,int &medianValue,int &pixleCountLowerMedian)
{
	float *data=(float *)histogram.data;//直方圖
	int sum=0;
	for (int i=0;i<=255;++i)
	{
		//
		sum+=data[i];
		if (2*sum>pixelCount)
		{
			medianValue=i;
			pixleCountLowerMedian=sum;
			break;
		}
	}
}

使用窗口大小爲3*3的窗口,運行效果圖:

wKiom1SO04mhmep2AAKFs-KPMaM505.jpg

wKioL1SO1CiDmbp6AAHtVVKzbvM013.jpg

運行這段代碼之前,需要配置一下OpenCV,算法核心和OpenCV沒有太大關聯。

注意:算法沒有處理邊界的情況,還不太清楚怎麼處理邊界,有會的朋友,希望能夠一起分享一下邊界處理的一些技巧

代碼寫的不是特別規範,大家有什麼看不懂的地方,可以一起討論討論


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章