opencv基础:平滑图像

187 阅读5分钟

1. 目标

在本文中,将学习如何使用 OpenCV 函数应用各种线性滤波器来平滑图像,例如:

2. 理论

笔记

下面的解释来自Richard Szeliski的Computer Vision: Algorithms and Applications 一书和LearningOpenCV

  • 平滑,也称为模糊,是一种简单且经常使用的图像处理操作。

  • 平滑的原因有很多。在本文中,将专注于平滑以减少噪声(其他用途将在以后文章中看到)。

  • 为了执行平滑操作,将对图像应用过滤器。最常见的过滤器类型是线性的,其中输出像素的值(即g(i,j)g(i,j))被确定为输入像素值的加权和(即 f(i + k,j + l )f(i + k,j + l )) :

g(i,j)=k,lf(i+k,j+l)h(k,l) g(i,j)=\sum_{k,l}f(i+k,j+l)h(k,l)

h(k,l)h(k,l) 称为,其实就是滤波器的系数。

可以将滤波器可视化为在图像上滑动的系数窗口。

  • 过滤器有很多种,这里将提到最常用的:

2.1 Normalized Box Filter

  • 这个过滤器是最简单的!每个输出像素是其邻居像素的平均值(它们都具有相同的权重)

  • 内核如下:

K=1KwidthKheight=[11111111111111] K=\frac{1}{K_{width}\cdot K_{height} } = \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 \\ 1 & 1 & 1 & \cdots & 1 \\ \cdot & \cdot & \cdot & \cdots & 1 \\ \cdot & \cdot & \cdot & \cdots & 1 \\ 1 & 1 & 1& \cdots & 1 \end{bmatrix}

2.2 Gaussian Filter

  • 可能是最有用的过滤器(虽然不是最快的)。高斯滤波是通过将输入图像与高斯核进行卷积,然后将它们全部相加。

  • 只是为了让图片更清晰,还记得一维高斯核的样子吗?

    Smoothing_Tutorial_theory_gaussian_0.jpg

    假设图像是一维的,注意到位于中间的像素的权重最大。其邻居的权重随着它们与中心像素之间的空间距离的增加而减小。

  • 笔记

请记住,二维高斯可以表示为:

G0(x,y)=Ae(xμx)22σx2+(yμy)22σy2G_0(x,y)=Ae^{\frac{-(x-\mu_x )^2}{2\sigma_x^2 }+ \frac{-(y-\mu_y )^2}{2\sigma_y^2 } }

μ\mu是均值(峰值)和σ2\sigma^2表示方差(对于变量x和y)

2.3 Median Filter

中值滤波器遍历信号的每个元素(在本例中为图像),并将每个像素替换为其相邻像素的中值(位于被评估像素周围的正方形邻域中)。

2.4 Bilateral Filter

  • 到目前为止,已经解释了一些主要目标是平滑输入图像的过滤器。但是,有时过滤器不仅可以消除噪声,还把边缘平滑了,导致图像模糊。为了避免平滑边缘(至少在一定程度上避免),可以使用双边滤波器。双边滤波是一种非线性滤波器,它可以达到保持边缘、降噪平滑的效果。
  • 与高斯滤波器类似,双边滤波器也考虑相邻像素,并为每个像素分配权重。这些权重有两个分量,第一个分量与高斯滤波器使用的权重相同。第二个组件考虑了相邻像素和评估像素之间的强度差异。
  • 有关更详细的说明,您可以查看此链接

3. 代码

  • 这个程序有什么作用?

    • 加载图像
    • 应用 4 种不同类型的过滤器(在理论中解释)并按顺序显示过滤后的图像
  • 点击这里下载代码

  • 代码一览:


#include <iostream>
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace std;
using namespace cv;
int DELAY_CAPTION = 1500;
int DELAY_BLUR = 100;
int MAX_KERNEL_LENGTH = 31;
Mat src; Mat dst;
char window_name[] = "Smoothing Demo";
int display_caption( const char* caption );
int display_dst( int delay );
int main( int argc, char ** argv )
{
   namedWindow( window_name, WINDOW_AUTOSIZE );
   const char* filename = argc >=2 ? argv[1] : "lena.jpg";
   src = imread( samples::findFile( filename ), IMREAD_COLOR );
   if (src.empty())
   {
       printf(" Error opening image\n");
       printf(" Usage:\n %s [image_name-- default lena.jpg] \n", argv[0]);
       return EXIT_FAILURE;
   }
   if( display_caption( "Original Image" ) != 0 )
   {
       return 0;
   }
   dst = src.clone();
   if( display_dst( DELAY_CAPTION ) != 0 )
   {
       return 0;
   }
   if( display_caption( "Homogeneous Blur" ) != 0 )
   {
       return 0;
   }
   for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
   {
       blur( src, dst, Size( i, i ), Point(-1,-1) );
       if( display_dst( DELAY_BLUR ) != 0 )
       {
           return 0;
       }
   }
   if( display_caption( "Gaussian Blur" ) != 0 )
   {
       return 0;
   }
   for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
   {
       GaussianBlur( src, dst, Size( i, i ), 0, 0 );
       if( display_dst( DELAY_BLUR ) != 0 )
       {
           return 0;
       }
   }
   if( display_caption( "Median Blur" ) != 0 )
   {
       return 0;
   }
   for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
   {
       medianBlur ( src, dst, i );
       if( display_dst( DELAY_BLUR ) != 0 )
       {
           return 0;
       }
   }
   if( display_caption( "Bilateral Blur" ) != 0 )
   {
       return 0;
   }
   for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
   {
       bilateralFilter ( src, dst, i, i*2, i/2 );
       if( display_dst( DELAY_BLUR ) != 0 )
       {
           return 0;
       }
   }
   display_caption( "Done!" );
   return 0;
}
int display_caption( const char* caption )
{
   dst = Mat::zeros( src.size(), src.type() );
   putText( dst, caption,
            Point( src.cols/4, src.rows/2),
            FONT_HERSHEY_COMPLEX, 1, Scalar(255, 255, 255) );
   return display_dst(DELAY_CAPTION);
}
int display_dst( int delay )
{
   imshow( window_name, dst );
   int c = waitKey ( delay );
   if( c >= 0 ) { return -1; }
   return 0;
}

4. 代码解释

下边讨论一下只涉及平滑过程的 OpenCV 函数,因为其余的现在已经知道了。

4.1 Normalized Block Filter:

  • OpenCV 提供了blur()函数来使用这个过滤器进行平滑处理。指定了 4 个参数(更多详细信息,请查看参考资料):

    • src:源图像
    • dst:目标图像
    • Size( w, h ) :定义要使用的内核的大小(宽度为w像素,高度为h像素)
    • Point(-1, -1) :指示锚点(评估的像素)相对于邻域的位置。如果有负值,则内核的中心被认为是锚点。

for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
    {
        blur( src, dst, Size( i, i ), Point(-1,-1) );
        if( display_dst( DELAY_BLUR ) != 0 )
        {
            return 0;
        }
    }

4.2 Gaussian Filter:

  • 它由函数GaussianBlur()执行:这里使用 4 个参数(更多详细信息,请查看 OpenCV 参考):

    • src:源图像
    • dst:目标图像
    • Size(w, h) :要使用的内核的大小(要考虑的邻居)。在和H必须是奇数和正数,否则大小将使用pX和p是论据。
    • σx\sigma_x: x 的标准差。参数值0意味σx\sigma_x是使用内核大小计算的。
    • σy\sigma_y: y 的标准差。参数值0意味σy\sigma_y是使用内核大小计算的。

 for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
    {
        GaussianBlur( src, dst, Size( i, i ), 0, 0 );
        if( display_dst( DELAY_BLUR ) != 0 )
        {
            return 0;
        }
    }

4.3 Median Filter:

  • 该过滤器由medianBlur()函数提供:使用三个参数:

    • src:源图像
    • dst:目标图像,必须与src类型相同
    • i:内核的大小(只有一个,因为我们使用方形窗口)。大小必须为奇数。

for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
    {
        medianBlur ( src, dst, i );
        if( display_dst( DELAY_BLUR ) != 0 )
        {
            return 0;
        }
    }

4.4 Bilateral Filter

  • 由 OpenCV 函数提供的**双边过滤器()** 我们使用 5 个参数:

    • src:源图像
    • dst:目标图像
    • d:每个像素邻域的直径。
    • σSolor\sigma_{Solor}:色彩空间的标准差。
    • σSpace\sigma_{Space}:坐标空间标准偏差(以像素为单位)

 for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
    {
        bilateralFilter ( src, dst, i, i*2, i/2 );
        if( display_dst( DELAY_BLUR ) != 0 )
        {
            return 0;
        }
    }

5.结果

  • 打开一个图像(在本例中为 lena.jpg)并展示了在 4 个过滤器下处理效果。

  • 图像的快照:

原 图 image.png

Median Filter效果 Smoothing_Tutorial_Result_Median_Filter.jpg

Gaussian Filter效果 image.png

Bilateral Filter 效果 image.png

原文连接