中值濾波的原理很簡單:就是用滑動窗口中灰度中值代替窗口中心像素的灰度值
高效中值濾波:
代碼實現: //中值濾波 //窗口大小爲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的窗口,運行效果圖:
運行這段代碼之前,需要配置一下OpenCV,算法核心和OpenCV沒有太大關聯。
注意:算法沒有處理邊界的情況,還不太清楚怎麼處理邊界,有會的朋友,希望能夠一起分享一下邊界處理的一些技巧
代碼寫的不是特別規範,大家有什麼看不懂的地方,可以一起討論討論