一、开发环境
1、Windows 7 64位 SP1 旗舰版;
2、Qt 5.10.1;
3、OpenCV 3.4.1
腐蚀与膨胀作为形态学的基本操作,经过组合后可以很容易的实现更高一级的形态学运算。开运算即是先腐蚀后膨胀的得到的结果,即dst = open(src, element) = dilate(erode(src, element))。
开运算可以消除局部小的白色杂点,在纤细点处分离物体,并且在平滑较大物体的边界的同时不明显改变其面积。
闭运算过程与开运算过程相反,其是先膨胀后腐蚀的过程,可以消除图像中的小黑点,dst = close(src, element) = erode(dilate(src, element))。。
顶帽运算又常称为礼帽运算,是原始图像与图像开运算结果之差,其数学表达式为:dst = tophat(src, element) = src - open(src, element)。顶帽运算通常用来分离比邻近点亮一些的斑块,在一幅图像具有大幅背景且微小细节比较有规律的情况下,顶帽运算可以用来进行背景提取。
黑帽运算是图像闭运算结果与原始图像之差,其数学表达式为:dst = tophat(src, element) = close(src, element) - src。黑帽运算通常用来分离比邻近点暗一些的斑块,可以突出比原始图轮廓周围区域更暗的区域。
cv::morphologyEx()函数综合形态学大部分运算,可以实现形态学腐蚀、膨胀、开运算、闭运算、形态学梯度、顶帽、黑帽等。cv::morphologyEx()声明如下:
void 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() );
@param src Source image. The number of channels can be arbitrary. The depth should be one of
CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
@param dst Destination image of the same size and type as source image.
@param op Type of a morphological operation, see #MorphTypes
@param kernel Structuring element. It can be created using #getStructuringElement.
@param anchor Anchor position with the kernel. Negative values mean that the anchor is at the
kernel center.
@param iterations Number of times erosion and dilation are applied.
@param borderType Pixel extrapolation method, see #BorderTypes
@param borderValue Border value in case of a constant border. The default value has a special
meaning.
cv::morphologyEx()函数在OpenCV_3.4.1_Source\modules\imgproc\src\morph.cpp文件中定义,其中OpenCV_3.4.1_Source为源码下载解压后的文件夹。morphologyEx()函数定义如下:
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;
}
其中,参数op用于选择形态学运算类型,这里用到的有下面几个:
1)MORPH_OPEN:开运算
2)MORPH_CLOSE:闭运算
3)MORPH_GRADIENT:形态学梯度
4)MORPH_TOPHAT:顶帽
5)MORPH_BLACKHAP:黑帽
6)MORPH_ERODE:腐蚀
7)MORPH_DILATE:膨胀
界面设计如图1所示。
图1 界面设计
1、mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#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
2、mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#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);
}
}
运行效果如下:
图2 膨胀
图3 腐蚀
图4 开运算
图5 闭运算
图6 形态学梯度(1)
图7 形态学梯度(2)
图8 顶帽