参考教程:https://blog.csdn.net/qingyang8513/article/details/80413373
非线型滤波
线型滤波易于构造,易于从频率响应角度分析,但是,很多情况下,如果脉冲噪声、椒盐噪声明显时,或者需要较好的保存边缘时,线型滤波往往无法达到需要的效果,此时就需要用到非线性滤波。
常见的非线性滤波有:中值滤波、双边滤波。
中值滤波基本思想是用像素点邻域灰度值的中值来代替该像素点的灰度值,该方法在去除脉冲噪声、椒盐噪声的同时又能保存图像的边缘细节。和均值滤波相比,中值滤波可以有效滤除高频噪声的同时保存边缘,但其由于运算过程中需要进行排序,因此运行时间约是均值滤波的5倍以上。中值滤波邻域大小一般采用奇数点来运算。
双边滤波是基于空间分布的高斯滤波函数,比高斯滤波多一个高斯方差sigma-d,在边缘附近,离得远的像素不会对边缘上的像素影响太多,可以很好的做边缘保存。
3.1 中值滤波
OpenCV函数原型及参数说明:
medianBlur()
void cv::medianBlur | ( | InputArray | src, |
OutputArray | dst, | ||
int | ksize | ||
) |
#include <opencv2/imgproc.hpp>
使用中值滤波器模糊图像。
该函数使用具有ksize×ksize的中值滤波器来平滑图像光圈。多通道图像的每个通道都是独立处理的。支持就地操作。
参数
SRC | 输入1,3-或4通道图像; 当ksize为3或5时,图像深度应为CV_8U,CV_16U或CV_32F,对于较大的光圈尺寸,它只能是CV_8U。 |
DST | 与src具有相同大小和类型的目标数组。 |
ksize | 孔径线性尺寸; 它必须是奇数且大于1,例如:3,5,7 ...... |
https://docs.opencv.org/4.1.0/d4/d86/group__imgproc__filter.html#ga564869aa33e58769b4469101aac458f9
3.2 双边滤波
OpenCV函数原型及参数说明:
bilateralFilter()
void cv::bilateralFilter | ( | InputArray | src, |
OutputArray | dst, | ||
int | d, | ||
double | sigmaColor, | ||
double | sigmaSpace, | ||
int | borderType = BORDER_DEFAULT | ||
) |
#include <opencv2/imgproc.hpp>
将双边滤波器应用于图像。
该函数对输入图像应用双边过滤,如http://www.dai.e.ac.uk/cvonline/local_copies/manduchi1/bilateral_filtering.html所述,双边过滤可以很好地减少不必要的噪声,同时保持边缘相当清晰。然而,与大多数过滤器相比,它非常慢。
西格玛值:为了简单起见,您可以将2西格玛值设置为相同的值。如果它们很小(< 10),过滤器将不会有太大的效果,而如果它们很大(> 150),它们将有非常强的效果,使图像看起来“卡通化”。
过滤器大小:大型过滤器(d > 5)非常慢,因此建议对实时应用程序使用d=5,对于需要进行重噪声过滤的离线应用程序可能使用d=9。
这个过滤器不支持就地工作。
参数
Parameters
src | 源8位或浮点数,1通道或3通道图像 |
dst | 与src大小和类型相同的目标映像 |
d | 用于滤波的每个像素邻域的直径。如果它是非正数,则从sigmaSpace计算。 |
sigmaColor | F在颜色空间中过滤。较大的参数值意味着像素邻域内的较远颜色(参见sigmaSpace)将混合在一起,从而产生较大的半等色区域。 |
sigmaSpace | 在坐标空间中过滤。参数值越大,表示距离越远的像素只要颜色足够接近就会相互影响(参见sigmaColor)。当d>0时,它指定了与sigmaSpace无关的邻域大小。否则,d与sigmaSpace成正比。 |
borderType | 用于推断图像外部像素的边框模式,请参见BorderTypes |
示例设计与测试
4.1 界面设计
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>979</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_DValue">
<property name="geometry">
<rect>
<x>180</x>
<y>320</y>
<width>671</width>
<height>19</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<widget class="QPushButton" name="pushButton_OpenImg">
<property name="geometry">
<rect>
<x>880</x>
<y>320</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>打开图像</string>
</property>
</widget>
<widget class="QLabel" name="label_ProcessedImg2">
<property name="geometry">
<rect>
<x>690</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 Image2</string>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>690</x>
<y>20</y>
<width>54</width>
<height>12</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>979</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.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 = 3;
m_isOpenFile = false;
//初始化控件
ui->horizontalSlider_DValue->setMinimum(KERNEL_MIN_VALUE);
ui->horizontalSlider_DValue->setMaximum(KERNEL_MAX_VALUE);
ui->horizontalSlider_DValue->setValue(m_KernelValue);
ui->label_KernelValue->setText(QString::number(m_KernelValue));
}
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_MedianBlur();
on_BilateralFilter();
}
void MainWindow::on_horizontalSlider_DValue_valueChanged(int value)
{
if (m_isOpenFile)
{
m_KernelValue = value;
ui->label_KernelValue->setText(QString::number(m_KernelValue));
on_MedianBlur();
on_BilateralFilter();
}
}
void MainWindow::on_MedianBlur()
{
medianBlur(m_srcImage, m_dstImage, m_KernelValue * 2 + 1);//中值滤波
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_BilateralFilter()
{
bilateralFilter(m_srcImage, m_dstImage, m_KernelValue, m_KernelValue * 2, m_KernelValue / 2);//双边滤波
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_ProcessedImg2->setPixmap(QPixmap::fromImage(disImage.scaled(ui->label_ProcessedImg2->width(), ui->label_ProcessedImg2->height(), Qt::KeepAspectRatio)));
}
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;
Mat m_srcImage;
Mat m_dstImage;
public:
void on_MedianBlur(void);
void on_BilateralFilter(void);
private slots:
void on_pushButton_OpenImg_clicked();
void on_horizontalSlider_DValue_valueChanged(int value);
};
#endif // MAINWINDOW_H
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
效果:
可以看出,双边滤波与中值滤波相比能够更清晰地保留边缘,前者实际上就是先对图像做高斯模糊,再对模糊后的图像做边缘保持的操作。用时上,双边滤波更慢一些。