opencv基础:腐蚀和膨胀

522 阅读4分钟

已经更文10天


1.目标

在本文中,将学习如何:

  • 应用两个非常常见的形态学算子:腐蚀和膨胀。使用以下 OpenCV 函数:

  • 笔记

    下面的解释来自Bradski 和 Kaehler的 《Learning OpenCV 》一书。

2.形态学运算

  • 形态学运算:一组基于形状处理图像的操作。形态学操作将结构元应用于输入图像并生成输出图像。

  • 最基本的形态学运算是:腐蚀和膨胀。它们具有广泛的用途,即:

    • 去除噪音
    • 隔离单个元素并连接图像中的不同元素。
    • 查找图像中的较大的突起或孔洞
  • 将以下图为例简要说明膨胀和腐蚀:

    Morphology_1_Tutorial_Theory_Original_Image.png

2.1.膨胀

  • 膨胀将图像A和核B进行卷积,核B可以是任何形状或大小,通常是正方形或圆形。

  • 核B定义一个锚点,通常是核的中心。

  • 将核B在图像上扫描,计算核B和图像A重叠部分的最大像素值,并用该最大值替换锚点位置的图像像素。可以推断出,这种最大化操作会导致图像中的明亮区域“增长”(因此名称为dilation)。

  • 膨胀运算为:dst(x,y)=max(x,y):element(x,y)0src(x+x,y+y)dst(x,y)=max_{ (x^{'},y^{'}):element(x^{'},y^{'}) \ne 0 } src(x+x^{'},y+y^{'})

  • 以上图为例。应用膨胀我们可以得到:

    Morphology_1_Tutorial_Theory_Dilation.png

  • 字母的明亮区域在背景的黑色区域周围扩大。

2.2 腐蚀

  • 这是膨胀的反向操作。它计算给定内核区域的局部最小值。

  • 将核B在图像上扫描,计算核B和图像A重叠部分的最小像素值并用该最小值替换锚点下的图像像素。

  • 腐蚀操作是:dst(x,y)=min(x,y):element(x,y)0src(x+x,y+y)dst(x,y)=min_{ (x^{'},y^{'}):element(x^{'},y^{'}) \ne 0 } src(x+x^{'},y+y^{'})

  • 类似于膨胀的例子,可以将腐蚀算子应用于原始图像(如上所示)。可以在下面的结果中看到图像的明亮区域变得更窄,而暗区域变得更大。

    Morphology_1_Tutorial_Theory_Erosion.png

3. 代码

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


#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
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 argc, char** argv )
{
  CommandLineParser parser( argc, argv, "{@input | LinuxLogo.jpg | input image}" );
  src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR );
  if( src.empty() )
  {
    cout << "Could not open or find the image!\n" << endl;
    cout << "Usage: " << argv[0] << " <Input image>" << endl;
    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 );
}

4. 代码解释

代码主题框架如下:

int main( int argc, char** argv )
{
  CommandLineParser parser( argc, argv, "{@input | LinuxLogo.jpg | input image}" );
  src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR );
  if( src.empty() )
  {
    cout << "Could not open or find the image!\n" << endl;
    cout << "Usage: " << argv[0] << " <Input image>" << endl;
    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;
}
  1. 加载图像(可以是 BGR 或灰度)

  2. 创建两个窗口(一个用于膨胀输出,另一个用于腐蚀)

  3. 为每个操作创建一组两个 Trackbar:

    • 第一个轨迹栏“元素”返回erosion_elemdilation_elem
    • 第二个轨迹栏“内核大小”返回对应操作的腐蚀大小或膨胀大小。
  4. 调用一次腐蚀和膨胀来显示初始图像。

每次移动任何滑块时,都会调用用户的函数ErosionDilation,它会根据当前的轨迹栏值更新输出图像。

我们来分析这两个函数:

4.1 腐蚀函数


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 ) );

可以为内核选择三种形状中的任何一种:

  • 矩形框:MORPH_RECT
  • 交叉:MORPH_CROSS
  • 椭圆:MORPH_ELLIPSE

然后,我们只需要指定内核的大小和锚点。如果未指定,则假定它位于中心。

就这些。下边进行腐蚀操作。

4.2 膨胀函数

代码如下。它与腐蚀的代码片段完全相似。在这里,还可以选择定义我们的内核、它的锚点和要使用的运算符的大小。


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 );
}
  • 笔记

    此外,还有更多参数允许一次执行多个腐蚀/膨胀(迭代),还可以设置边框类型和值。但是,没有在这个简单的教程中使用这些。可以查看参考以获取更多详细信息。

5. 结果

编译上面的代码并使用图像作为参数执行它(或者如果使用 python,则运行脚本)。如果您不提供图像作为参数,则将使用默认示例图像 ( LinuxLogo.jpg )。

例如,使用此图像:

Morphology_1_Tutorial_Original_Image.jpg

得到下面的结果。改变 Trackbars 中的索引自然会给出不同的输出图像。试试看!甚至可以尝试添加第三个 Trackbar 来控制迭代次数。

Morphology_1_Result.jpg

(取决于编程语言,输出可能略有不同或只有 1 个窗口)

原文地址