Skip to content

更多形态学变换

在本教程中,你将学到:

  • 如何使用 OpenCV 函数 cv::morphologyEx 来应用形态学变换(Morphological Transformation),例如:
    • 开运算(Opening)
    • 闭运算(Closing)
    • 形态学梯度(Morphological Gradient)
    • 顶帽(Top Hat)
    • 黑帽(Black Hat)

在上一篇教程中,我们介绍了两种基本的形态学操作:

  • 腐蚀(Erosion)
  • 膨胀(Dilation)

基于这两种操作,我们可以对图像执行更复杂的变换。下面我们简要讨论 OpenCV 提供的 5 种操作:

  • 它通过对图像先腐蚀、再膨胀得到:

    dst=open(src,element)=dilate(erode(src,element))dst = open( src, element) = dilate( erode( src, element ) )

  • 可用于移除小的物体(假设物体在暗背景上是亮的)。

  • 例如,看看下面的例子。左图是原图,右图是应用开运算后的结果。可以观察到小点已经消失了。

Morphology_2_Tutorial_Theory_Opening.png

  • 它通过对图像先膨胀、再腐蚀得到:

    dst=close(src,element)=erode(dilate(src,element))dst = close( src, element ) = erode( dilate( src, element ) )

  • 可用于移除小孔(暗区域)。

Morphology_2_Tutorial_Theory_Closing.png

  • 它是图像膨胀与腐蚀之间的差:

    dst=morphgrad(src,element)=dilate(src,element)−erode(src,element)dst = morph_{grad}( src, element ) = dilate( src, element ) - erode( src, element )

  • 可用于寻找物体的轮廓。

Morphology_2_Tutorial_Theory_Gradient.png

  • 它是输入图像与其开运算结果之间的差:

    dst=tophat(src,element)=src−open(src,element)dst = tophat( src, element ) = src - open( src, element )

Morphology_2_Tutorial_Theory_TopHat.png

  • 它是输入图像的闭运算结果与输入图像之间的差:

    dst=blackhat(src,element)=close(src,element)−srcdst = blackhat( src, element ) = close( src, element ) - src

C++:

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

/**
* @file Morphology_2.cpp
* @brief Advanced morphology Transformations sample code
* @author OpenCV team
*/
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
/// Global variables
Mat src, dst;
int morph_elem = 0;
int morph_size = 0;
int morph_operator = 0;
int const max_operator = 4;
int const max_elem = 3;
int const max_kernel_size = 21;
const char* window_name = "Morphology Transformations Demo";
/** Function Headers */
void Morphology_Operations( int, void* );
/**
* @function main
*/
int main( int argc, char** argv )
{
CommandLineParser parser( argc, argv, "{@input | baboon.jpg | input image}" );
src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR );
if (src.empty())
{
std::cout << "Could not open or find the image!\n" << std::endl;
std::cout << "Usage: " << argv[0] << " <Input image>" << std::endl;
return EXIT_FAILURE;
}
namedWindow( window_name, WINDOW_AUTOSIZE ); // Create window
/// Create Trackbar to select Morphology operation
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 );
/// Create Trackbar to select kernel type
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse - 3: Diamond", window_name,
&morph_elem, max_elem,
Morphology_Operations );
/// Create Trackbar to choose kernel size
createTrackbar( "Kernel size:\n 2n +1", window_name,
&morph_size, max_kernel_size,
Morphology_Operations );
/// Default start
Morphology_Operations( 0, 0 );
waitKey(0);
return 0;
}
/**
* @function 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 ) );
/// Apply the specified morphology operation
morphologyEx( src, dst, operation, element );
imshow( window_name, dst );
}

Java:

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

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Size;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
public class MorphologyDemo2 {
private static final String[] MORPH_OP = { "Opening", "Closing", "Gradient", "Top Hat", "Black Hat" };
private static final int[] MORPH_OP_TYPE = { Imgproc.MORPH_OPEN, Imgproc.MORPH_CLOSE,
Imgproc.MORPH_GRADIENT, Imgproc.MORPH_TOPHAT, Imgproc.MORPH_BLACKHAT };
private static final String[] ELEMENT_TYPE = { "Rectangle", "Cross", "Ellipse" };
private static final int MAX_KERNEL_SIZE = 21;
private Mat matImgSrc;
private Mat matImgDst = new Mat();
private int morphOpType = Imgproc.MORPH_OPEN;
private int elementType = Imgproc.CV_SHAPE_RECT;
private int kernelSize = 0;
private JFrame frame;
private JLabel imgLabel;
public MorphologyDemo2(String[] args) {
String imagePath = args.length > 0 ? args[0] : "../data/LinuxLogo.jpg";
matImgSrc = Imgcodecs.imread(imagePath);
if (matImgSrc.empty()) {
System.out.println("Empty image: " + imagePath);
System.exit(0);
}
// Create and set up the window.
frame = new JFrame("Morphology Transformations demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Set up the content pane.
Image img = HighGui.toBufferedImage(matImgSrc);
addComponentsToPane(frame.getContentPane(), img);
// Use the content pane's default BorderLayout. No need for
// setLayout(new BorderLayout());
// Display the window.
frame.pack();
frame.setVisible(true);
}
private void addComponentsToPane(Container pane, Image img) {
if (!(pane.getLayout() instanceof BorderLayout)) {
pane.add(new JLabel("Container doesn't use BorderLayout!"));
return;
}
JPanel sliderPanel = new JPanel();
sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.PAGE_AXIS));
JComboBox<String> morphOpBox = new JComboBox<>(MORPH_OP);
morphOpBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
@SuppressWarnings("unchecked")
JComboBox<String> cb = (JComboBox<String>)e.getSource();
morphOpType = MORPH_OP_TYPE[cb.getSelectedIndex()];
update();
}
});
sliderPanel.add(morphOpBox);
JComboBox<String> elementTypeBox = new JComboBox<>(ELEMENT_TYPE);
elementTypeBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
@SuppressWarnings("unchecked")
JComboBox<String> cb = (JComboBox<String>)e.getSource();
if (cb.getSelectedIndex() == 0) {
elementType = Imgproc.CV_SHAPE_RECT;
} else if (cb.getSelectedIndex() == 1) {
elementType = Imgproc.CV_SHAPE_CROSS;
} else if (cb.getSelectedIndex() == 2) {
elementType = Imgproc.CV_SHAPE_ELLIPSE;
}
update();
}
});
sliderPanel.add(elementTypeBox);
sliderPanel.add(new JLabel("Kernel size: 2n + 1"));
JSlider slider = new JSlider(0, MAX_KERNEL_SIZE, 0);
slider.setMajorTickSpacing(5);
slider.setMinorTickSpacing(5);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
kernelSize = source.getValue();
update();
}
});
sliderPanel.add(slider);
pane.add(sliderPanel, BorderLayout.PAGE_START);
imgLabel = new JLabel(new ImageIcon(img));
pane.add(imgLabel, BorderLayout.CENTER);
}
private void update() {
Mat element = Imgproc.getStructuringElement(elementType, new Size(2 * kernelSize + 1, 2 * kernelSize + 1),
new Point(kernelSize, kernelSize));
Imgproc.morphologyEx(matImgSrc, matImgDst, morphOpType, element);
Image img = HighGui.toBufferedImage(matImgDst);
imgLabel.setIcon(new ImageIcon(img));
frame.repaint();
}
public static void main(String[] args) {
// Load the native OpenCV library
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// Schedule a job for the event dispatch thread:
// creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new MorphologyDemo2(args);
}
});
}
}

Python:

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

from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
morph_size = 0
max_operator = 4
max_elem = 3
max_kernel_size = 21
title_trackbar_operator_type = 'Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat'
title_trackbar_element_type = 'Element:\n 0: Rect - 1: Cross - 2: Ellipse - 3: Diamond'
title_trackbar_kernel_size = 'Kernel size:\n 2n + 1'
title_window = 'Morphology Transformations Demo'
morph_op_dic = {0: cv.MORPH_OPEN, 1: cv.MORPH_CLOSE, 2: cv.MORPH_GRADIENT, 3: cv.MORPH_TOPHAT, 4: cv.MORPH_BLACKHAT}
def morphology_operations(val):
morph_operator = cv.getTrackbarPos(title_trackbar_operator_type, title_window)
morph_size = cv.getTrackbarPos(title_trackbar_kernel_size, title_window)
morph_elem = 0
val_type = cv.getTrackbarPos(title_trackbar_element_type, title_window)
if val_type == 0:
morph_elem = cv.MORPH_RECT
elif val_type == 1:
morph_elem = cv.MORPH_CROSS
elif val_type == 2:
morph_elem = cv.MORPH_ELLIPSE
elif val_type == 3:
morph_elem = cv.MORPH_DIAMOND
element = cv.getStructuringElement(morph_elem, (2*morph_size + 1, 2*morph_size+1), (morph_size, morph_size))
operation = morph_op_dic[morph_operator]
dst = cv.morphologyEx(src, operation, element)
cv.imshow(title_window, dst)
parser = argparse.ArgumentParser(description='Code for More Morphology Transformations tutorial.')
parser.add_argument('--input', help='Path to input image.', default='LinuxLogo.jpg')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
print('Could not open or find the image: ', args.input)
exit(0)
cv.namedWindow(title_window)
cv.createTrackbar(title_trackbar_operator_type, title_window , 0, max_operator, morphology_operations)
cv.createTrackbar(title_trackbar_element_type, title_window , 0, max_elem, morphology_operations)
cv.createTrackbar(title_trackbar_kernel_size, title_window , 0, max_kernel_size, morphology_operations)
morphology_operations(0)
cv.waitKey()

Morphology_2_Tutorial_Theory_BlackHat.png

  1. 我们先来看看 C++ 程序的整体结构:
    • 加载一幅图像;

    • 创建一个窗口,用于显示形态学操作的结果;

    • 创建三个 Trackbar,供用户输入参数:

      • 第一个 Trackbar Operator 返回要使用的形态学操作类型(morph_operator):
      /// Create Trackbar to select Morphology operation
      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 );
      • 第二个 Trackbar Element 返回 morph_elem,表示核的结构类型:
      /// Create Trackbar to select kernel type
      createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse - 3: Diamond", window_name,
      &morph_elem, max_elem,
      Morphology_Operations );
      • 最后一个 Trackbar Kernel Size 返回要使用的核的大小(morph_size):
      /// Create Trackbar to choose kernel size
      createTrackbar( "Kernel size:\n 2n +1", window_name,
      &morph_size, max_kernel_size,
      Morphology_Operations );
    • 每当我们移动任意滑块时,用户函数 Morphology_Operations 就会被调用,它会执行一次新的形态学操作,并根据当前 Trackbar 的值更新输出图像:

      /**
      * @function 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 ) );
      /// Apply the specified morphology operation
      morphologyEx( src, dst, operation, element );
      imshow( window_name, dst );
      }

      我们可以看到,执行形态学变换的关键函数是 cv::morphologyEx。在本例中我们使用了四个参数(其余保持默认值):

      • src:源(输入)图像;

      • dst:输出图像;

      • operation:要执行的形态学变换类型。注意我们有 5 种选择:

        • Opening:MORPH_OPEN:2
        • Closing:MORPH_CLOSE:3
        • Gradient:MORPH_GRADIENT:4
        • Top Hat:MORPH_TOPHAT:5
        • Black Hat:MORPH_BLACKHAT:6

        如你所见,这些值的范围是 2~6,这就是为什么我们要给 Trackbar 输入的值加上(+2):

        int operation = morph_operator + 2;

Morphology_2_Tutorial_Original_Image.jpg Morphology_2_Tutorial_Result.jpg

- **element**:要使用的核。我们使用函数 cv::getStructuringElement 来定义自己的结构。
  • 编译上面的代码后,我们可以传入一幅图像的路径作为参数来运行它。使用图像 baboon.png 的结果如下:

  • 下面是显示窗口的两张截图。第一张图展示了使用开运算(Opening)配合十字核的输出。第二张图展示了使用黑帽(Blackhat)算子配合椭圆核的结果。