学习Qt_OpenCV形态学开运算、闭运算、形态学梯度、顶帽、黑帽及OpenCV实现

原文:https://blog.csdn.net/qingyang8513/article/details/80675872

形态学运算


2.1 开运算
    腐蚀与膨胀作为形态学的基本操作,经过组合后可以很容易的实现更高一级的形态学运算。开运算即是先腐蚀后膨胀的得到的结果,即dst = open(src, element) = dilate(erode(src, element))。开运算可以消除局部小的白色杂点,在纤细点处分离物体,并且在平滑较大物体的边界的同时不明显改变其面积

2.2 闭运算
    闭运算过程与开运算过程相反,其是先膨胀后腐蚀的过程,可以消除图像中的小黑点,dst = close(src, element) = erode(dilate(src, element))。。

2.3 形态学梯度
    形态学梯度是膨胀图与腐蚀图之差,其数学表达式为:dst = morph_grad(src, element) = dilate(src, element) - erode(src, element)。对二值图像进行形态学梯度运算,可以突出物体边缘。通常,我们采用形态学梯度来保留物体的边缘轮廓

2.4 顶帽
    顶帽运算又常称为礼帽运算,是原始图像与图像开运算结果之差,其数学表达式为:dst = tophat(src, element) = src - open(src, element)。顶帽运算通常用来分离比邻近点亮一些的斑块,在一幅图像具有大幅背景且微小细节比较有规律的情况下,顶帽运算可以用来进行背景提取

2.5 黑帽
    黑帽运算是图像闭运算结果与原始图像之差,其数学表达式为:dst = tophat(src, element) = close(src, element) - src。黑帽运算通常用来分离比邻近点暗一些的斑块,可以突出比原始图轮廓周围区域更暗的区域

OpenCV实现

    cv::morphologyEx()函数综合形态学大部分运算,可以实现形态学腐蚀、膨胀、开运算、闭运算、形态学梯度、顶帽、黑帽等。cv::morphologyEx()声明如下:

void cv::morphologyEx ( InputArray  src,
    OutputArray  dst,
    int  op,
    InputArray  kernel,
    Point  anchor = Point(-1,-1),
    int  iterations = 1,
    int  borderType = BORDER_CONSTANT,
    const Scalar &  borderValue = morphologyDefaultBorderValue() 
  )  

#include <opencv2/imgproc.hpp>

执行高级形态转换。

函数cv :: morphologyEx可以使用侵蚀和膨胀作为基本操作来执行高级形态学变换。

任何操作都可以就地完成。在多通道图像的情况下,每个通道被独立处理。

参数

SRC 来源图片。通道数可以是任意的。深度应为CV_8U,CV_16U,CV_16S,CV_32F或CV_64F之一。
DST 与源图像大小和类型相同的目标图像。
op 形态学操作的类型,请参见变形类型
核心 结构元素。它可以使用getStructuringElement创建。
用内核锚定位置。负值意味着锚位于核心中心。
迭代 侵蚀和扩张的次数。
borderType 像素外推方法,请参阅BorderTypes
borderValue 边界不变的边界值。默认值具有特殊含义。

   其中,参数op用于选择形态学运算类型,这里用到的有下面几个,参考函数定义:

    1)MORPH_OPEN:开运算

    2)MORPH_CLOSE:闭运算

    3)MORPH_GRADIENT:形态学梯度

    4)MORPH_TOPHAT:顶帽

    5)MORPH_BLACKHAP:黑帽

    6)MORPH_ERODE:腐蚀

    7)MORPH_DILATE:膨胀

static bool ocl_morphologyEx(InputArray _src, OutputArray _dst, int op,
                             InputArray kernel, Point anchor, int iterations,
                             int borderType, const Scalar& borderValue)
{
    _dst.createSameSize(_src, _src.type());
    bool submat = _dst.isSubmatrix();
    UMat temp;
    _OutputArray _temp = submat ? _dst : _OutputArray(temp);
 
    switch( op )
    {
    case MORPH_ERODE:
        if (!ocl_morphOp( _src, _dst, kernel, anchor, iterations, MORPH_ERODE, borderType, borderValue ))
            return false;
        break;
    case MORPH_DILATE:
        if (!ocl_morphOp( _src, _dst, kernel, anchor, iterations, MORPH_DILATE, borderType, borderValue ))
            return false;
        break;
    case MORPH_OPEN:
        if (!ocl_morphOp( _src, _temp, kernel, anchor, iterations, MORPH_ERODE, borderType, borderValue ))
            return false;
        if (!ocl_morphOp( _temp, _dst, kernel, anchor, iterations, MORPH_DILATE, borderType, borderValue ))
            return false;
        break;
    case MORPH_CLOSE:
        if (!ocl_morphOp( _src, _temp, kernel, anchor, iterations, MORPH_DILATE, borderType, borderValue ))
            return false;
        if (!ocl_morphOp( _temp, _dst, kernel, anchor, iterations, MORPH_ERODE, borderType, borderValue ))
            return false;
        break;
    case MORPH_GRADIENT:
        if (!ocl_morphOp( _src, temp, kernel, anchor, iterations, MORPH_ERODE, borderType, borderValue ))
            return false;
        if (!ocl_morphOp( _src, _dst, kernel, anchor, iterations, MORPH_DILATE, borderType, borderValue, MORPH_GRADIENT, temp ))
            return false;
        break;
    case MORPH_TOPHAT:
        if (!ocl_morphOp( _src, _temp, kernel, anchor, iterations, MORPH_ERODE, borderType, borderValue ))
            return false;
        if (!ocl_morphOp( _temp, _dst, kernel, anchor, iterations, MORPH_DILATE, borderType, borderValue, MORPH_TOPHAT, _src ))
            return false;
        break;
    case MORPH_BLACKHAT:
        if (!ocl_morphOp( _src, _temp, kernel, anchor, iterations, MORPH_DILATE, borderType, borderValue ))
            return false;
        if (!ocl_morphOp( _temp, _dst, kernel, anchor, iterations, MORPH_ERODE, borderType, borderValue, MORPH_BLACKHAT, _src ))
            return false;
        break;
    default:
        CV_Error( CV_StsBadArg, "unknown morphological operation" );
    }
 
    return true;
}

综合示例及演示

1 界面设计

与005完全相同.mainwindow.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>655</width>
    <height>416</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <widget class="QLabel" name="label">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>20</y>
      <width>54</width>
      <height>12</height>
     </rect>
    </property>
    <property name="text">
     <string>原始图像:</string>
    </property>
   </widget>
   <widget class="QLabel" name="label_2">
    <property name="geometry">
     <rect>
      <x>360</x>
      <y>20</y>
      <width>54</width>
      <height>12</height>
     </rect>
    </property>
    <property name="text">
     <string>运行效果:</string>
    </property>
   </widget>
   <widget class="QLabel" name="label_OriginalImg">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>70</y>
      <width>261</width>
      <height>191</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">border:1px solid black</string>
    </property>
    <property name="text">
     <string>Original  Image</string>
    </property>
   </widget>
   <widget class="QLabel" name="label_ProcessedImg">
    <property name="geometry">
     <rect>
      <x>360</x>
      <y>70</y>
      <width>261</width>
      <height>191</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">border:1px solid black</string>
    </property>
    <property name="text">
     <string>Processed  Image</string>
    </property>
   </widget>
   <widget class="QLabel" name="label_5">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>320</y>
      <width>54</width>
      <height>12</height>
     </rect>
    </property>
    <property name="text">
     <string>核大小:</string>
    </property>
   </widget>
   <widget class="QLabel" name="label_KernelValue">
    <property name="geometry">
     <rect>
      <x>120</x>
      <y>320</y>
      <width>21</width>
      <height>20</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">background-color: rgb(255, 85, 127);</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="alignment">
     <set>Qt::AlignCenter</set>
    </property>
   </widget>
   <widget class="QSlider" name="horizontalSlider_KernelValue">
    <property name="geometry">
     <rect>
      <x>160</x>
      <y>320</y>
      <width>221</width>
      <height>20</height>
     </rect>
    </property>
    <property name="orientation">
     <enum>Qt::Horizontal</enum>
    </property>
   </widget>
   <widget class="QPushButton" name="pushButton_OpenImg">
    <property name="geometry">
     <rect>
      <x>550</x>
      <y>320</y>
      <width>75</width>
      <height>23</height>
     </rect>
    </property>
    <property name="text">
     <string>打开图像</string>
    </property>
   </widget>
   <widget class="QComboBox" name="comboBox_Type">
    <property name="geometry">
     <rect>
      <x>468</x>
      <y>321</y>
      <width>71</width>
      <height>21</height>
     </rect>
    </property>
   </widget>
   <widget class="QLabel" name="label_3">
    <property name="geometry">
     <rect>
      <x>400</x>
      <y>320</y>
      <width>61</width>
      <height>16</height>
     </rect>
    </property>
    <property name="text">
     <string>操作类型:</string>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>655</width>
     <height>23</height>
    </rect>
   </property>
  </widget>
  <widget class="QToolBar" name="mainToolBar">
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QMainWindow>
 
#include "opencv2/opencv.hpp"
using namespace cv;
 
namespace Ui {
class MainWindow;
}
 
class MainWindow : public QMainWindow
{
    Q_OBJECT
 
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
 
private:
    Ui::MainWindow *ui;
 
    int m_KernelValue;
    bool m_isOpenFile;
    int m_typeCurSel;
 
    Mat m_srcImage;
    Mat m_dstImage;
 
public:
    void on_MorphologyEx(int typeSel);
private slots:
    void on_pushButton_OpenImg_clicked();
    void on_comboBox_Type_currentIndexChanged(int index);
    void on_horizontalSlider_KernelValue_valueChanged(int value);
};
 
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
#include <QMessageBox>
 
#define KERNEL_MIN_VALUE    0
#define KERNEL_MAX_VALUE    40
 
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setWindowTitle(tr("Qt_OpenCV形态学运算"));
 
    //初始化变量
    m_KernelValue = 15;
    m_isOpenFile = false;
    m_typeCurSel = 0;
 
    //初始化控件
    ui->horizontalSlider_KernelValue->setMinimum(KERNEL_MIN_VALUE);
    ui->horizontalSlider_KernelValue->setMaximum(KERNEL_MAX_VALUE);
    ui->horizontalSlider_KernelValue->setValue(m_KernelValue);
    ui->label_KernelValue->setText(QString::number(m_KernelValue));
 
    ui->comboBox_Type->insertItem(0, "膨胀");
    ui->comboBox_Type->insertItem(1, "腐蚀");
    ui->comboBox_Type->insertItem(2, "开运算");
    ui->comboBox_Type->insertItem(3, "闭运算");
    ui->comboBox_Type->insertItem(4, "形态学梯度");
    ui->comboBox_Type->insertItem(5, "顶帽");
    ui->comboBox_Type->insertItem(6, "黑帽");
    ui->comboBox_Type->setCurrentIndex(m_typeCurSel);
}
 
MainWindow::~MainWindow()
{
    delete ui;
}
 
void MainWindow::on_pushButton_OpenImg_clicked()
{
    QString fileName = QFileDialog::getOpenFileName(this,tr("Open Image"),".",tr("Image File(*.png *.jpg *.jpeg *.bmp)"));
    if (fileName.isEmpty())
    {
        return;
    }
 
    m_srcImage = imread(fileName.toLatin1().data());//读取图片数据
    if (!m_srcImage.data)
    {
        m_isOpenFile = false;
        QMessageBox box(QMessageBox::Critical, "打开图像", "读取图像文件失败!请重新打开.");
        box.setStandardButtons(QMessageBox::Ok);
        box.setButtonText(QMessageBox::Ok, QString("确定"));
        box.exec();
        return;
    }
    m_isOpenFile = true;//修改打开标志
 
    Mat disImageTemp;
    cvtColor(m_srcImage, disImageTemp, COLOR_BGR2RGB);//图像格式转换
    QImage disImage = QImage((const unsigned char*)(disImageTemp.data),disImageTemp.cols,disImageTemp.rows,QImage::Format_RGB888);
    ui->label_OriginalImg->setPixmap(QPixmap::fromImage(disImage.scaled(ui->label_OriginalImg->width(), ui->label_OriginalImg->height(), Qt::KeepAspectRatio)));
 
    on_MorphologyEx(m_typeCurSel);
}
 
void MainWindow::on_horizontalSlider_KernelValue_valueChanged(int value)
{
    if (m_isOpenFile)
    {
        m_KernelValue = value;
        ui->label_KernelValue->setText(QString::number(m_KernelValue));
        on_MorphologyEx(m_typeCurSel);
    }
}
 
void MainWindow::on_MorphologyEx(int typeSel)
{
    //获取内核形状和尺寸
    Mat element = getStructuringElement(MORPH_RECT, Size(m_KernelValue * 2 + 1, m_KernelValue * 2 + 1), Point(m_KernelValue, m_KernelValue));
 
    //腐蚀操作
    switch (typeSel) {
    case 0:
        morphologyEx(m_srcImage, m_dstImage, MORPH_DILATE, element);
        break;
    case 1:
        morphologyEx(m_srcImage, m_dstImage, MORPH_ERODE, element);
        break;
    case 2:
        morphologyEx(m_srcImage, m_dstImage, MORPH_OPEN, element);
        break;
    case 3:
        morphologyEx(m_srcImage, m_dstImage, MORPH_CLOSE, element);
        break;
    case 4:
        morphologyEx(m_srcImage, m_dstImage, MORPH_GRADIENT, element);
        break;
    case 5:
        morphologyEx(m_srcImage, m_dstImage, MORPH_TOPHAT, element);
        break;
    case 6:
        morphologyEx(m_srcImage, m_dstImage, MORPH_BLACKHAT, element);
        break;
    default:
        break;
    }
 
    //显示
    cvtColor(m_dstImage, m_dstImage, COLOR_BGR2RGB);//图像格式转换
    QImage disImage = QImage((const unsigned char*)(m_dstImage.data),m_dstImage.cols,m_dstImage.rows,QImage::Format_RGB888);
    ui->label_ProcessedImg->setPixmap(QPixmap::fromImage(disImage.scaled(ui->label_ProcessedImg->width(), ui->label_ProcessedImg->height(), Qt::KeepAspectRatio)));
}
 
void MainWindow::on_comboBox_Type_currentIndexChanged(int index)
{
    m_typeCurSel = index;
    if (m_isOpenFile)
    {
        on_MorphologyEx(m_typeCurSel);
    }
}

pro 文件:

#-------------------------------------------------
#
# Project created by QtCreator 2019-07-12T14:41:02
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = OpenCV_Filter
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

CONFIG += c++11

SOURCES += \
        main.cpp \
        mainwindow.cpp

HEADERS += \
        mainwindow.h

FORMS += \
        mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
INCLUDEPATH += $$PWD/../opencv_qt/include\
             $$PWD/../opencv_qt/include/opencv\
              $$PWD/../opencv_qt/include/opencv2

unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_calib3d410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_core410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_dnn410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_features2d410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_flann410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_highgui410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_imgcodecs410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_imgproc410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_ml410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_objdetect410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_photo410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_stitching410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_video410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -llibopencv_videoio410
unix|win32: LIBS += -L$$PWD/../opencv_qt/bin/ -lopencv_ffmpeg410

实验效果:

或许用纯黑白的图像更能说明问题:

经过了膨胀操作,大圆中的小黑点被消除了

经过腐蚀操作,黑色背景上的小白点被消除了

开运算较好的保留了边缘

闭运算保留背景细节

形态学梯度突出了图像的轮廓

顶帽是原图像与开操作之间的差值图像,分离比邻近点亮一些的斑块

黑帽是闭操作图像与源图像的差值图像,分离比邻近点暗一点的斑块

发布了46 篇原创文章 · 获赞 5 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_36808245/article/details/95984562