图像处理:基础——更多形态变换 OpenCV v4.8.0

上一个教程腐蚀和膨胀

下一个教程Hit-or-Miss

原作者Ana Huamán
兼容性OpenCV >= 3.0

目标

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

  • 使用 OpenCV 函数 cv::morphologyEx 来应用形态变换,例如:
    • 开运算
    • 闭运算
    • 形态渐变
    • 顶帽
    • 黑帽

理论

注释
以下解释来自 Bradski 和 Kaehler 合著的《学习 OpenCV》一书。

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

  • 腐蚀
  • 膨胀
    在这两种操作的基础上,我们可以对图像进行更复杂的变换。下面我们将简要讨论 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=open\left( src,element \right) =dilate\left( erode\left( src,element \right) \right) dst=open(src,element)=dilate(erode(src,element))

  • 适用于去除小物体(假定物体在暗前景上是亮的)
  • 例如,请看下面的例子。左边的图像是原始图像,右边的图像是应用开场变换后的结果。我们可以看到,小圆点已经消失了。

在这里插入图片描述

闭运算

  • 它是通过对图像进行膨胀后再进行腐蚀得到的。

d s t = c l o s e ( s r c , e l e m e n t ) = e r o d e ( d i l a t e ( s r c , e l e m e n t ) ) dst=close\left( src,element \right) =erode\left( dilate\left( src,element \right) \right) dst=close(src,element)=erode(dilate(src,element))

  • 用于去除小洞(暗区)。

Morphology_2_Tutorial_Theory_Closing.png

形态梯度

  • 它是图像扩张和侵蚀之间的差值。

d s t = m o r p h g r a d ( s r c , e l e m e n t ) = d i l a t e ( s r c , e l e m e n t ) − e r o d e ( s r c , e l e m e n t ) dst=morph_{grad}\left( src,element \right) =dilate\left( src,element \right) -erode\left( src,element \right) dst=morphgrad(src,element)=dilate(src,element)erode(src,element)

  • 如下图所示,它在查找物体轮廓时非常有用:

在这里插入图片描述

顶帽

  • 它是输入图像与其开运算图像之间的差值。

d s t = t o p h a t ( s r c , e l e m e n t ) = s r c − o p e n ( s r c , e l e m e n t ) dst=tophat\left( src,element \right) =src-open\left( src,element \right) dst=tophat(src,element)=srcopen(src,element)

在这里插入图片描述

黑帽

  • 是指闭运算图像与输入图像之间的差异

d s t = b l a c k h a t ( s r c , e l e m e n t ) = c l o s e ( s r c , e l e m e n t ) − s r c dst=blackhat\left( src,element \right) =close\left( src,element \right) -src dst=blackhat(src,element)=close(src,element)src

在这里插入图片描述

代码

C++
本教程的代码如下所示。您也可以在此处下载


#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
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 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
 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 );
}

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);
 }
 // 创建并设置窗口。
 frame = new JFrame("Morphology Transformations Demo");
 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)// 设置内容窗格。
 图像 img = HighGui.toBufferedImage(matImgSrc)addComponentsToPane(frame.getContentPane(), img)// 使用内容窗格的默认边框布局。无需
 // setLayout(new BorderLayout());
 // 显示窗口。
 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) {
 // 加载本地 OpenCV 库
 System.loadLibrary(Core.NATIVE_LIBRARY_NAME)// 为事件派发线程安排任务:
 // 创建并显示此应用程序的图形用户界面。
 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 = 2
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'
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
 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()

说明

  1. 让我们来看看 C++ 程序的总体结构:
    • 加载图像
    • 创建一个窗口来显示形态学操作的结果
    • 创建三个轨迹条,供用户输入参数:
      • 第一个轨迹条 Operator 返回要使用的形态学操作类型(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 );
  • 第二个跟踪条 Element 返回 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: 输出图像

  • 操作: 要执行的形态变换类型。请注意,我们有 5 种选择:

    • 开运算 : MORPH_OPEN : 2
    • 闭运算 : MORPH_CLOSE : 3
    • 渐变 : morph_gradient: 4
    • 顶帽 : MORPH_TOPHAT: 5
    • 黑帽 : MORPH_BLACKHAT: 6

正如您所看到的,数值范围为 <2-6>,这就是我们在 Trackbar 输入的数值上添加 (+2) 的原因:

 int operation = morph_operator + 2;

元素: 要使用的内核。我们使用函数 cv::getStructuringElement 来定义我们自己的结构。

结果

编译完上面的代码后,我们可以将图像路径作为参数来执行它。使用图片 baboon.png 得出的结果:

在这里插入图片描述

下面是两张显示窗口的快照。第一张图片显示的是使用运算符 Opening 和交叉内核后的输出结果。第二张图片(右侧)显示的是使用椭圆内核的黑帽运算符后的结果。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值