1. 程式人生 > >opencv學習筆記(三):幾種去噪濾波器的實現

opencv學習筆記(三):幾種去噪濾波器的實現

現在在上數字影象處理的課程,最近的一次作業要求不用OpenCV自帶的濾波器函式來實現幾種濾波器,以實現對加入椒鹽噪聲的影象的去噪。也是對markdown編輯器的一次練習。

椒鹽噪聲

椒鹽噪聲是一種很簡單的噪聲,即隨機將影象中一定數量的畫素點設定為0(黑)或255(白)。由於看起來好像在影象上撒了椒鹽一樣,故被稱為椒鹽噪聲。
下面是椒鹽噪聲的處理程式碼(假定輸入影象為3通道)

void salt(Mat &image, float salt_ratio){
    int n=image.rows*image.cols*salt_ratio;
    for (int k = 0
; k < n; k++) { int i = rand() % image.cols; //cols 和 rows 給出影象的寬與高 int j = rand() % image.rows; int type= rand() %2; if (type==1){ image.at<Vec3b>(j, i)[0] = 255; image.at<Vec3b>(j, i)[1] = 255; image.at<Vec3b>(j, i)[2
] = 255; } else{ image.at<Vec3b>(j, i)[0] = 0; image.at<Vec3b>(j, i)[1] = 0; image.at<Vec3b>(j, i)[2] = 0; } } }

以經典的lena圖為例,加入10%的椒鹽噪聲後:



濾波器原理

首先介紹一些通用的設定:令Sxy表示中心在(x,y)的點,尺寸為m×n的矩形子影象視窗的座標集。m×n為濾波器模板的大小。f^(x,y)

為濾波器得到的結果,賦值給(x,y)處的畫素。g(s,t)(s,t)位置的畫素值。

算術均值濾波器

f^(x,y)=1mn(s,t)Sxyg(s,t)

幾何均值濾波器

f^(x,y)=(s,t)Sxyg(s,t)1mn

諧波濾波器

f^(x,y)=mn(s,t)Sxy1g(s,t)

中值濾波器

用相鄰區域畫素灰度的中值代替該點的畫素值

f^(x,y)=median(s,t)Sxy(g(s,t))

程式設計實現

       根據以上公式,實現了四種模板大小可變的濾波器。
     主要程式設計思想為:構建一個ImageRecovery類,其中filter函式為公用的濾波器介面,在filter函式中對result(Mat格式3通道圖片)進行遍歷,然後根據選擇的濾波器型別呼叫不同的濾波器模板,進行濾波運算。濾波器模板函式則作為類的private函式。
      此外,在諧波濾波和幾何濾波中,若模板中有一個畫素的取值為0,則不將這個點引入計算。若引入計算則會導致錯誤或是0畫素點相鄰的一片區域完全變黑。相當於增加了影象的噪聲。程式碼如下:

#include "stdafx.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

class ImageRecovery{
private:
    double filter_aver(Mat src)
    {
        //算術均值濾波
        double sum=0;
        for (int i =0;i < src.rows;i++ ){
            uchar* data=src.ptr<uchar>(i);
            for (int j =0;j <src.cols;j++){
                sum+=double(data[j]);
            }
        }
        return sum/double(src.cols* src.rows);
    }
    double filter_geo(Mat src)
    {
        //幾何均值濾波
        double geo=1;
        for (int i =0;i < src.rows;i++ ){
            uchar* data=src.ptr<uchar>(i);
            for (int j =0;j <src.cols;j++){
                if (data[j]!=0) geo*=data[j];
            }
        }
        double power=1.0/double(src.cols*src.rows);
        return pow(geo,power);
    }
    double filter_har(Mat src)
    {
        //諧波濾波
        double har=0;
        for (int i =0;i < src.rows;i++ ){
            uchar* data=src.ptr<uchar>(i);
            for (int j =0;j <src.cols;j++){
                if (data[j]!=0) har+=1/(double)(data[j]);
            }
        }
        return (src.cols*src.rows)/har;
    }
    void BubbleSort(float* pData, int count)  
    {  
        //氣泡排序,用於中值濾波
        float tData;
        for (int i = 1; i < count; i++){  
            for (int j = count - 1; j >= i; j--){  
                if (pData[j] < pData[j - 1]){  
                    tData = pData[j - 1];  
                    pData[j - 1] = pData[j];  
                    pData[j] = tData;  
                }  
            }  
        }  
    }  
    double filter_median(Mat src)
    {
        //中值濾波
        int index=0;
        int bubble_len=(src.cols)*(src.rows);
        float* bubble=new float[bubble_len];
        for (int i =0;i < src.rows;i++ ){
            uchar* data=src.ptr<uchar>(i);
            for (int j =0;j <src.cols;j++){
                bubble[index] = data[j];
                index ++;
            }
        }
        BubbleSort(bubble,bubble_len);
        double median=bubble[bubble_len/2];
        return median;
    }
public:
    void salt(Mat &image, float salt_ratio )
    {
        //salt_ratio為加入椒鹽噪聲的比例
        int n=image.rows*image.cols*salt_ratio;
        for (int k = 0; k < n; k++)
        {
            int i = rand() % image.cols;  
            int j = rand() % image.rows;
            int type= rand() %2;
            if (type==1){
                image.at<Vec3b>(j, i)[0] = 255;
                image.at<Vec3b>(j, i)[1] = 255;
                image.at<Vec3b>(j, i)[2] = 255;
            }
            else{
                image.at<Vec3b>(j, i)[0] = 0;
                image.at<Vec3b>(j, i)[1] = 0;
                image.at<Vec3b>(j, i)[2] = 0;
            }
        }
    }
    Mat filter(Mat image,string filter_type,Size size)
    {
        //image為輸入待濾波影象,filter_tpye為濾波器型別,size為濾波器的尺寸
        Mat result;
        image.copyTo(result);
        Mat channel[3];
        split(image,channel);
        int l =(size.height-1)/2;
        int w =(size.width-1)/2;    
        for (int i = l;i < result.rows-l;i ++){
            for (int j =w;j < result.cols-w;j ++){
                for (int ii =0;ii < 3;ii++){
                    if (filter_type=="aver")    result.at<Vec3b>(i,j)[ii]=saturate_cast<uchar>(filter_aver(channel[ii](Rect(j-w,i-l,size.width,size.height))));
                    if (filter_type=="geo" )    result.at<Vec3b>(i,j)[ii]=saturate_cast<uchar>(filter_geo(channel[ii](Rect(j-w,i-l,size.width,size.height))));
                    if (filter_type=="har" )    result.at<Vec3b>(i,j)[ii]=saturate_cast<uchar>(filter_har(channel[ii](Rect(j-w,i-l,size.width,size.height))));
                    if (filter_type=="median" ) result.at<Vec3b>(i,j)[ii]=saturate_cast<uchar>(filter_median(channel[ii](Rect(j-w,i-l,size.width,size.height))));
                }
            }
        }
        return result;
    }
};



int main()
{
    Mat img=imread("lena.jpg");
    //初始化IR類
    ImageRecovery IR;
    //加入椒鹽噪聲
    IR.salt(img,0.1);
    imshow("salt",img);
    //對噪聲圖片進行濾波
    Mat result=IR.filter(img,"geo",Size(3,3));
    imshow("result",result);
    waitKey();
    return 0;
}

效果如圖所示



算術均值濾波(左:3*3;右:5*5)



幾何均值濾波(左:3*3;右:5*5)



諧波濾波(左:3*3;右:5*5)



中值濾波(左:3*3;右:5*5)

        總的來看,基於個人主觀的判斷,我認為對於椒鹽噪聲圖片的恢復,中值濾波的效果最好,諧波濾波的效果次之,均值濾波的效果再次,幾何濾波的效果最差。
        而對於濾波器的不同模板大小,從結果上可以看出選擇更大的模板可以使得結果影象中的噪聲更加平滑,但同時影象也變得模糊了。這是由於更大的濾波器模板使得影象整體變得平滑了。

        這次也是我第一次使用markdown寫部落格,線下使用wiznote的markdown模式進行編輯,圖片則儲存在七牛雲上。這樣線上下寫完的部落格可以直接複製到csdn上,省去了我之前每次上傳部落格還要改格式以及傳圖片的煩惱。此外,用markdown寫感覺格式上也好看了很多,特別是公式方面。