美文网首页
第 6 章 图像滤波

第 6 章 图像滤波

作者: sumpig | 来源:发表于2019-06-19 12:11 被阅读0次

    本章包括以下内容:

    • 用低通滤波器进行图像滤波;
    • 用滤波器进行缩减像素采样;
    • 用中值滤波器进行图像滤波;
    • 用定向滤波器检测边缘;
    • 计算图像的拉普拉斯算子。
    boldt.jpg

    6.2 低通滤波器

    本节将介绍几种非常基本的低通滤波器。这种滤波器的目的是减少图像变化的幅度。要做到这点,一个简单的方法是把每个像素的值替换成它周围像素的平均值。这样一来,强度的快速变化会被消除,取而代之的是更加平滑的过渡。

    cv::blur 函数将每个像素的值替换成该像素邻域的平均值(邻域是矩形的),从而使图像更加平滑。这个低通滤波器的用法如下所示:

    cv::blur(image,result, cv::Size(5,5)); // 滤波器尺寸
    

    这是使用滤波器后得到的结果。

    result.jpg

    有时需要让邻域内较近的像素具有更高的重要度。因此可计算加权平均值,即较近的像素比较远的像素具有更大的权重。要得到加权平均值,可采用依据高斯函数(即“钟形曲线”函数)制定的加权策略。函数cv::GaussianBlur 应用了这种滤波器,调用方法如下所示:

        cv::GaussianBlur(image, result,
            cv::Size(5, 5), // 滤波器尺寸
            1.5); // 控制高斯曲线形状的参数
    

    得到的结果如下所示。

    result.jpg

    观察本节产生的输出图像,可以发现低通滤波器的最终效果是使图像更加模糊或更加平滑。这不奇怪,因为低通滤波器减弱了高频成分,而高频成分正好对应了物体边缘处的快速视觉变化。

    对于高斯滤波器,像素对应的权重与它到中心像素之间的距离成正比。一维高斯函数的公式为:

    G(x)=Ae^{-x^2/2\sigma^2}

    使用归一化系数A 是为了确保高斯曲线下方的面积等于1。符号σ的值决定了高斯函数曲线的宽度。这个值越大,函数曲线就越扁平。

    例如计算一维高斯滤波器的系数,区间[-4, 0, 4],如果σ = 0.5,得到如下系数:

    [0.0 0.0 0.00026 0.10645 0.78657 0.10645 0.00026 0.0 0.0]
    

    计算这些系数的方法是用对应的σ 的值调用函数cv::getGaussianKernel:

    cv::Mat gauss= cv::getGaussianKernel(9, sigma,CV_32F);
    

    6.3 用滤波器进行缩减像素采样

    降低图像精度的过程称为缩减像素采样(downsampling),提升图像精度的过程称为提升像素采样(upsampling)。

    要缩小一幅图像,可以用低通滤波器实现。因此在删除部分列和行之前,必须先在原始图像上应用低通滤波器,这样才能使图像在缩小到四分之一后不出现伪影。这是用OpenCV 的实现方法:

        // 首先去除高频成分
        cv::GaussianBlur(image, image, cv::Size(11, 11), 2.0);
        // 只保留每4 个像素中的1 个
        cv::Mat reduced(image.rows / 4, image.cols / 4, CV_8U);
        for (int i = 0; i < reduced.rows; i++)
            for (int j = 0; j < reduced.cols; j++)
                reduced.at<uchar>(i, j) = image.at<uchar>(i * 4, j * 4);
    

    OpenCV 中有一个专用函数,利用这个原理实现了图像缩减,即cv::pyrDown 函数:

    cv::Mat reducedImage; // 用于存储缩小后的图像
    cv::pyrDown(image,reducedImage); // 图像尺寸缩小一半
    
    result.jpg

    上述函数使用了一个5×5 的高斯滤波器,在把图像缩小一半之前先进行低通滤波。此外还有功能相反的函数cv::pyrUp,它可以放大图像的尺寸。

    在这种提升像素采样的过程中,先在每两行和每两列之间分别插入值为0 的像素,然后对扩展后的图像应用同样的5×5 高斯滤波器(但系数要扩大4 倍)。先缩小一幅图像再把它放大,显然不能完全让它恢复到原始状态,因为缩小过程中丢失的信息是无法恢复的。

    此外还有一个更通用的函数cv::resize,它可以指定缩放后图像的尺寸。你只需要在调用它时指定新的尺寸,这个尺寸可以比原始图像小,也可以比原始图像大:

    cv::Mat resizedImage; // 用于存储缩放后的图像
    cv::resize(image, resizedImage, cv::Size(image.cols/4,image.rows/4)); // 行和列均缩小为原来的1/4
    

    你也可以指定缩放比例。在参数中提供一个空的图像实例,然后提供缩放比例:

    cv::resize(image, resizedImage, cv::Size(), 1.0/4.0, 1.0/4.0); // 缩小为原来的1/4
    

    像素插值

    进行插值的最基本方法是使用最近邻策略。把待生成图像的像素网格放在原图像的上方,每个新像素被赋予原图像中最邻近像素的值。当图像升采样(即新网格比原始网格更密集时)时,会根据同一个原始像素,确定新网格中多个像素的值。例如要把缩小后的图像放大4 倍,采用最邻近插值法的代码如下所示:

    cv::resize(reduced, newImage, cv::Size(), 3, 3, cv::INTER_NEAREST);
    

    双线性插值方案是cv::resize 函数的缺省方法(可以用标志cv::INTER_LINEAR 显式地指定):

    cv::resize(reduced, newImage, cv::Size(), 4, 4, cv::INTER_LINEAR);
    

    此外还有一些算法,可以得到更好的结果。如果想使用双三次插值算法,就要在执行插值运算时考虑4×4 的邻域像素。但因为这种算法使用了更多的像素(16 个),并且包含了三次函数的计算,所以它的运算速度比双线性插值算法慢。

    6.4 中值滤波器

    非线性滤波器在图像处理中也起着很重要的作用。本节将介绍的中值滤波器就是其中的一种。

    因为中值滤波器对消除椒盐噪声非常有用(这里用只有盐的噪声),所以我们将使用2.2 节创建的图像,如下所示。

    result.jpg

    调用中值滤波器函数的方法与调用其他滤波器差不多:

    cv::medianBlur(image, result, 5);
    // 最后一个参数是滤波器尺寸
    

    结果如下所示。

    result.jpg

    因为中值滤波器是非线性的,所以不能用核心矩阵表示,也不能进行卷积运算。但它也是通过操作一个像素的邻域,来确定输出的像素值的。正如其名,中值滤波器把当前像素和它的邻域组成一个集合,然后计算出这个集合的中间值,以此作为当前像素的值(集合中数值经过排序,中间位置的数值就是中间值)。当前像素被中间值代替。

    中值滤波器还有利于保留边缘的尖锐度,但它会洗去均质区域中的纹理(例如背景中的树木)。

    6.5 用定向滤波器检测边缘

    本节将执行一种反向的变换,即放大图像中的高频成分,再用本节介绍的高通滤波器进行边缘检测。

    我们将要使用的滤波器称为Sobel 滤波器。因为它只对垂直或水平方向的图像频率起作用,所以被认为是一种定向滤波器。水平方向滤波器的调用方法为:

    cv::Sobel(image, // 输入
        sobelX, // 输出
        CV_8U, // 图像类型
        1, 0, // 内核规格
        3, // 正方形内核的尺寸
        0.4, 128); // 比例和偏移量
    

    水平方向Sobel 算子得到的结果如下所示。

    result.jpg

    垂直方向滤波的调用方法为:

    cv::Sobel(image, // 输入
        sobelY, // 输出
        CV_8U, // 图像类型
        0, 1, // 内核规格
        3, // 正方形内核的尺寸
        0.4, 128); // 比例和偏移量
    

    垂直方向Sobel 算子得到的结果如下所示。

    result.jpg

    你可以组合这两个结果(垂直和水平方向),得到Sobel 滤波器的范数:

    // 计算Sobel 滤波器的范数
    cv::Sobel(image,sobelX,CV_16S,1,0);
    cv::Sobel(image,sobelY,CV_16S,0,1);
    cv::Mat sobel;
    // 计算L1 范数
    sobel= abs(sobelX)+abs(sobelY);
    

    在convertTo 方法中使用可选的缩放参数可得到一幅图像,图像中的白色用0 表示,更黑的灰色阴影用大于0 的值表示。这幅图像可以很方便地显示Sobel 算子的范数,代码如下所示:

    // 找到Sobel 最大值
    double sobmin, sobmax;
    cv::minMaxLoc(sobel,&sobmin,&sobmax);
    // 转换成8 位图像
    // sobelImage = -alpha*sobel + 255
    cv::Mat sobelImage;
    sobel.convertTo(sobelImage,CV_8U,-255./sobmax,255);
    

    得到的结果如下所示。

    result.jpg

    cv::Sobel 函数使用Sobel 内核来计算图像的卷积。函数的完整说明如下所示:

    cv::Sobel(image, // 输入
        sobel, // 输出
        image_depth, // 图像类型
        xorder, yorder, // 内核规格
        kernel_size, // 正方形内核的尺寸
        alpha, beta); // 比例和偏移量
    

    在图像处理领域,通常把绝对值之和作为范数进行计算。这称为L1 范数,它得到的结果与L2 范数比较接近,但计算速度快。本节将采用L1 范数:

    // 计算L1 范数
    sobel= abs(sobelX)+abs(sobelY);
    

    在检测边缘时,通常只计算范数。但如果需要同时计算范数和方向,可以使用下面的OpenCV函数:

    // 计算Sobel 算子,必须用浮点数类型
    cv::Sobel(image,sobelX,CV_32F,1,0);
    cv::Sobel(image,sobelY,CV_32F,0,1);
    // 计算梯度的L2 范数和方向
    cv::Mat norm, dir;
    // 将笛卡儿坐标换算成极坐标,得到幅值和角度
    cv::cartToPolar(sobelX,sobelY,norm,dir);
    

    6.6 计算拉普拉斯算子

    拉普拉斯算子也是一种基于图像导数运算的高通线性滤波器,它通过计算二阶导数来度量图像函数的曲率。

    在OpenCV 中,可用cv::Laplacian 函数计算图像的拉普拉斯算子。

    我们为这个算子创建一个简单的类,封装几个与拉普拉斯算子有关的运算。基本的属性和方法如下所示:

    class LaplacianZC {
    private:
        // 拉普拉斯算子
        cv::Mat laplace;
        // 拉普拉斯内核的孔径大小
        int aperture;
    public:
        LaplacianZC() : aperture(3) {}
        // 设置内核的孔径大小
        void setAperture(int a) {
            aperture = a;
        }
        // 计算浮点数类型的拉普拉斯算子
        cv::Mat computeLaplacian(const cv::Mat& image) {
            // 计算拉普拉斯算子
            cv::Laplacian(image, laplace, CV_32F, aperture);
            return laplace;
        }
    

    拉普拉斯算子的计算在浮点数类型的图像上进行。与上节一样,要对结果做缩放处理才能使其正常显示。缩放基于拉普拉斯算子的最大绝对值,其中数值0 对应灰度级128。类中有一个方法可获得下面的图像表示:

    // 获得拉普拉斯结果,存在8 位图像中
    // 0 表示灰度级128
    // 如果不指定缩放比例,那么最大值会放大到255
    cv::Mat getLaplacianImage(double scale = -1.0) {
        if (scale < 0) {
            double lapmin, lapmax;
            // 取得最小和最大拉普拉斯值
            cv::minMaxLoc(laplace, &lapmin, &lapmax);
            // 缩放拉普拉斯算子到127
            scale = 127 / std::max(-lapmin, lapmax);
        }
        // 生成灰度图像
        cv::Mat laplaceImage;
        laplace.convertTo(laplaceImage, CV_8U, scale, 128);
        return laplaceImage;
    }
    

    使用这个类,从7×7 内核计算拉普拉斯图像的方法为:

        // 用LaplacianZC 类计算拉普拉斯算子
        LaplacianZC laplacian;
        laplacian.setAperture(7); // 7×7 的拉普拉斯算子
        cv::Mat flap = laplacian.computeLaplacian(image);
        cv::Mat laplace = laplacian.getLaplacianImage();
    

    得到的图像如下所示。

    result.jpg

    相关文章

      网友评论

          本文标题:第 6 章 图像滤波

          本文链接:https://www.haomeiwen.com/subject/yaphqctx.html