2017-02-26 17:37:26 sn_gis 阅读数 7818

形态学在图像处理中的应用


腐蚀和膨胀

理论基础

形态学操作

  • 简而言之:一系列操作基于形状来操作图像,形态学操作通过在图像上应用结构元素来产生输出图像。
  • 最基础的形态学操作包括腐蚀和扩张。它包含广泛的应用:
    • 移除噪声
    • 孤立一些单独的元素和聚合一些分散的元素
    • 找到图像中的局部块状或者孔
  • 我们将使用下面的图像简要的介绍膨胀和腐蚀

    这里写图片描述

膨胀

  • 这一操作包含使用卷积核B对图片A进行卷积运算,这个卷积核可以有任意的形状和大小,通常是一个方形或者圆形。
  • 卷积核B通常有个锚点,通常位于卷积核的中央位置。
  • 随着卷积核扫描这个图像,我们计算叠加区域的最大像素值,并将锚点的位置用最大值替换。这样你可以推断最大化操作导致图片中亮的区域增长(所以这里面叫做膨胀)。举个例子,应用膨胀我们可以得到:

    这里写图片描述

    我们看到背景亮的区域膨胀变大。
    为了掌握这一思想,避免可能的混淆,我们在另外一个例子中反转了原来的图像,现在白色代表原先的字。我们使用3*3矩形元素的元素进行膨胀操作两次。


    左侧是反转之后的图像-右侧是膨胀之后的图像

膨胀使得对象的白色区域变大。

腐蚀

  1. 腐蚀与膨胀类似。它是计算卷积核里面的最小元素。
  2. 随着卷积核B扫描图片,它会计算B叠加区域的最小像素值,并使用这个像素值替换锚点的值。
  3. 与膨胀相似,对原始的图像应用腐蚀操作。你可以看到背景亮的区域变小,而黑的区域变得很大。



    同样对原始图像进行反转,进行腐蚀操作,得到:


    左侧为原始图像反转的图像-右侧为腐蚀的结果

    腐蚀使得对象白色变小。

代码

示例代码如下,你可以从这里下载代码

#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也可以是灰度图像)
    • 创建两个窗口(一个是膨胀输出,一个是腐蚀输出)
    • 对于每个操作常见一组滑动条
      • 第一个元素返回元素类型erosion_elem 或者dilation_elem
      • 第二个元素返回卷积核的大小
    • 当我们移动滑动条的时候,用户的函数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:我们将使用的卷积核。如果不指定,将使用3*3的矩阵。我们可以指定形状。为了指定形状,我们可以使用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
    然后我们只需指定核的大小和锚点。如果没有指定,假定在中心。

  • 指定这些值之后,我们就可以进行图像的腐蚀操作了。

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提供的5中操作。

开操作

  • 它通过先进性腐蚀操作,再进行膨胀操作得到
d s t = o p e n ( s r c , e l e m e n t ) = d i l a t e ( e r o d e ( s r c , e l e m e n t ) )

  • 在移除小的对象时候很有用(假设物品是亮色,前景色是黑色)
  • 如下所示。左侧是原始图像右侧是应用开操作之后的图像。我们可以看到左侧图像的小的空间消失



    为了更清晰,对原先对象进行反转,然后再进行开操作,结果如下:


    左侧为原始图像反转-右侧为开操作之后的图像

闭操作

  • 比操作是先进行膨胀然后进行腐蚀操作
    dst = close( src, element ) = erode( dilate( src, element ) )

  • 有利于移除小的洞(黑色区域)




    便于说明问题,对反转的图像进行闭操作


    左侧反转图像-右侧为进行闭操作之后的图像

梯度操作

  • 是膨胀操作与腐蚀操作的差
    dst=morphgrad(src,element)=dilate(src,element)erode(src,element)
  • 对于寻找对象的轮廓很有用,如下:


顶帽操作

  • 是原图像与开操作对象的差
    dst=tophat(src,element)=srcopen(src,element)


黑帽操作

  • 是闭操作与原始图像的差值
    dst=blackhat(src,element)=close(src,element)src



    示例代码

    教程代码如下,你可以从这里下载

#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
Mat src, dst;
int morph_elem = 0;
int morph_size = 0;
int morph_operator = 0;
int const max_operator = 4;
int const max_elem = 2;
int const max_kernel_size = 21;
const char* window_name = "Morphology Transformations Demo";
void Morphology_Operations( int, void* );
int main( int, char** argv )
{
  src = imread( argv[1], IMREAD_COLOR ); // Load an image
  if( src.empty() )
    { return -1; }
  namedWindow( window_name, WINDOW_AUTOSIZE ); // Create window
  createTrackbar("Operator:\n 0: Opening - 1: Closing  \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );
  createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
                  &morph_elem, max_elem,
                  Morphology_Operations );
  createTrackbar( "Kernel size:\n 2n +1", window_name,
                  &morph_size, max_kernel_size,
                  Morphology_Operations );
  Morphology_Operations( 0, 0 );
  waitKey(0);
  return 0;
}
void Morphology_Operations( int, void* )
{
  // Since MORPH_X : 2,3,4,5 and 6
  int operation = morph_operator + 2;
  Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
  morphologyEx( src, dst, operation, element );
  imshow( window_name, dst );
}

代码阐述

  1. 我们来看一下程序的结构:

    • 加载图像
    • 创建一个窗口展示形态学操作结果
    • 创建三个滑动按钮来输入参数

      • 第一个滑动按钮返回形态学操作类型,使用morph_operator createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );
      • 第二个滑动条参数返回morph_elem,它表示卷积核的类型createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
        &morph_elem, max_elem,
        Morphology_Operations );
      • 最后一个参数表示morph_size表示卷积核的大小createTrackbar( "Kernel size:\n 2n +1", window_name,
        &morph_size, max_kernel_size,
        Morphology_Operations );
    • 每次你移动滑动条的时候,Morphology_Operations 函数会被调用,使得新的形态学操作有效,它会根据当前滑动按钮的值输出图像void Morphology_Operations( int, void* )
      {
      // Since MORPH_X : 2,3,4,5 and 6
      int operation = morph_operator + 2;
      Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
      morphologyEx( src, dst, operation, element );
      imshow( window_name, dst );
      }

      我们可以看到执行形态学转换的例子的关键函数是cv::morphologyEx,在这里我们指定了四个参数(剩余的使用默认值):

      • src,源输入图像
      • dst,输出文件
      • operation,执行的形态学转换。注意我们有5个可选项:
        • 开操作: MORPH_OPEN : 2
        • 闭操作: MORPH_CLOSE: 3
        • 梯度操作: MORPH_GRADIENT: 4
        • 顶帽操作: MORPH_TOPHAT: 5
        • 黑帽操作: MORPH_BLACKHAT: 6

      这里的值从2到6,这就是前面函数中加2的原因。
      int operation = morph_operator + 2;

      • element,卷积核,我们使用cv::getStructuringElement来定义元素结构

结果

  • 在编译完代码之后,我们使用以下图片作为输入参数:



    • 这里我们显示了两个窗口截图第一幅图展示了使用十字形卷积核进行开操作的输出结果,第二幅展示了使用椭圆形卷积核进行黑帽操作得出的结果。


利用形态学操作抽取水平和竖直线

理论

形态学操作

形态学是一系列的图像处理操作,这些图像处理基于预先定义好的结构核。输出图像中的每个像素的值由输入图像的响应像素和周边像素决定。通过选择核的尺寸和大小,你可以构造对于输入图像中特定形状敏感的形态学操作。
形态学中,最基础的两个操作是膨胀和腐蚀。膨胀向图像中的对象周边添加像素,腐蚀恰恰相反,添加或者移除 的多少依赖于构造的核的形状和大小,一般来说,这两项操作遵循以下规则:

  • 膨胀:输出的像素值是落在核内的最大值。例如一个二值图像,如果有一个落在核内的像素值是1,那么相应的输出图像的像素值为1.


    二值图像上的膨胀

    灰度图像上的膨胀
  • 腐蚀:腐蚀与膨胀恰恰相反,取最小值


    二值图像的腐蚀

    灰度图像的腐蚀
  • 构造核
    通过以上可以看出,一般的形态学操作都使用构造的核来探测输出的图像,它是最重要的。这个核通常只包含0和1两个元素,可以拥有任意的形状和大小。通常来说,要比输入的 图像小的多,值为1的元素定义邻居。核的中心元素,也叫原始元素决定要处理的元素。
    例如下面是一个7*7大小的菱形核。


    菱形核和它的起始点

    构造的核元素可以有许多形状,例如线形,菱形,周期性线性,磁盘形,圆形等。通常形状和大小和输入图像中要处理的对象类似。例如,要发现图像中的线性,需要构造出线性的核,你在后面将会看到。

示例代码

示例代码如下,你可以在这里下载

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int, char** argv)
{
    // Load the image
    Mat src = imread(argv[1]);
    // Check if image is loaded fine
    if(!src.data)
        cerr << "Problem loading image!!!" << endl;
    // Show source image
    imshow("src", src);
    // Transform source image to gray if it is not
    Mat gray;
    if (src.channels() == 3)
    {
        cvtColor(src, gray, CV_BGR2GRAY);
    }
    else
    {
        gray = src;
    }
    // Show gray image
    imshow("gray", gray);
    // Apply adaptiveThreshold at the bitwise_not of gray, notice the ~ symbol
    Mat bw;
    adaptiveThreshold(~gray, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
    // Show binary image
    imshow("binary", bw);
    // Create the images that will use to extract the horizontal and vertical lines
    Mat horizontal = bw.clone();
    Mat vertical = bw.clone();
    // Specify size on horizontal axis
    int horizontalsize = horizontal.cols / 30;
    // Create structure element for extracting horizontal lines through morphology operations
    Mat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontalsize,1));
    // Apply morphology operations
    erode(horizontal, horizontal, horizontalStructure, Point(-1, -1));
    dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1));
    // Show extracted horizontal lines
    imshow("horizontal", horizontal);
    // Specify size on vertical axis
    int verticalsize = vertical.rows / 30;
    // Create structure element for extracting vertical lines through morphology operations
    Mat verticalStructure = getStructuringElement(MORPH_RECT, Size( 1,verticalsize));
    // Apply morphology operations
    erode(vertical, vertical, verticalStructure, Point(-1, -1));
    dilate(vertical, vertical, verticalStructure, Point(-1, -1));
    // Show extracted vertical lines
    imshow("vertical", vertical);
    // Inverse vertical image
    bitwise_not(vertical, vertical);
    imshow("vertical_bit", vertical);
    // Extract edges and smooth image according to the logic
    // 1. extract edges
    // 2. dilate(edges)
    // 3. src.copyTo(smooth)
    // 4. blur smooth img
    // 5. smooth.copyTo(src, edges)
    // Step 1
    Mat edges;
    adaptiveThreshold(vertical, edges, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2);
    imshow("edges", edges);
    // Step 2
    Mat kernel = Mat::ones(2, 2, CV_8UC1);
    dilate(edges, edges, kernel);
    imshow("dilate", edges);
    // Step 3
    Mat smooth;
    vertical.copyTo(smooth);
    // Step 4
    blur(smooth, smooth, Size(2, 2));
    // Step 5
    smooth.copyTo(vertical, edges);
    // Show final result
    imshow("smooth", vertical);
    waitKey(0);
    return 0;
}

阐述和结果

  1. 载入原始图像,并检查它是否加载成功,紧接着进行展示:
 // Load the image
    Mat src = imread(argv[1]);
    // Check if image is loaded fine
    if(!src.data)
        cerr << "Problem loading image!!!" << endl;
    // Show source image
    imshow("src", src);




2. 如果图像没有转换,将图像转为灰度图像

 // Transform source image to gray if it is not
    Mat gray;
    if (src.channels() == 3)
    {
        cvtColor(src, gray, CV_BGR2GRAY);
    }
    else
    {
        gray = src;
    }
    // Show gray image
    imshow("gray", gray);




3. 之后将灰度图像转为二值图像。注意到符号~表示取反:

 // Apply adaptiveThreshold at the bitwise_not of gray, notice the ~ symbol
    Mat bw;
    adaptiveThreshold(~gray, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
    // Show binary image
    imshow("binary", bw);




4. 现在我们已经准备好了提取水平线和竖直线,同时紧接着会将音符从音符本中提取出来,但是首先初始化输出的图像
// Create the images that will use to extract the horizontal and vertical lines
Mat horizontal = bw.clone();
Mat vertical = bw.clone();

5. 前面已经介绍为了提取想要的对象,我们必须构造相应的核,为了提取水平线我们构造下面形状的核:



在源代码中通过以下代码段展示:

  // Specify size on horizontal axis
    int horizontalsize = horizontal.cols / 30;
    // Create structure element for extracting horizontal lines through morphology operations
    Mat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontalsize,1));
    // Apply morphology operations
    erode(horizontal, horizontal, horizontalStructure, Point(-1, -1));
    dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1));
    // Show extracted horizontal lines
    imshow("horizontal", horizontal);




6. 竖直线处理类似:


  // Specify size on vertical axis
    int verticalsize = vertical.rows / 30;
    // Create structure element for extracting vertical lines through morphology operations
    Mat verticalStructure = getStructuringElement(MORPH_RECT, Size( 1,verticalsize));
    // Apply morphology operations
    erode(vertical, vertical, verticalStructure, Point(-1, -1));
    dilate(vertical, vertical, verticalStructure, Point(-1, -1));
    // Show extracted vertical lines
    imshow("vertical", vertical);




7. 如你所看到的那样,音符的边缘很粗糙。你需要细化边缘来获得平滑的结果:

   // Inverse vertical image
    bitwise_not(vertical, vertical);
    imshow("vertical_bit", vertical);
    // Extract edges and smooth image according to the logic
    // 1. extract edges
    // 2. dilate(edges)
    // 3. src.copyTo(smooth)
    // 4. blur smooth img
    // 5. smooth.copyTo(src, edges)
    // Step 1
    Mat edges;
    adaptiveThreshold(vertical, edges, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2);
    imshow("edges", edges);
    // Step 2
    Mat kernel = Mat::ones(2, 2, CV_8UC1);
    dilate(edges, edges, kernel);
    imshow("dilate", edges);
    // Step 3
    Mat smooth;
    vertical.copyTo(smooth);
    // Step 4
    blur(smooth, smooth, Size(2, 2));
    // Step 5
    smooth.copyTo(vertical, edges);
    // Show final result
    imshow("smooth", vertical);



2016-11-15 14:25:23 yangleo1987 阅读数 978

在我做的项目中,应用到一些形态学的运算。在这里记录下来,大家相互学习和提升。

项目背景说明: 在项目中,采用红外相机检测并跟踪进港船舶;因此在相机检测目标的过程中,需要先对运动船只进行精确定位,定位到船舶后,再采用目标跟踪算法,对目标进行实时跟踪。在运动船舶定位的过程,用到了形态学的运算。并借此把常用到的一些形态学运算都测试一下。

关于形态学的介绍,请看我的另一篇博文:点击打开链接


下图的左侧是红外相机采集到的视频录像,有一条船进入港口,右侧是通过运动检测,检测出来的船舶的运动块,大家可以看到,左侧的检测结果会有一些噪声(白点),船体有点分离。



下面,就是我对上述右侧的图像进行一系列的形态学运算:

下列图框左上角处表明框图的意思:

src:  操作原图

gray:操作原图的灰度图

erode:对gray图进行腐蚀操作

dilate:对gray图进行膨胀操作

open:对gray图进行开运算

close:对gray进行闭运算



本项目中,我采用的闭运算的效果图进行后续的处理。


2019-12-12 22:48:37 weixin_44225182 阅读数 122

一、实验名称

数学形态学应用

二、实验目的

1.熟悉MATLAB软件的使用。
2.掌握数字图像处理中的腐蚀、膨胀定义及操作。
3.掌握图像的开运算、闭运算

三、实验内容

1.在长方形目标物A中,内部有噪声导致的空洞,周围有噪声块。编写MATLAB程序,达到处理后效果:目标本身除了4个角的直角变为圆角外没有变化。
2.利用击中击不中原理,确定所需判断图像在图像中的位置

四、实验仪器与设备

Win10 64位电脑
MATLAB R2017a

五、实验原理

1.腐蚀
    把结构元素B平移a后得到Ba,若Ba包含于X,我们记下这个a点,所有满足上述条件的a点组成的集合称做X被B腐蚀(Erosion)的结果。用公式表示为:E(X)={a| Ba∈X}=XB。原理图如下:
在这里插入图片描述
2.膨胀
    膨胀(dilation)可以看做是腐蚀的对偶运算,其定义是:把结构元素B平移a后得到Ba,若Ba击中X,我们记下这个a点。所有满足上述条件的a点组成的集合称做X被B膨胀的结果。用公式表示为:D(X)={a | Ba↑X}=X B,如图6.13所示。图6.13中X是被处理的对象,B是结构元素,不难知道,对于任意一个在阴影部分的点a,Ba击中X,所以X被B膨胀的结果就是那个阴影部分。阴影部分包括X的所有范围,就象X膨胀了一圈似的,这就是为什么叫膨胀的原因。原理图如下:
在这里插入图片描述
3.击中与击不中原理
    击中击不中变换(HMT)需要两个结构元素B1和B2,合成一个结构元素对B=(B1,B2)一个用于探测图像内部,作为击中部分;另一个用于探测图像外部,作为击不中部分。显然,B1和B2是不应该相连接的,即B1∩B2=Φ。击中击不中变换的数学表达式为:
g(x, y)=hitmiss[f(x, y), B]=erode[f(x, y), B1]AND erode[fc(x, y), B2]
其中,fc(x,y)表示的是f(x,y)的补集。

Hit-miss算法步骤:
击中击不中变换是形态学中用来检测特定形状所处位置的一个基本工具。它的原理就是使用腐蚀;如果要在一幅图像A上找到B形状的目标,我们要做的是:

  1. 建立一个比B大的模板W;使用此模板对图像A进行腐蚀,得到图像假设为Process1;
  2. 用B减去W,从而得到V模板(W-B);使用V模板对图像A的补集进行腐蚀,得到图像假设为Process2
  3. Process1与Process2取交集;得到的结果就是B的位置。这里的位置可能不是B的中心位置,要视W-B时对齐的位置而异; 其实很简单,两次腐蚀,然后交集,结果就出来了。

六、实验过程及代码

1.去除噪声、空洞

A1=imread('noise.png');

C=[0 1 0
   1 1 1
   0 1 0];
A2=imdilate(A1,C);%图像A1被结构元素B膨胀
A3=imdilate(A2,C);
A4=imdilate(A3,C);
A5=imdilate(A4,C);
A6=imdilate(A5,C);
A7=imdilate(A6,C);
A8=imdilate(A7,C);

A9=imerode(A8,C); %腐蚀
A10=imerode(A9,C);
A11=imerode(A10,C);
A12=imerode(A11,C);
A13=imerode(A12,C);
A14=imerode(A13,C);
A15=imerode(A14,C);
A16=imerode(A15,C);
A17=imerode(A16,C);
A18=imerode(A17,C);
A19=imerode(A18,C);
A20=imerode(A19,C);

D=strel('rectangle',[2 2]);
A21=imdilate(A20,C);
A22=imdilate(A21,C);
A23=imdilate(A22,C);
A24=imdilate(A23,C);
A25=imdilate(A24,C);
A26=imdilate(A25,C);
A27=imdilate(A26,C);

imshow(A27);

2.击中与击不中

t=zeros(500,500);

for i=100:200
for j=100:200
t(i,j)=1;
end
end

for i=70:140
for j=370:440
t(i,j)=1;
end
end

for i=300:400
for j=300:480
t(i,j)=1;
end
end

imshow(t);%构造原图

m=zeros(100,100);

for i=1:100
for j=1:100
m(i,j)=1;
end
end

figure,imshow(m);%构造m结构元素

n=zeros(120,120);

for i=1:120
for j=1:120
n(i,j)=1;
end
end

for i=10:110
for j=10:110
n(i,j)=0;
end
end

figure,imshow(n);%构造n结构元素 与m交集为空

x1=imerode(t,m);
figure,imshow(x1);%原图被m结构元素腐蚀后
x2=imerode(~t,n);
figure,imshow(x2);%原图补集被n结构元素腐蚀后

x3=x1.*x2;
figure,imshow(x3);%求两张腐蚀后图像的交集

七、实验结果与分析

1.去除噪声、空洞
在这里插入图片描述在这里插入图片描述
图 1 左:原图 右:处理后

2.击中与击不中
在这里插入图片描述在这里插入图片描述
图 2原图(左) 结构元素m(右,纯白色)

在这里插入图片描述在这里插入图片描述

图 3 原图补集(左) n结构元素
在这里插入图片描述在这里插入图片描述
图 4 m对原图腐蚀后(左) n对原图补集腐蚀后(右)

在这里插入图片描述
图 5两腐蚀图求交集

在这里插入图片描述

图 6 最终结果图(红色线圈出的位置就是我们需要找的位置)

八、实验总结及心得体会

    在这次实验中,自己学会了数字图像处理中的腐蚀、膨胀操作,其原理就是利用集合论的知识,对图像进行处理。这利用MATLAB编程的过程中,遇到了一些小困难,主要原因还是对一些函数的不熟悉。在击中与击不中这一块,感触颇多。这一块内容几乎用了半天的时间,才搞懂。这里暴露出的问题时原理理解的不是很深刻,加之MATLAB这里与课本上的例子有点出入,在这里饶了一点弯路。其实还好,在csdn上看别人写的文章,多看几遍,照着例子多看几遍,最后还是明白了。在击中与击不中实验中,我采用的方法是先自己创造出原图、两个结构元素,这样是为了更好的和课本一致,然后稍微改动了一些,感觉课本在讲这块有点错误,课本是从原理角度解释击中与击不中,在MATLAB中,我们则需要取反即可。

更多

获取更多资料、代码,微信公众号:海轰Pro
回复 海轰 即可

2019-06-08 20:35:03 qq_43660987 阅读数 72

1,图像形态学概念
形态学,即数学形态学(mathematical Morphology),,主要用于从图像中提取对表达和描绘区域形状有意义的图像分量,使后续的识别工作能够抓住目标对象最为本质〈最具区分能力-most discriminative)的形状特征,如边界和连通区域等。同时像细化、像素化和修剪毛刺等技术也常应用于图像的预处理和后处理中,成为图像增强技术的有力补充。
在这里插入图片描述

2,形态学图像处理操作
按照处理对象不同,分为对二值图像处理和对灰度图像处理,处理函数大部分都是相同的,除了击中或击不中变换(仅适用于二值图像)。
2.1 二值图像
(1)膨胀:
膨胀是图像中的目标“生长”或“变粗”的操作
(2)腐蚀:
腐蚀是图像中的目标“收缩”或“变细”的操作。
(3)开操作:
先腐蚀,再膨胀 。开操作可以去除所有不能包含结构元的部分,平滑目标的轮廓,断开了细的连接部分。
(4)闭操作:
先膨胀,再腐蚀 。闭操作可以平滑目标的轮廓,并且连接窄的断裂并填满细长的“港湾”,填满闭结构元小的洞。
(5)击中或击不中变换:
结构元是一对儿结构元,A被B1腐蚀 ∩ A的补集被B2腐蚀
(6)标记连通分量:
连通分量就是前景图像内部单个元素之间的关系,是通过连通得到的像素集合。连通分为4连通和8连通。
(8)形态学重建:
重建是一种形态学变换,包括一幅原图像G(模板),一幅标记图像F(标记),一个结构元B。其中F被包含于G。有一些重建和其他操作结合的方法并能取得很好的效果。

2.2 灰度图像
(1)膨胀和腐蚀:
从膨胀结果中减去腐蚀的结果,可产生形态学梯度,这是图像局部灰度变化的一种度量。
(2)开操作和闭操作:
开操作可以去除小的亮点细节,同时保留所有的灰度并保证较大的亮区特征不受干扰。闭操作去除比结构元校的黑暗结构。

两者组合可以平滑图像并去除噪声;交替顺序滤波;开操作可以用来补偿非均匀照明的背景(就是图像背景的亮度不一样,有暗有亮);顶帽操作,原图像减去图像开操作结果;底帽操作,原图像减去图像闭操作结果(这两个操作配合可以增强图像的对比度)
(3)重建:
抑制小的灰度峰值;用重建移去复杂的背景(结合腐蚀,重建的开操作,并选择合适的结构元)
3,总结:
膨胀和腐蚀常用于边界的提取。边界的提取是通过对目标图像进行腐蚀或膨胀处理,比较结果图像和原图像的差别来实现的。内边界的提取利用图像的腐蚀处理得到原图像的一个收缩,再将收缩结果和目标图像进行异或运算。外边界的提取对目标图像进行膨胀处理,然后利用膨胀结果与原目标图像进行异或运算。
开操作和闭操作:开运算用来图像进行消噪处理,同时,图像的开运算可以选择性地保留目标图像中符合结构原色几何性质的部分,而过滤掉相对结构元素而言残损的部分。闭运算用来对目标图像分开的区域进行连接及对图像中细小缝隙进行填补,通过适当地选择结构元素,图像的闭运算可以令图像的填补结果具有一点的几何特征,适当地对图像进行闭运算有时可以使图像变得更加清晰连贯,同时可以避免原图像中线条加粗。

3,结构元
结构元本质上和掩膜比较相似,也是0-1矩阵,不过进行的是集合运算而不是卷积运算。除了自己写结构元,也有特定的函数来产生结构元。
在这里插入图片描述

2016-04-11 16:57:43 swj110119 阅读数 3162

前几天一直在研究形态学在图像处理中的应用,查了很多资料。首先关于图像形态学的具体理论知识,课参考如下博客:
http://www.cnblogs.com/slysky/archive/2011/10/16/2214015.html
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.html

形态学操作及其公式小结:
这里写图片描述

上述操作是对二值图像进行操作的,当腐蚀、膨胀是最基本的操作,其他操作是基于这两个操作和集合论的组合。对于灰度图像来说,腐蚀和膨胀也是基本其他操作的基础,但是灰度图像腐蚀和膨胀不同于二值图像的腐蚀和膨胀。

灰度图像的腐蚀和膨胀定义如下:

(1)腐蚀:当结构元素b的原点位于(x,y)处时,用b对图像f进行腐蚀,是查找f中与结构元素b重合区域灰度级别最小的值,然后把最小的灰度值赋值给点(x,y):
这里写图片描述

(2)膨胀:当结构元素b的原点位于(x,y)处时,用b的反射(-b)对图像f进行腐蚀,是查找f中与结构元素b的反射重合区域灰度级别最大的值,然后把最大的灰度值赋值给点(x,y):
这里写图片描述

关于灰度图像的应用有:
(1)形态学梯度:
这里写图片描述

(2)顶帽变换:
这里写图片描述

(3)底帽变换:
这里写图片描述

具体代码如下:

#include<opencv.hpp>
#include<highgui.h>
#include<imgproc.hpp>
using namespace cv;

//把灰度图像转化为二值图像
Mat changeToBinaryImage(Mat grayImage)
{
    Mat binaryImage(grayImage.rows, grayImage.cols, CV_8UC1, Scalar(0));

    //转化为二值图像
    for (int i = 0; i < grayImage.rows; i++)
    {
        for (int j = 0; j < grayImage.cols; j++)
        {
            if (grayImage.data[i*grayImage.step + j]>100)
            {
                binaryImage.data[i*grayImage.step + j] = 255;
            }
            else
            {
                binaryImage.data[i*grayImage.step + j] = 0;
            }
        }
    }
    imshow("binaryImage", binaryImage);

    return binaryImage; 
}

//创建结构元素
//一般结构元素 关于原点对称
//Mat createSE()
//{
//  int a[3][3]={ 0,1,0,
//  1,1,1,
//  0,1,0};
//  Mat structureElement(3, 3, CV_8UC1, a);
//}

//二值图像腐蚀操作
Mat binaryErosion(Mat binaryImage, Mat se)
{
    //二值图像移动
    Mat window(se.rows, se.cols, CV_8UC1);

    //定义一个矩阵,存储腐蚀后的图像
    Mat binaryErosionImage(binaryImage.rows, binaryImage.cols, CV_8UC1, Scalar(0));

    for (int i = (se.rows-1)/2; i < binaryImage.rows-(se.rows-1)/2; i++)
    {
        for (int j = (se.cols - 1) / 2; j < binaryImage.cols - (se.cols - 1) / 2; j++)
        {
            //先设置第i行第j列像素值为255,即白色
            binaryErosionImage.data[i*binaryImage.step + j] = 255;
            for (int row = 0; row < se.rows; row++)
            {
                for (int col = 0; col < se.cols; col++)
                {
                    //把se对应的元素赋值到与se结构相同的矩阵中
                    window.data[row*window.step + col] = binaryImage.data[(i + row - (window.rows - 1) / 2)*binaryImage.step + (j + col - (window.cols - 1) / 2)];
                }
            }
            //比较se与window中的像素值
            int row, col;
            for (row = 0; row < se.rows; row++)
            {
                for (col = 0; col < se.cols; col++)
                {
                    if (se.data[row*se.step + col] != window.data[row*se.step + col])
                    {
                        break;
                    }
                }
                if (col == se.cols)
                {
                    continue;
                }
                else
                {
                    break;
                }
            }
            if (row == se.rows&&col == se.cols)
            {
                binaryErosionImage.data[i*binaryImage.step + j] = 0;
        }
        }
    }

    //imshow("binaryErosionImage", binaryErosionImage);

    return binaryErosionImage;
}

//二值图像膨胀操作
Mat binaryDilation(Mat binaryImage, Mat se)
{
    //二值图像移动
    Mat window(se.rows, se.cols, CV_8UC1);

    //定义一个矩阵,存储膨胀后的图像
    Mat binaryDilationImage(binaryImage.rows, binaryImage.cols, CV_8UC1, Scalar(0));

    for (int i = (se.rows - 1) / 2; i < binaryImage.rows - (se.rows - 1) / 2; i++)
    {
        for (int j = (se.cols - 1) / 2; j < binaryImage.cols - (se.cols - 1) / 2; j++)
        {
            //先设置第i行第j列像素值为255,即白色
            binaryDilationImage.data[i*binaryImage.step + j] = 255;
            for (int row = 0; row < se.rows; row++)
            {
                for (int col = 0; col < se.cols; col++)
                {
                    //把se对应的元素赋值到与se结构相同的矩阵中
                    window.data[row*window.step + col] = binaryImage.data[(i + row - (window.rows - 1) / 2)*binaryImage.step + (j + col - (window.cols - 1) / 2)];
                }
            }
            //比较se与window中的像素值
            //只要有一个相匹配 就把像素值设为0,即置黑
            int flag = 0;  //标记是否有对应相等的像素值:0表示没有,1表示有
            int row, col;
            for (row = 0; row < se.rows; row++)
            {
                for (col = 0; col < se.cols; col++)
                {
                    if (se.data[row*se.step + col] == window.data[row*se.step + col])
                    {
                        flag = 1;
                        break;
                    }
                }
                if (flag)
                {
                    break;
                }
            }
            if (flag)
            {
                //如果有交集,就设置为黑,即0
                binaryDilationImage.data[i*binaryImage.step + j] = 0;
            }
        }
    }

    //imshow("binaryDilationImage", binaryDilationImage);
    return binaryDilationImage;
}

//灰度图像腐蚀操作
Mat grayErosion(Mat grayImage,Mat se)
{
    //结构元素移动时所对应的源图像区域
    Mat window(se.rows, se.cols, CV_8UC1);

    //定义一个矩阵,存储腐蚀后的图像
    Mat grayErosionImage(grayImage.rows, grayImage.cols, CV_8UC1, Scalar(0));

    for (int i = (se.rows - 1) / 2; i < grayImage.rows - (se.rows - 1) / 2; i++)
    {
        for (int j = (se.cols - 1) / 2; j < grayImage.cols - (se.cols - 1) / 2; j++)
        {
            //先设置第i行第j列像素值为255,即白色
            grayErosionImage.data[i*grayImage.step + j] = 255;
            for (int row = 0; row < se.rows; row++)
            {
                for (int col = 0; col < se.cols; col++)
                {
                    //把se对应的元素赋值到与se结构相同的矩阵window中
                    window.data[row*window.step + col] = grayImage.data[(i + row - (window.rows - 1) / 2)*grayImage.step + (j + col - (window.cols - 1) / 2)];
                }
            }
            //比较se与window中的像素值
            //在灰度图像中,腐蚀是取window中最小的值赋值给原点所对用的像素
            int minPixel = 255;
            int row, col;
            for (row = 0; row < se.rows; row++)
            {
                for (col = 0; col < se.cols; col++)
                {
                    if (window.data[row*se.step + col] < minPixel)
                    {
                        minPixel = window.data[row*se.step + col];
                    }
                }   
            }   
            grayErosionImage.data[i*grayImage.step + j] = minPixel;
        }
    }

    /*imshow("grayErosionImage", grayErosionImage);*/

    return grayErosionImage;
}

//灰度图像膨胀操作
Mat grayDilation(Mat grayImage,Mat se)
{
    //结构元素移动时所对应的源图像区域
    Mat window(se.rows, se.cols, CV_8UC1);

    //定义一个矩阵,存储腐蚀后的图像
    Mat grayDilationImage(grayImage.rows, grayImage.cols, CV_8UC1, Scalar(0));

    for (int i = (se.rows - 1) / 2; i < grayImage.rows - (se.rows - 1) / 2; i++)
    {
        for (int j = (se.cols - 1) / 2; j < grayImage.cols - (se.cols - 1) / 2; j++)
        {
            //先设置第i行第j列像素值为255,即白色
            grayDilationImage.data[i*grayImage.step + j] = 255;
            for (int row = 0; row < se.rows; row++)
            {
                for (int col = 0; col < se.cols; col++)
                {
                    //把se对应的元素赋值到与se结构相同的矩阵window中
                    window.data[row*window.step + col] = grayImage.data[(i + row - (window.rows - 1) / 2)*grayImage.step + (j + col - (window.cols - 1) / 2)];
                }
            }
            //比较se与window中的像素值
            //在灰度图像中,膨胀是取window中最大的值赋值给原点所对用的像素
            int maxPixel = 0;
            int row, col;
            for (row = 0; row < se.rows; row++)
            {
                for (col = 0; col < se.cols; col++)
                {
                    if (window.data[row*se.step + col] > maxPixel)
                    {
                        maxPixel = window.data[row*se.step + col];
                    }
                }
            }
            grayDilationImage.data[i*grayImage.step + j] = maxPixel;
        }
    }

    /*imshow("grayDilationImage", grayDilationImage);*/

    return grayDilationImage;
}

//二值图像开操作
Mat binaryOpen(Mat binaryImage, Mat se)
{
    Mat openImage(binaryImage.rows,binaryImage.cols,CV_8UC1,Scalar(0));

    openImage = binaryDilation(binaryErosion(binaryImage, se), se);

    return openImage;
}

//二值图像闭操作
Mat binaryClose(Mat binaryImage, Mat se)
{
    Mat closeImage(binaryImage.rows, binaryImage.cols, CV_8UC1, Scalar(0));

    closeImage = binaryErosion(binaryDilation(binaryImage, se), se);

    return closeImage;
}

//灰度图像开操作
Mat grayOpen(Mat grayImage, Mat se)
{
    Mat openImage(grayImage.rows, grayImage.cols, CV_8UC1, Scalar(0));

    openImage = grayDilation(grayErosion(grayImage, se), se);

    return openImage;
}

//灰度图像闭操作
Mat grayClose(Mat grayImage, Mat se)
{
    Mat closeImage(grayImage.rows, grayImage.cols, CV_8UC1, Scalar(0));

    closeImage = grayErosion(grayDilation(grayImage, se), se);

    return closeImage;
}

//二值图像边界提取
Mat binaryBorder(Mat binaryImage,Mat se)
{
    Mat borderImage(binaryImage.rows, binaryImage.cols, CV_8UC1, Scalar(0));
    Mat erosionImage(binaryImage.rows, binaryImage.cols, CV_8UC1, Scalar(0));
    erosionImage = binaryErosion(binaryImage,se);

    for (int i = 0; i < erosionImage.rows; i++)
    {
        for (int j = 0; j < erosionImage.cols; j++)
        {
            if (binaryImage.data[i*erosionImage.step+j]!=erosionImage.data[i*erosionImage.step+j])
            {
                borderImage.data[i*erosionImage.step + j] = 255;
            }
        }
    }

    return borderImage;
}

//灰度图像边界提取
Mat grayBorder(Mat grayImage, Mat se)
{
    Mat borderImage(grayImage.rows, grayImage.cols, CV_8UC1, Scalar(0));

    borderImage = grayImage - grayErosion(grayImage, se);

    return borderImage;
}

//灰度图像梯度
Mat gradient(Mat grayImage, Mat se)
{
    Mat gradient(grayImage.rows, grayImage.cols, CV_8UC1, Scalar(0));

    gradient = grayDilation(grayImage, se) - grayErosion(grayImage, se);

    return gradient;
}

//灰度图像的顶帽运算 T(f)=f-fob
Mat topHat(Mat grayImage,Mat se)
{
    Mat topHatImage(grayImage.rows, grayImage.cols, CV_8UC1, Scalar(0));

    topHatImage = grayImage - grayOpen(grayImage,se);

    return topHatImage;
}

//灰度图像的底帽运算 B(f)=f⋅b-f
Mat bottomHat(Mat grayImage, Mat se)
{
    Mat bottomHatImage(grayImage.rows, grayImage.cols, CV_8UC1, Scalar(0));

    bottomHatImage = grayClose(grayImage, se)-grayImage;

    return bottomHatImage;
}


int main()
{
    Mat src = imread("E:\\project\\images\\32.jpg");

    Mat grayImage(src.rows, src.cols, CV_8UC1);
    //转化为灰度图像
    cvtColor(src, grayImage, CV_BGR2GRAY);

    imshow("original Image",src);
    imshow("gray Image", grayImage);

    //转化为二值图像
    Mat binaryImage = changeToBinaryImage(grayImage);

    //创建模板  一般结构元素关于自身原点对称  
    //也可以自定义结构元素  下面的变量是3*3的矩阵 全部为0  
    Mat structureElement(3, 3, CV_8UC1, Scalar(0));

    //调用二值图像腐蚀函数
    //binaryErosion(binaryImage, structureElement);
    imshow("binaryErosionImage", binaryErosion(binaryImage, structureElement));

    //调用二值图像膨胀函数
    //binaryDilation(binaryImage, structureElement);
    imshow("binaryDilationImage", binaryDilation(binaryImage, structureElement));

    //调用灰度图像腐蚀函数
    //grayErosion(grayImage, structureElement);
    imshow("grayErosionImage", grayErosion(grayImage, structureElement));

    //调用灰度图像膨胀函数
    //grayDilation(grayImage, structureElement);
    imshow("grayDilationImage", grayDilation(grayImage, structureElement));

    //调用二值图像开操作
    imshow("binaryOpenImage",binaryOpen(binaryImage,structureElement));

    //调用二值图像闭操作
    imshow("binaryCloseImage", binaryClose(binaryImage, structureElement));

    //调用灰度图像开操作
    imshow("grayOpenImage", grayOpen(grayImage, structureElement));

    //调用灰度图像闭操作
    imshow("grayCloseImage", grayClose(grayImage, structureElement));

    //二值图像边界提取
    imshow("binaryBorderImage",binaryBorder(binaryImage,structureElement));
    //灰度图像边界提取
    imshow("grayBorderImage",grayBorder(grayImage,structureElement));

    //调用灰度梯度函数
    imshow("Gradient", gradient(binaryImage, structureElement));

    //调用顶帽函数
    imshow("topHat",topHat(grayImage,structureElement));

    //调用底帽函数
    imshow("bottomHat", bottomHat(grayImage, structureElement));

    cvWaitKey(0);

    return 0;
}

形态学处理

阅读数 109

没有更多推荐了,返回首页