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写感觉格式上也好看了很多,特别是公式方面。

猜你喜欢

转载自blog.csdn.net/wzmsltw/article/details/52927787