更多形态学变换
在本教程中,你将学到:
- 如何使用 OpenCV 函数 cv::morphologyEx 来应用形态学变换(Morphological Transformation),例如:
- 开运算(Opening)
- 闭运算(Closing)
- 形态学梯度(Morphological Gradient)
- 顶帽(Top Hat)
- 黑帽(Black Hat)
在上一篇教程中,我们介绍了两种基本的形态学操作:
- 腐蚀(Erosion)
- 膨胀(Dilation)
基于这两种操作,我们可以对图像执行更复杂的变换。下面我们简要讨论 OpenCV 提供的 5 种操作:
-
它通过对图像先腐蚀、再膨胀得到:
-
可用于移除小的物体(假设物体在暗背景上是亮的)。
-
例如,看看下面的例子。左图是原图,右图是应用开运算后的结果。可以观察到小点已经消失了。

-
它通过对图像先膨胀、再腐蚀得到:
-
可用于移除小孔(暗区域)。

-
它是图像膨胀与腐蚀之间的差:
-
可用于寻找物体的轮廓。

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

-
它是输入图像的闭运算结果与输入图像之间的差:
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 variablesMat 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_functionimport cv2 as cvimport numpy as npimport argparse
morph_size = 0max_operator = 4max_elem = 3max_kernel_size = 21title_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()
- 我们先来看看 C++ 程序的整体结构:
-
加载一幅图像;
-
创建一个窗口,用于显示形态学操作的结果;
-
创建三个 Trackbar,供用户输入参数:
- 第一个 Trackbar Operator 返回要使用的形态学操作类型(morph_operator):
/// Create Trackbar to select Morphology operationcreateTrackbar("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 typecreateTrackbar( "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 sizecreateTrackbar( "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 6int 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 operationmorphologyEx( 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;
-
-

- **element**:要使用的核。我们使用函数 cv::getStructuringElement 来定义自己的结构。-
编译上面的代码后,我们可以传入一幅图像的路径作为参数来运行它。使用图像 baboon.png 的结果如下:
-
下面是显示窗口的两张截图。第一张图展示了使用开运算(Opening)配合十字核的输出。第二张图展示了使用黑帽(Blackhat)算子配合椭圆核的结果。