阅读(1988) (11)

OpenCV侵蚀和扩张

2017-09-01 10:32:04 更新

目标

在本教程中,您将学习如何:

  • 应用两个非常常见的形态运算符:侵蚀和扩张。为此,您将使用以下OpenCV功能:
注意
下面的解释属于Bradski和Kaehler 的“ 学习OpenCV ”一书。

形态作业

  • 简而言之:一组基于形状处理图像的操作。形态操作将结构元素应用于输入图像并生成输出图像。
  • 最基本的形态作用是:侵蚀和扩张。它们有广泛的用途,即:
  1. 消除噪音
  2. 隔离单个元素并连接图像中的不同元素。
  3. 查找图像中的强度凸点或孔
  • 我们将简要解释膨胀和侵蚀,使用以下图像作为示例:

OpenCV侵蚀和扩张

扩张

  • 该操作包括将图像与某些内核(B)进行卷积,其可以具有任何形状或尺寸,通常为正方形或圆形。AB
  • 内核具有定义的锚点,通常是内核的中心。B
  • 当内核在图像上扫描时,我们计算由B重叠的最大像素值,并用该最大值替换锚点位置中的图像像素。您可以推断,这种最大化的操作会使图像中的亮区“增长”(因此称为扩张)。以上图为例。应用扩张我们可以得到:BB

OpenCV扩张

背景(明亮)扩大了字母的黑色地区。

为了更好地把握想法并避免可能的混乱,在另一个例子中,我们已经将原始图像倒过来,如白色的对象现在是这个字母。我们已经执行了两个具有大小的矩形结构元素的扩张3x3。

OpenCV扩张

左图:原图反转,右图:产生扩张

膨胀使物体变白。

侵蚀

  • 这个操作是扩张的姊妹。它计算给定内核区域的局部最小值。
  • 当内核在图像上扫描时,我们计算由重叠的最小像素值,并用该最小值替换锚点下的图像像素。BB
  • 对于扩张的例子,我们可以将侵蚀算子应用于原始图像(如上所示)。您可以在下面的结果中看到,图像的明亮区域(背景,显然)变得更薄,而黑暗区域(“写作”)变得更大。

OpenCV侵蚀

以相似的方式,通过对反转的原始图像(具有尺寸的矩形结构元素的两次侵蚀)施加侵蚀操作来产生相应的图像3x3:

OpenCV侵蚀

左图:原图反转,右图:造成侵蚀

侵蚀使物体变白。

Code

本教程的代码如下所示。您也可以在这里下载


#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
Mat src, erosion_dst, dilation_dst;
int erosion_elem = 0;
int erosion_size = 0;
int dilation_elem = 0;
int dilation_size = 0;
int const max_elem = 2;
int const max_kernel_size = 21;
void Erosion( int, void* );
void Dilation( int, void* );
int main( int, char** argv )
{
  src = imread( argv[1], IMREAD_COLOR );
  if( src.empty() )
    { return -1; }
  namedWindow( "Erosion Demo", WINDOW_AUTOSIZE );
  namedWindow( "Dilation Demo", WINDOW_AUTOSIZE );
  moveWindow( "Dilation Demo", src.cols, 0 );
  createTrackbar( "Element:n 0: Rect n 1: Cross n 2: Ellipse", "Erosion Demo",
          &erosion_elem, max_elem,
          Erosion );
  createTrackbar( "Kernel size:n 2n +1", "Erosion Demo",
          &erosion_size, max_kernel_size,
          Erosion );
  createTrackbar( "Element:n 0: Rect n 1: Cross n 2: Ellipse", "Dilation Demo",
          &dilation_elem, max_elem,
          Dilation );
  createTrackbar( "Kernel size:n 2n +1", "Dilation Demo",
          &dilation_size, max_kernel_size,
          Dilation );
  Erosion( 0, 0 );
  Dilation( 0, 0 );
  waitKey(0);
  return 0;
}
void Erosion( int, void* )
{
  int erosion_type = 0;
  if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }
  else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }
  else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }
  Mat element = getStructuringElement( erosion_type,
                       Size( 2*erosion_size + 1, 2*erosion_size+1 ),
                       Point( erosion_size, erosion_size ) );
  erode( src, erosion_dst, element );
  imshow( "Erosion Demo", erosion_dst );
}
void Dilation( int, void* )
{
  int dilation_type = 0;
  if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }
  else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }
  else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }
  Mat element = getStructuringElement( dilation_type,
                       Size( 2*dilation_size + 1, 2*dilation_size+1 ),
                       Point( dilation_size, dilation_size ) );
  dilate( src, dilation_dst, element );
  imshow( "Dilation Demo", dilation_dst );
}

说明

1、这里显示的大部分材料是微不足道的(如果您有任何疑问,请参阅前几节中的教程)。我们来看一下程序的一般结构:

  • 加载图像(可以是BGR或灰度)
  • 创建两个窗口(一个用于扩张输出,另一个用于侵蚀)
  • 为每个操作创建一组两个Trackbars:

      a、第一个跟踪栏 “元素”返回一个erosion_elem或dilation_ele

      b、第二个跟踪栏 “内核大小”返回对于相应操作的erosion_size或dilation_size。

    • 每次我们移动任何滑块,用户的功能Erosion或Dilation将被调用,它会根据当前的跟踪栏值来更新输出图像。

    我们分析这两个功能:

    2、侵蚀:
    void Erosion( int, void* )
    {
      int erosion_type = 0;
      if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }
      else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }
      else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }
      Mat element = getStructuringElement( erosion_type,
                           Size( 2*erosion_size + 1, 2*erosion_size+1 ),
                           Point( erosion_size, erosion_size ) );
      erode( src, erosion_dst, element );
      imshow( "Erosion Demo", erosion_dst );
    }

    执行侵蚀操作的功能是cv :: erode。我们可以看到,它有三个参数:

    • src:源图像
    • erosion_dst:输出图像
    • element:这是我们将用来执行操作的内核。如果我们不指定,默认是一个简单的3x3矩阵。否则,我们可以指定它的形状。为此,我们需要使用函数cv :: getStructuringElement
      Mat element = getStructuringElement( erosion_type,
                           Size( 2*erosion_size + 1, 2*erosion_size+1 ),
                           Point( erosion_size, erosion_size ) );

    我们可以为我们的内核选择三种形状:

    1. 矩形框:MORPH_RECT
    2. 十字架:MORPH_CROSS
    3. 椭圆:MORPH_ELLIPSE

    那么,我们只需要指定我们的内核和锚点的大小。如果未指定,则假定为中心。

    • 就这些。我们准备好对我们的形象进行侵蚀。
    注意
    此外,还有一个参数允许您一次执行多次侵蚀(迭代)。但是,我们没有在这个简单的教程中使用它。您可以查阅参考资料了解更多详情。

    3、扩张:

    代码如下。你可以看到,它完全类似于侵蚀代码片段。这里我们还可以选择定义我们的内核,它的锚点和要使用的操作符的大小。

    
    void Dilation( int, void* )
    {
      int dilation_type = 0;
      if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }
      else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }
      else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }
      Mat element = getStructuringElement( dilation_type,
                           Size( 2*dilation_size + 1, 2*dilation_size+1 ),
                           Point( dilation_size, dilation_size ) );
      dilate( src, dilation_dst, element );
      imshow( "Dilation Demo", dilation_dst );
    }

    结果

    编译上面的代码,并以图像作为参数执行。例如,使用这个图像:

    OpenCV侵蚀和扩张

    我们得到以下结果。自然地,改变轨道栏中的索引给出不同的输出图像。试试吧!甚至可以尝试添加第三个Trackbar来控制迭代次数。

    OpenCV侵蚀和扩张