OpenCV系列七 --- 非线性滤波
上一篇我们学习了了线性滤波(方框滤波、均值滤波以及高斯滤波),这节呢,我们来学习一下非线性滤波(中值滤波、双边滤波)。
一、理论
从连续的(或离散的)输入数据中滤除噪声和干扰以提取有用信息的过程称为滤波,而相应的装置称为滤波器。根据滤波器的输出是否为输入的线性函数,可将它分为线性滤波器和非线性滤波器两种。非线性滤波的原始数据与滤波结果是一种逻辑关系,即用逻辑运算实现,如最大值滤波器、最小值滤波器、中值滤波器等,是通过比较一定邻域内的灰度值大小来实现的,没有固定的模板,因而也就没有特定的转移函数。
1、中值滤波
中值滤波(Median filter)是一种典型的非线性滤波技术,基本思想是用像素点邻域灰度值的中值来代替该像素点的灰度值,该方法在去除脉冲噪声、椒盐噪声的同时又能保留图像边缘细节。
中值滤波是基于排序统计理论的一种能有效抑制噪声的非线性信号处理技术,其基本原理是把数字图像或数字序列中一点的值用该点的一个邻域中各点值的中值代替,让周围的像素值接近的真实值,从而消除孤立的噪声点,对于斑点噪声(speckle noise)和椒盐噪声(salt - and-pepper noise)来说尤其有用,因为它不依赖于邻域内那些与典型值差别很大的值。中值滤波器在处理连续图像窗函数时与线性滤波器的工作方式类似,但滤波过程却不再是加权运算。
中值滤波在一定的条件下可以克服常见线性滤波器如最小均方滤波、方框滤波器、均值滤波等带来的图像细节模糊,而且对滤除脉冲干扰及图像扫描噪声非常有效,也常用于保护边缘信息, 保存边缘的特性使它在不希望出现边缘模糊的场合也很有用,是非常经典的平滑噪声处理方法。
中值滤波的改进改进——加权中值滤波:在中值滤波的基础上加以改进,其性能在一定程度上优于中值滤波。
中值滤波与均值滤波器比较
中值滤波器与均值滤波器比较的优势:在均值滤波器中,由于噪声成分被放入平均计算中,所以输出受到了噪声的影响,但是在中值滤波器中,由于噪声成分很难选上,所以几乎不会影响到输出。因此同样用3x3区域进行处理,中值滤波消除的噪声能力更胜一筹。中值滤波无论是在消除噪声还是保存边缘方面都是一个不错的方法。
中值滤波器与均值滤波器比较的劣势:中值滤波花费的时间是均值滤波的5倍以上。
2.双边滤波
双边滤波(Bilateral filter)是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。具有简单、非迭代、局部的特点。
双边滤波器的好处是可以做边缘保存(edge preserving),一般过去用的维纳滤波或者高斯滤波去降噪,都会较明显地模糊边缘,对于高频细节的保护效果并不明显。双边滤波器,顾名思义比高斯滤波多了一个高斯方差sigma-d,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多影响到边缘上的像素值,这样就保证了边缘附近像素值的保存。但是由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净的滤掉,只能够对于低频信息进行较好的滤波。
二、OpenCV非线性滤波API函数详解
1、中值滤波
函数原型:
void medianBlur(InputArray src,
OutputArray dst,
int ksize)
函数功能:
平滑(模糊)处理一张图片,从src输入,而结果从dst输出。且对于多通道图片,每一个通道都单独进行处理,并且支持就地操作(In-placeoperation)。
参数详解:
-
第一个参数,InputArray类型的src,函数的输入参数,填1、3或者4通道的Mat类型的图像;当ksize为3或者5的时候,图像深度需为CV_8U,CV_16U,或CV_32F其中之一,而对于较大孔径尺寸的图片,它只能是CV_8U。
-
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。我们可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
-
第三个参数,int类型的ksize,孔径的线性尺寸(aperture linear size),注意这个参数必须是大于1的奇数,比如:3,5,7,9 ...
实例:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
int main()
{
//载入原图
Mat image = imread("lena.png");
//创建窗口
namedWindow("中值滤波【原图】");
namedWindow("中值滤波【效果图】");
//显示原图
imshow("中值滤波【原图】", image);
//进行中值滤波操作
Mat out;
medianBlur(image, out, 7);
//显示效果图
imshow("中值滤波【效果图】", out);
waitKey(0);
return 0;
}
实验结果:
中值滤波原图(左)与效果图(右)2、双边滤波
函数原型:
void bilateralFilter(InputArray src,
OutputArraydst,
int d,
double sigmaColor,
double sigmaSpace,
int borderType=BORDER_DEFAULT)
函数功能:
用双边滤波器来处理一张图片,由src输入图片,结果于dst输出。
参数详解:
-
第一个参数,InputArray类型的src,输入图像,即源图像,需要为8位或者浮点型单通道、三通道的图像。
-
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
-
第三个参数,int类型的d,表示在过滤过程中每个像素邻域的直径。如果这个值我们设其为非正数,那么OpenCV会从第五个参数sigmaSpace来计算出它来。
-
第四个参数,double类型的sigmaColor,颜色空间滤波器的sigma值。这个参数的值越大,就表明该像素邻域内有更宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
-
第五个参数,double类型的sigmaSpace坐标空间中滤波器的sigma值,坐标空间的标注方差。他的数值越大,意味着越远的像素会相互影响,从而使更大的区域足够相似的颜色获取相同的颜色。当d>0,d指定了邻域大小且与sigmaSpace无关。否则,d正比于sigmaSpace。
-
第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
实例:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
int main()
{
//载入原图
Mat image = imread("lena.png");
if (image.empty())
{
printf("image error!");
return -1;
}
//创建窗口
namedWindow("双边滤波【原图】");
namedWindow("双边滤波【效果图】");
//显示原图
imshow("双边滤波【原图】", image);
//进行双边滤波操作
Mat out;
bilateralFilter(image, out,
25, 25 * 2, 25 / 2);
//显示效果图
imshow("双边滤波【效果图】", out);
waitKey(0);
}
实验结果:
双边滤波原图(左)与效果图(右)
综合实例:
实现中值滤波与双边滤波在不同的卷积核之间的效果
#include <iostream>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
using namespace std;
using namespace cv;
// 标题的显示时常
int DELAY_CAPTION = 1500;
// 滤波的显示时常
int DELAY_BLUR = 100;
// 滤波卷积核的大小
int MAX_KERNEL_LENGTH = 31;
Mat src; Mat dst;
char window_name[] = "Smoothing Demo";
int display_caption(const char* caption);
int display_dst(int delay);
int main(int argc, char ** argv)
{
// 创建窗体
namedWindow(window_name, WINDOW_AUTOSIZE);
// 图像路径
const char* filename = "lena.png";
// 加载原图
src = imread(filename, IMREAD_COLOR);
if (src.empty())
{
printf(" Error opening image\n");
return -1;
}
// 显示原始图像
if (display_caption("Original Image") != 0)
{
return 0;
}
// 对原始图像进行备份
dst = src.clone();
if (display_caption("Median Blur") != 0)
{
return 0;
}
for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2)
{
// 中值滤波
medianBlur(src, dst, i);
if (display_dst(DELAY_BLUR) != 0)
{
return 0;
}
}
if (display_caption("Bilateral Blur") != 0)
{
return 0;
}
for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2)
{
// 双边滤波
bilateralFilter(src, dst,
i, i * 2, i / 2);
if (display_dst(DELAY_BLUR) != 0)
{
return 0;
}
}
display_caption("Done!");
return 0;
}
// 将caption绘制到图像上
int display_caption(const char* caption)
{
dst = Mat::zeros(src.size(),
src.type());
// 将caption显示到图像上
putText(dst, caption,
Point(src.cols / 4,
src.rows / 2),
FONT_HERSHEY_COMPLEX, 1,
Scalar(255, 255, 255));
// 显示图像
return display_dst(DELAY_CAPTION);
}
// 根据延时显示多长时间的图像
int display_dst(int delay)
{
// 显示图像
imshow(window_name, dst);
// 等待的时间
int c = waitKey(delay);
if (c >= 0)
{
return -1;
}
return 0;
}
好了,今天的OpenCV学到这里就结束了,喜欢的朋友可以给我点个赞哦!!!